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 webUrl = this.devServerManager 42 .getWebDevServer() 43 ?.getDevServerUrl({ hostType: 'localhost' }); 44 if (webUrl) { 45 Log.log(); 46 Log.log(printItem(chalk`Webpack waiting on {underline ${webUrl}}`)); 47 Log.log( 48 chalk.gray(printItem('Expo Webpack (web) is in beta, and subject to breaking changes!')) 49 ); 50 } 51 52 printUsage(options, { verbose: false }); 53 printHelp(); 54 Log.log(); 55 } 56 57 async openJsInspectorAsync() { 58 Log.log('Opening JavaScript inspector in the browser...'); 59 const port = this.devServerManager.getNativeDevServerPort(); 60 assert(port, 'Metro dev server is not running'); 61 const metroServerOrigin = `http://localhost:${port}`; 62 const apps = await queryAllInspectorAppsAsync(metroServerOrigin); 63 if (!apps.length) { 64 Log.warn( 65 `No compatible apps connected. This feature is only available for apps using the Hermes runtime. ${learnMore( 66 'https://docs.expo.dev/guides/using-hermes/' 67 )}` 68 ); 69 return; 70 } 71 for (const app of apps) { 72 openJsInspector(app); 73 } 74 } 75 76 reloadApp() { 77 Log.log(`${BLT} Reloading apps`); 78 // Send reload requests over the dev servers 79 this.devServerManager.broadcastMessage('reload'); 80 } 81 82 async openMoreToolsAsync() { 83 try { 84 // Options match: Chrome > View > Developer 85 const value = await selectAsync(chalk`Dev tools {dim (native only)}`, [ 86 { title: 'Inspect elements', value: 'toggleElementInspector' }, 87 { title: 'Toggle performance monitor', value: 'togglePerformanceMonitor' }, 88 { title: 'Toggle developer menu', value: 'toggleDevMenu' }, 89 { title: 'Reload app', value: 'reload' }, 90 // TODO: Maybe a "View Source" option to open code. 91 // Toggling Remote JS Debugging is pretty rough, so leaving it disabled. 92 // { title: 'Toggle Remote Debugging', value: 'toggleRemoteDebugging' }, 93 ]); 94 this.devServerManager.broadcastMessage('sendDevCommand', { name: value }); 95 } catch (error: any) { 96 debug(error); 97 // do nothing 98 } finally { 99 printHelp(); 100 } 101 } 102 103 toggleDevMenu() { 104 Log.log(`${BLT} Toggling dev menu`); 105 this.devServerManager.broadcastMessage('devMenu'); 106 } 107} 108