1import { ExpoConfig } from '@expo/config';
2import { Middleware } from 'metro-config';
3
4import { env } from '../env';
5import { DebugTool, getMetroDebugProperties } from './getMetroDebugProperties';
6import { logEventAsync } from './rudderstackClient';
7
8/**
9 * Create a Metro middleware that reports when a debugger request was found.
10 * This will only be reported once, if the app uses Hermes and telemetry is not enabled.
11 */
12export function createDebuggerTelemetryMiddleware(
13  projectRoot: string,
14  exp: ExpoConfig
15): Middleware {
16  let hasReported = false;
17
18  // This only works for Hermes apps, disable when telemetry is turned off
19  if (env.EXPO_NO_TELEMETRY || exp.jsEngine !== 'hermes') {
20    return (req, res, next) => next(undefined);
21  }
22
23  return (req, res, next) => {
24    // Only report once
25    if (hasReported) {
26      return next(undefined);
27    }
28
29    const debugTool = findDebugTool(req);
30    if (debugTool) {
31      hasReported = true;
32      logEventAsync('metro debug', getMetroDebugProperties(projectRoot, exp, debugTool));
33    }
34
35    return next(undefined);
36  };
37}
38
39/** Exposed for testing */
40export function findDebugTool(
41  req: Pick<Parameters<Middleware>[0], 'headers' | 'url'>
42): DebugTool | null {
43  if (req.headers['origin']?.includes('chrome-devtools')) {
44    return { name: 'chrome' };
45  }
46
47  if (req.url?.startsWith('/json')) {
48    const flipperUserAgent = req.headers['user-agent']?.match(/(Flipper)\/([^\s]+)/);
49    if (flipperUserAgent) {
50      return {
51        name: flipperUserAgent[1].toLowerCase(),
52        version: flipperUserAgent[2],
53      };
54    }
55  }
56
57  return null;
58}
59