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 try { 23 const url = this.devServerManager.getDefaultDevServer().getNativeRuntimeUrl()!; 24 25 printQRCode(url); 26 Log.log(printItem(chalk`Metro waiting on {underline ${url}}`)); 27 // TODO: if development build, change this message! 28 Log.log(printItem('Scan the QR code above with Expo Go (Android) or the Camera app (iOS)')); 29 } catch (error) { 30 // @ts-ignore: If there is no development build scheme, then skip the QR code. 31 if (error.code !== 'NO_DEV_CLIENT_SCHEME') { 32 throw error; 33 } else { 34 const serverUrl = this.devServerManager.getDefaultDevServer().getDevServerUrl(); 35 Log.log(printItem(chalk`Metro waiting on {underline ${serverUrl}}`)); 36 Log.log(printItem(`Linking is disabled because the client scheme cannot be resolved.`)); 37 } 38 } 39 } 40 41 const webDevServer = this.devServerManager.getWebDevServer(); 42 const webUrl = webDevServer?.getDevServerUrl({ hostType: 'localhost' }); 43 if (webUrl) { 44 Log.log(); 45 Log.log(printItem(chalk`Web is waiting on {underline ${webUrl}}`)); 46 } 47 48 printUsage(options, { verbose: false }); 49 printHelp(); 50 Log.log(); 51 } 52 53 async openJsInspectorAsync() { 54 Log.log('Opening JavaScript inspector in the browser...'); 55 const port = this.devServerManager.getNativeDevServerPort(); 56 assert(port, 'Metro dev server is not running'); 57 const metroServerOrigin = `http://localhost:${port}`; 58 const apps = await queryAllInspectorAppsAsync(metroServerOrigin); 59 if (!apps.length) { 60 Log.warn( 61 `No compatible apps connected. This feature is only available for apps using the Hermes runtime. ${learnMore( 62 'https://docs.expo.dev/guides/using-hermes/' 63 )}` 64 ); 65 return; 66 } 67 for (const app of apps) { 68 openJsInspector(app); 69 } 70 } 71 72 reloadApp() { 73 Log.log(`${BLT} Reloading apps`); 74 // Send reload requests over the dev servers 75 this.devServerManager.broadcastMessage('reload'); 76 } 77 78 async openMoreToolsAsync() { 79 try { 80 // Options match: Chrome > View > Developer 81 const value = await selectAsync(chalk`Dev tools {dim (native only)}`, [ 82 { title: 'Inspect elements', value: 'toggleElementInspector' }, 83 { title: 'Toggle performance monitor', value: 'togglePerformanceMonitor' }, 84 { title: 'Toggle developer menu', value: 'toggleDevMenu' }, 85 { title: 'Reload app', value: 'reload' }, 86 // TODO: Maybe a "View Source" option to open code. 87 // Toggling Remote JS Debugging is pretty rough, so leaving it disabled. 88 // { title: 'Toggle Remote Debugging', value: 'toggleRemoteDebugging' }, 89 ]); 90 this.devServerManager.broadcastMessage('sendDevCommand', { name: value }); 91 } catch (error: any) { 92 debug(error); 93 // do nothing 94 } finally { 95 printHelp(); 96 } 97 } 98 99 toggleDevMenu() { 100 Log.log(`${BLT} Toggling dev menu`); 101 this.devServerManager.broadcastMessage('devMenu'); 102 } 103} 104