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 try { 77 for (const app of apps) { 78 await openJsInspector(app); 79 } 80 } catch (error: any) { 81 Log.error('Failed to open JavaScript inspector. This is often an issue with Google Chrome.'); 82 Log.exception(error); 83 } 84 } 85 86 reloadApp() { 87 Log.log(`${BLT} Reloading apps`); 88 // Send reload requests over the dev servers 89 this.devServerManager.broadcastMessage('reload'); 90 } 91 92 async openMoreToolsAsync() { 93 try { 94 // Options match: Chrome > View > Developer 95 const value = await selectAsync(chalk`Dev tools {dim (native only)}`, [ 96 { title: 'Inspect elements', value: 'toggleElementInspector' }, 97 { title: 'Toggle performance monitor', value: 'togglePerformanceMonitor' }, 98 { title: 'Toggle developer menu', value: 'toggleDevMenu' }, 99 { title: 'Reload app', value: 'reload' }, 100 // TODO: Maybe a "View Source" option to open code. 101 // Toggling Remote JS Debugging is pretty rough, so leaving it disabled. 102 // { title: 'Toggle Remote Debugging', value: 'toggleRemoteDebugging' }, 103 ]); 104 this.devServerManager.broadcastMessage('sendDevCommand', { name: value }); 105 } catch (error: any) { 106 debug(error); 107 // do nothing 108 } finally { 109 printHelp(); 110 } 111 } 112 113 toggleDevMenu() { 114 Log.log(`${BLT} Toggling dev menu`); 115 this.devServerManager.broadcastMessage('devMenu'); 116 } 117} 118