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 metroServerOrigin = this.devServerManager.getDefaultDevServer().getJsInspectorBaseUrl(); 66 assert(metroServerOrigin, 'Metro dev server is not running'); 67 const apps = await queryAllInspectorAppsAsync(metroServerOrigin); 68 if (!apps.length) { 69 Log.warn( 70 `No compatible apps connected. JavaScript Debugging can only be used with the Hermes engine. ${learnMore( 71 'https://docs.expo.dev/guides/using-hermes/' 72 )}` 73 ); 74 return; 75 } 76 for (const app of apps) { 77 openJsInspector(app); 78 } 79 } 80 81 reloadApp() { 82 Log.log(`${BLT} Reloading apps`); 83 // Send reload requests over the dev servers 84 this.devServerManager.broadcastMessage('reload'); 85 } 86 87 async openMoreToolsAsync() { 88 try { 89 // Options match: Chrome > View > Developer 90 const value = await selectAsync(chalk`Dev tools {dim (native only)}`, [ 91 { title: 'Inspect elements', value: 'toggleElementInspector' }, 92 { title: 'Toggle performance monitor', value: 'togglePerformanceMonitor' }, 93 { title: 'Toggle developer menu', value: 'toggleDevMenu' }, 94 { title: 'Reload app', value: 'reload' }, 95 // TODO: Maybe a "View Source" option to open code. 96 // Toggling Remote JS Debugging is pretty rough, so leaving it disabled. 97 // { title: 'Toggle Remote Debugging', value: 'toggleRemoteDebugging' }, 98 ]); 99 this.devServerManager.broadcastMessage('sendDevCommand', { name: value }); 100 } catch (error: any) { 101 debug(error); 102 // do nothing 103 } finally { 104 printHelp(); 105 } 106 } 107 108 toggleDevMenu() { 109 Log.log(`${BLT} Toggling dev menu`); 110 this.devServerManager.broadcastMessage('devMenu'); 111 } 112} 113