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
11/** Wraps the DevServerManager and adds an interface for user actions. */
12export class DevServerManagerActions {
13  constructor(private devServerManager: DevServerManager) {}
14
15  printDevServerInfo(
16    options: Pick<StartOptions, 'devClient' | 'isWebSocketsEnabled' | 'platforms'>
17  ) {
18    // If native dev server is running, print its URL.
19    if (this.devServerManager.getNativeDevServerPort()) {
20      try {
21        const url = this.devServerManager.getDefaultDevServer().getNativeRuntimeUrl()!;
22
23        printQRCode(url);
24        Log.log(printItem(chalk`Metro waiting on {underline ${url}}`));
25        // TODO: if development build, change this message!
26        Log.log(printItem('Scan the QR code above with Expo Go (Android) or the Camera app (iOS)'));
27      } catch (error) {
28        // @ts-ignore: If there is no development build scheme, then skip the QR code.
29        if (error.code !== 'NO_DEV_CLIENT_SCHEME') {
30          throw error;
31        } else {
32          const serverUrl = this.devServerManager.getDefaultDevServer().getDevServerUrl();
33          Log.log(printItem(chalk`Metro waiting on {underline ${serverUrl}}`));
34          Log.log(printItem(`Linking is disabled because the client scheme cannot be resolved.`));
35        }
36      }
37    }
38
39    const webUrl = this.devServerManager
40      .getWebDevServer()
41      ?.getDevServerUrl({ hostType: 'localhost' });
42    if (webUrl) {
43      Log.log();
44      Log.log(printItem(chalk`Webpack waiting on {underline ${webUrl}}`));
45      Log.log(
46        chalk.gray(printItem('Expo Webpack (web) is in beta, and subject to breaking changes!'))
47      );
48    }
49
50    printUsage(options, { verbose: false });
51    printHelp();
52    Log.log();
53  }
54
55  async openJsInspectorAsync() {
56    Log.log('Opening JavaScript inspector in the browser...');
57    const port = this.devServerManager.getNativeDevServerPort();
58    assert(port, 'Metro dev server is not running');
59    const metroServerOrigin = `http://localhost:${port}`;
60    const apps = await queryAllInspectorAppsAsync(metroServerOrigin);
61    if (!apps.length) {
62      Log.warn(
63        `No compatible apps connected. This feature is only available for apps using the Hermes runtime. ${learnMore(
64          'https://docs.expo.dev/guides/using-hermes/'
65        )}`
66      );
67      return;
68    }
69    for (const app of apps) {
70      openJsInspector(app);
71    }
72  }
73
74  reloadApp() {
75    Log.log(`${BLT} Reloading apps`);
76    // Send reload requests over the dev servers
77    this.devServerManager.broadcastMessage('reload');
78  }
79
80  async openMoreToolsAsync() {
81    try {
82      // Options match: Chrome > View > Developer
83      const value = await selectAsync(chalk`Dev tools {dim (native only)}`, [
84        { title: 'Inspect elements', value: 'toggleElementInspector' },
85        { title: 'Toggle performance monitor', value: 'togglePerformanceMonitor' },
86        { title: 'Toggle developer menu', value: 'toggleDevMenu' },
87        { title: 'Reload app', value: 'reload' },
88        // TODO: Maybe a "View Source" option to open code.
89        // Toggling Remote JS Debugging is pretty rough, so leaving it disabled.
90        // { title: 'Toggle Remote Debugging', value: 'toggleRemoteDebugging' },
91      ]);
92      this.devServerManager.broadcastMessage('sendDevCommand', { name: value });
93    } catch (error: any) {
94      Log.debug(error);
95      // do nothing
96    } finally {
97      printHelp();
98    }
99  }
100
101  toggleDevMenu() {
102    Log.log(`${BLT} Toggling dev menu`);
103    this.devServerManager.broadcastMessage('devMenu');
104  }
105}
106