1import chalk from 'chalk';
2import type { NextHandleFunction } from 'connect';
3import type { IncomingMessage, ServerResponse } from 'http';
4import net from 'net';
5import { TLSSocket } from 'tls';
6import { URL } from 'url';
7
8import { openJsInspector, queryInspectorAppAsync } from './JsInspector';
9
10export default function createJsInspectorMiddleware(): NextHandleFunction {
11  return async function (req: IncomingMessage, res: ServerResponse, next: (err?: Error) => void) {
12    const { origin, searchParams } = new URL(req.url ?? '/', getServerBase(req));
13    const applicationId = searchParams.get('applicationId');
14    if (!applicationId) {
15      res.writeHead(400).end('Missing applicationId');
16      return;
17    }
18
19    const app = await queryInspectorAppAsync(origin, applicationId);
20    if (!app) {
21      res.writeHead(404).end('Unable to find inspector target from metro-inspector-proxy');
22      console.warn(
23        chalk.yellow(
24          'No compatible apps connected. JavaScript Debugging can only be used with the Hermes engine.'
25        )
26      );
27      return;
28    }
29
30    if (req.method === 'GET') {
31      const data = JSON.stringify(app);
32      res.writeHead(200, {
33        'Content-Type': 'application/json; charset=UTF-8',
34        'Cache-Control': 'no-cache',
35        'Content-Length': data.length.toString(),
36      });
37      res.end(data);
38    } else if (req.method === 'POST' || req.method === 'PUT') {
39      try {
40        await openJsInspector(app);
41      } catch (error: any) {
42        // abort(Error: Command failed: osascript -e POSIX path of (path to application "google chrome")
43        // 15:50: execution error: Google Chrome got an error: Application isn’t running. (-600)
44
45        console.error(
46          chalk.red('Error launching JS inspector: ' + (error?.message ?? 'Unknown error occurred'))
47        );
48        res.writeHead(500);
49        res.end();
50        return;
51      }
52      res.end();
53    } else {
54      res.writeHead(405);
55    }
56  };
57}
58
59function getServerBase(req: IncomingMessage): string {
60  const scheme =
61    req.socket instanceof TLSSocket && req.socket.encrypted === true ? 'https' : 'http';
62  const { localAddress, localPort } = req.socket;
63  const address = localAddress && net.isIPv6(localAddress) ? `[${localAddress}]` : localAddress;
64  return `${scheme}:${address}:${localPort}`;
65}
66