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