1import { openJsInspector, queryAllInspectorAppsAsync } from '@expo/dev-server'; 2import assert from 'assert'; 3import chalk from 'chalk'; 4 5import * as Log from '../../log'; 6import { learnMore } from '../../utils/link'; 7import { selectAsync } from '../../utils/prompts'; 8import { DevServerManager } from '../server/DevServerManager'; 9import { BLT, printHelp, printItem, printQRCode, printUsage, StartOptions } from './commandsTable'; 10 11const debug = require('debug')('expo:start:interface:interactiveActions') as typeof console.log; 12 13/** Wraps the DevServerManager and adds an interface for user actions. */ 14export class DevServerManagerActions { 15 constructor(private devServerManager: DevServerManager) {} 16 17 printDevServerInfo( 18 options: Pick<StartOptions, 'devClient' | 'isWebSocketsEnabled' | 'platforms'> 19 ) { 20 // If native dev server is running, print its URL. 21 if (this.devServerManager.getNativeDevServerPort()) { 22 const devServer = this.devServerManager.getDefaultDevServer(); 23 try { 24 const nativeRuntimeUrl = devServer.getNativeRuntimeUrl()!; 25 const interstitialPageUrl = devServer.getRedirectUrl(); 26 27 printQRCode(interstitialPageUrl ?? nativeRuntimeUrl); 28 29 if (interstitialPageUrl) { 30 Log.log( 31 printItem( 32 chalk`Choose an app to open your project at {underline ${interstitialPageUrl}}` 33 ) 34 ); 35 } 36 Log.log(printItem(chalk`Metro waiting on {underline ${nativeRuntimeUrl}}`)); 37 // TODO: if development build, change this message! 38 Log.log(printItem('Scan the QR code above with Expo Go (Android) or the Camera app (iOS)')); 39 } catch (error) { 40 // @ts-ignore: If there is no development build scheme, then skip the QR code. 41 if (error.code !== 'NO_DEV_CLIENT_SCHEME') { 42 throw error; 43 } else { 44 const serverUrl = devServer.getDevServerUrl(); 45 Log.log(printItem(chalk`Metro waiting on {underline ${serverUrl}}`)); 46 Log.log(printItem(`Linking is disabled because the client scheme cannot be resolved.`)); 47 } 48 } 49 } 50 51 const webDevServer = this.devServerManager.getWebDevServer(); 52 const webUrl = webDevServer?.getDevServerUrl({ hostType: 'localhost' }); 53 if (webUrl) { 54 Log.log(); 55 Log.log(printItem(chalk`Web is waiting on {underline ${webUrl}}`)); 56 } 57 58 printUsage(options, { verbose: false }); 59 printHelp(); 60 Log.log(); 61 } 62 63 async openJsInspectorAsync() { 64 Log.log('Opening JavaScript inspector in the browser...'); 65 const port = this.devServerManager.getNativeDevServerPort(); 66 assert(port, 'Metro dev server is not running'); 67 const metroServerOrigin = `http://localhost:${port}`; 68 const apps = await queryAllInspectorAppsAsync(metroServerOrigin); 69 if (!apps.length) { 70 Log.warn( 71 `No compatible apps connected. This feature is only available for apps using the Hermes runtime. ${learnMore( 72 'https://docs.expo.dev/guides/using-hermes/' 73 )}` 74 ); 75 return; 76 } 77 for (const app of apps) { 78 openJsInspector(app); 79 } 80 } 81 82 reloadApp() { 83 Log.log(`${BLT} Reloading apps`); 84 // Send reload requests over the dev servers 85 this.devServerManager.broadcastMessage('reload'); 86 } 87 88 async openMoreToolsAsync() { 89 try { 90 // Options match: Chrome > View > Developer 91 const value = await selectAsync(chalk`Dev tools {dim (native only)}`, [ 92 { title: 'Inspect elements', value: 'toggleElementInspector' }, 93 { title: 'Toggle performance monitor', value: 'togglePerformanceMonitor' }, 94 { title: 'Toggle developer menu', value: 'toggleDevMenu' }, 95 { title: 'Reload app', value: 'reload' }, 96 // TODO: Maybe a "View Source" option to open code. 97 // Toggling Remote JS Debugging is pretty rough, so leaving it disabled. 98 // { title: 'Toggle Remote Debugging', value: 'toggleRemoteDebugging' }, 99 ]); 100 this.devServerManager.broadcastMessage('sendDevCommand', { name: value }); 101 } catch (error: any) { 102 debug(error); 103 // do nothing 104 } finally { 105 printHelp(); 106 } 107 } 108 109 toggleDevMenu() { 110 Log.log(`${BLT} Toggling dev menu`); 111 this.devServerManager.broadcastMessage('devMenu'); 112 } 113} 114