18d307f52SEvan Baconimport assert from 'assert'; 28d307f52SEvan Baconimport chalk from 'chalk'; 38d307f52SEvan Bacon 48a424bebSJames Ideimport { BLT, printHelp, printItem, printQRCode, printUsage, StartOptions } from './commandsTable'; 58d307f52SEvan Baconimport * as Log from '../../log'; 6fd055557SKudo Chienimport { delayAsync } from '../../utils/delay'; 78d307f52SEvan Baconimport { learnMore } from '../../utils/link'; 8065a44f7SCedric van Puttenimport { openBrowserAsync } from '../../utils/open'; 98d307f52SEvan Baconimport { selectAsync } from '../../utils/prompts'; 108d307f52SEvan Baconimport { DevServerManager } from '../server/DevServerManager'; 11fd055557SKudo Chienimport { 12fd055557SKudo Chien addReactDevToolsReloadListener, 13fd055557SKudo Chien startReactDevToolsProxyAsync, 14fd055557SKudo Chien} from '../server/ReactDevToolsProxy'; 15*edeec536SEvan Baconimport { 16*edeec536SEvan Bacon openJsInspector, 17*edeec536SEvan Bacon queryAllInspectorAppsAsync, 18*edeec536SEvan Bacon} from '../server/middleware/inspector/JsInspector'; 198d307f52SEvan Bacon 20474a7a4bSEvan Baconconst debug = require('debug')('expo:start:interface:interactiveActions') as typeof console.log; 21474a7a4bSEvan Bacon 228d307f52SEvan Bacon/** Wraps the DevServerManager and adds an interface for user actions. */ 238d307f52SEvan Baconexport class DevServerManagerActions { 248d307f52SEvan Bacon constructor(private devServerManager: DevServerManager) {} 258d307f52SEvan Bacon 268d307f52SEvan Bacon printDevServerInfo( 278d307f52SEvan Bacon options: Pick<StartOptions, 'devClient' | 'isWebSocketsEnabled' | 'platforms'> 288d307f52SEvan Bacon ) { 298d307f52SEvan Bacon // If native dev server is running, print its URL. 308d307f52SEvan Bacon if (this.devServerManager.getNativeDevServerPort()) { 31212e3a1aSEric Samelson const devServer = this.devServerManager.getDefaultDevServer(); 328d307f52SEvan Bacon try { 33212e3a1aSEric Samelson const nativeRuntimeUrl = devServer.getNativeRuntimeUrl()!; 34212e3a1aSEric Samelson const interstitialPageUrl = devServer.getRedirectUrl(); 358d307f52SEvan Bacon 36212e3a1aSEric Samelson printQRCode(interstitialPageUrl ?? nativeRuntimeUrl); 37212e3a1aSEric Samelson 38212e3a1aSEric Samelson if (interstitialPageUrl) { 39212e3a1aSEric Samelson Log.log( 40212e3a1aSEric Samelson printItem( 41212e3a1aSEric Samelson chalk`Choose an app to open your project at {underline ${interstitialPageUrl}}` 42212e3a1aSEric Samelson ) 43212e3a1aSEric Samelson ); 44212e3a1aSEric Samelson } 45212e3a1aSEric Samelson Log.log(printItem(chalk`Metro waiting on {underline ${nativeRuntimeUrl}}`)); 46a7e47f4dSEvan Bacon if (options.devClient === false) { 478d307f52SEvan Bacon // TODO: if development build, change this message! 48a7e47f4dSEvan Bacon Log.log( 49a7e47f4dSEvan Bacon printItem('Scan the QR code above with Expo Go (Android) or the Camera app (iOS)') 50a7e47f4dSEvan Bacon ); 51a7e47f4dSEvan Bacon } else { 52a7e47f4dSEvan Bacon Log.log( 53a7e47f4dSEvan Bacon printItem( 54a7e47f4dSEvan Bacon 'Scan the QR code above to open the project in a development build. ' + 55a7e47f4dSEvan Bacon learnMore('https://expo.fyi/start') 56a7e47f4dSEvan Bacon ) 57a7e47f4dSEvan Bacon ); 58a7e47f4dSEvan Bacon } 598d307f52SEvan Bacon } catch (error) { 60a7e47f4dSEvan Bacon console.log('err', error); 618d307f52SEvan Bacon // @ts-ignore: If there is no development build scheme, then skip the QR code. 628d307f52SEvan Bacon if (error.code !== 'NO_DEV_CLIENT_SCHEME') { 638d307f52SEvan Bacon throw error; 648d307f52SEvan Bacon } else { 65212e3a1aSEric Samelson const serverUrl = devServer.getDevServerUrl(); 668d307f52SEvan Bacon Log.log(printItem(chalk`Metro waiting on {underline ${serverUrl}}`)); 678d307f52SEvan Bacon Log.log(printItem(`Linking is disabled because the client scheme cannot be resolved.`)); 688d307f52SEvan Bacon } 698d307f52SEvan Bacon } 708d307f52SEvan Bacon } 718d307f52SEvan Bacon 726d6b81f9SEvan Bacon const webDevServer = this.devServerManager.getWebDevServer(); 736d6b81f9SEvan Bacon const webUrl = webDevServer?.getDevServerUrl({ hostType: 'localhost' }); 748d307f52SEvan Bacon if (webUrl) { 758d307f52SEvan Bacon Log.log(); 766d6b81f9SEvan Bacon Log.log(printItem(chalk`Web is waiting on {underline ${webUrl}}`)); 778d307f52SEvan Bacon } 788d307f52SEvan Bacon 798d307f52SEvan Bacon printUsage(options, { verbose: false }); 808d307f52SEvan Bacon printHelp(); 818d307f52SEvan Bacon Log.log(); 828d307f52SEvan Bacon } 838d307f52SEvan Bacon 848d307f52SEvan Bacon async openJsInspectorAsync() { 858d307f52SEvan Bacon Log.log('Opening JavaScript inspector in the browser...'); 8657a0d514SKudo Chien const metroServerOrigin = this.devServerManager.getDefaultDevServer().getJsInspectorBaseUrl(); 8723e1175dSKudo Chien assert(metroServerOrigin, 'Metro dev server is not running'); 888d307f52SEvan Bacon const apps = await queryAllInspectorAppsAsync(metroServerOrigin); 898d307f52SEvan Bacon if (!apps.length) { 908d307f52SEvan Bacon Log.warn( 9148103a3dSEvan Bacon `No compatible apps connected. JavaScript Debugging can only be used with the Hermes engine. ${learnMore( 928d307f52SEvan Bacon 'https://docs.expo.dev/guides/using-hermes/' 938d307f52SEvan Bacon )}` 948d307f52SEvan Bacon ); 958d307f52SEvan Bacon return; 968d307f52SEvan Bacon } 977a619cc1SEvan Bacon try { 988d307f52SEvan Bacon for (const app of apps) { 997a619cc1SEvan Bacon await openJsInspector(app); 1007a619cc1SEvan Bacon } 1017a619cc1SEvan Bacon } catch (error: any) { 1027a619cc1SEvan Bacon Log.error('Failed to open JavaScript inspector. This is often an issue with Google Chrome.'); 1037a619cc1SEvan Bacon Log.exception(error); 1048d307f52SEvan Bacon } 1058d307f52SEvan Bacon } 1068d307f52SEvan Bacon 1078d307f52SEvan Bacon reloadApp() { 1088d307f52SEvan Bacon Log.log(`${BLT} Reloading apps`); 1098d307f52SEvan Bacon // Send reload requests over the dev servers 1108d307f52SEvan Bacon this.devServerManager.broadcastMessage('reload'); 1118d307f52SEvan Bacon } 1128d307f52SEvan Bacon 1138d307f52SEvan Bacon async openMoreToolsAsync() { 1148d307f52SEvan Bacon try { 1158d307f52SEvan Bacon // Options match: Chrome > View > Developer 1168d307f52SEvan Bacon const value = await selectAsync(chalk`Dev tools {dim (native only)}`, [ 1178d307f52SEvan Bacon { title: 'Inspect elements', value: 'toggleElementInspector' }, 1188d307f52SEvan Bacon { title: 'Toggle performance monitor', value: 'togglePerformanceMonitor' }, 1198d307f52SEvan Bacon { title: 'Toggle developer menu', value: 'toggleDevMenu' }, 1208d307f52SEvan Bacon { title: 'Reload app', value: 'reload' }, 121fd055557SKudo Chien { title: 'Start React devtools', value: 'startReactDevTools' }, 1228d307f52SEvan Bacon // TODO: Maybe a "View Source" option to open code. 1238d307f52SEvan Bacon // Toggling Remote JS Debugging is pretty rough, so leaving it disabled. 1248d307f52SEvan Bacon // { title: 'Toggle Remote Debugging', value: 'toggleRemoteDebugging' }, 1258d307f52SEvan Bacon ]); 126fd055557SKudo Chien if (value === 'startReactDevTools') { 127fd055557SKudo Chien this.startReactDevToolsAsync(); 128fd055557SKudo Chien } else { 1298d307f52SEvan Bacon this.devServerManager.broadcastMessage('sendDevCommand', { name: value }); 130fd055557SKudo Chien } 13129975bfdSEvan Bacon } catch (error: any) { 132474a7a4bSEvan Bacon debug(error); 1338d307f52SEvan Bacon // do nothing 1348d307f52SEvan Bacon } finally { 1358d307f52SEvan Bacon printHelp(); 1368d307f52SEvan Bacon } 1378d307f52SEvan Bacon } 1388d307f52SEvan Bacon 139fd055557SKudo Chien async startReactDevToolsAsync() { 140fd055557SKudo Chien await startReactDevToolsProxyAsync(); 141fd055557SKudo Chien const url = this.devServerManager.getDefaultDevServer().getReactDevToolsUrl(); 142fd055557SKudo Chien await openBrowserAsync(url); 143fd055557SKudo Chien addReactDevToolsReloadListener(() => { 144fd055557SKudo Chien this.reconnectReactDevTools(); 145fd055557SKudo Chien }); 146fd055557SKudo Chien this.reconnectReactDevTools(); 147fd055557SKudo Chien } 148fd055557SKudo Chien 149fd055557SKudo Chien async reconnectReactDevTools() { 150fd055557SKudo Chien // Wait a little time for react-devtools to be initialized in browser 151fd055557SKudo Chien await delayAsync(3000); 152fd055557SKudo Chien this.devServerManager.broadcastMessage('sendDevCommand', { name: 'reconnectReactDevTools' }); 153fd055557SKudo Chien } 154fd055557SKudo Chien 1558d307f52SEvan Bacon toggleDevMenu() { 1568d307f52SEvan Bacon Log.log(`${BLT} Toggling dev menu`); 1578d307f52SEvan Bacon this.devServerManager.broadcastMessage('devMenu'); 1588d307f52SEvan Bacon } 1598d307f52SEvan Bacon} 160