1d42dd5d4SCedric van Puttenimport { ExpoConfig } from '@expo/config';
2d42dd5d4SCedric van Puttenimport { Middleware } from 'metro-config';
3d42dd5d4SCedric van Putten
4d42dd5d4SCedric van Puttenimport { DebugTool, getMetroDebugProperties } from './getMetroDebugProperties';
5d42dd5d4SCedric van Puttenimport { logEventAsync } from './rudderstackClient';
6*8a424bebSJames Ideimport { env } from '../env';
7d42dd5d4SCedric van Putten
80381251eSTomasz Sapetatype Request = Parameters<Middleware>[0];
90381251eSTomasz Sapetatype Response = Parameters<Middleware>[1];
100381251eSTomasz Sapetatype Next = Parameters<Middleware>[2];
110381251eSTomasz Sapeta
12d42dd5d4SCedric van Putten/**
13d42dd5d4SCedric van Putten * Create a Metro middleware that reports when a debugger request was found.
14d42dd5d4SCedric van Putten * This will only be reported once, if the app uses Hermes and telemetry is not enabled.
15d42dd5d4SCedric van Putten */
16d42dd5d4SCedric van Puttenexport function createDebuggerTelemetryMiddleware(
17d42dd5d4SCedric van Putten  projectRoot: string,
18d42dd5d4SCedric van Putten  exp: ExpoConfig
19d42dd5d4SCedric van Putten): Middleware {
20d42dd5d4SCedric van Putten  let hasReported = false;
21d42dd5d4SCedric van Putten
22d42dd5d4SCedric van Putten  // This only works for Hermes apps, disable when telemetry is turned off
23d42dd5d4SCedric van Putten  if (env.EXPO_NO_TELEMETRY || exp.jsEngine !== 'hermes') {
240381251eSTomasz Sapeta    return (req: Request, res: Response, next: Next) => {
250381251eSTomasz Sapeta      if (typeof next === 'function') {
260381251eSTomasz Sapeta        next(undefined);
270381251eSTomasz Sapeta      }
280381251eSTomasz Sapeta    };
29d42dd5d4SCedric van Putten  }
30d42dd5d4SCedric van Putten
310381251eSTomasz Sapeta  return (req: Request, res: Response, next: Next) => {
32d42dd5d4SCedric van Putten    // Only report once
330381251eSTomasz Sapeta    if (hasReported && typeof next === 'function') {
34d42dd5d4SCedric van Putten      return next(undefined);
35d42dd5d4SCedric van Putten    }
36d42dd5d4SCedric van Putten
37d42dd5d4SCedric van Putten    const debugTool = findDebugTool(req);
38d42dd5d4SCedric van Putten    if (debugTool) {
39d42dd5d4SCedric van Putten      hasReported = true;
40d42dd5d4SCedric van Putten      logEventAsync('metro debug', getMetroDebugProperties(projectRoot, exp, debugTool));
41d42dd5d4SCedric van Putten    }
42d42dd5d4SCedric van Putten
430381251eSTomasz Sapeta    if (typeof next === 'function') {
44d42dd5d4SCedric van Putten      return next(undefined);
450381251eSTomasz Sapeta    }
46d42dd5d4SCedric van Putten  };
47d42dd5d4SCedric van Putten}
48d42dd5d4SCedric van Putten
49d42dd5d4SCedric van Putten/** Exposed for testing */
50d42dd5d4SCedric van Puttenexport function findDebugTool(
51d42dd5d4SCedric van Putten  req: Pick<Parameters<Middleware>[0], 'headers' | 'url'>
52d42dd5d4SCedric van Putten): DebugTool | null {
53d42dd5d4SCedric van Putten  if (req.headers['origin']?.includes('chrome-devtools')) {
54d42dd5d4SCedric van Putten    return { name: 'chrome' };
55d42dd5d4SCedric van Putten  }
56d42dd5d4SCedric van Putten
57d42dd5d4SCedric van Putten  if (req.url?.startsWith('/json')) {
58d42dd5d4SCedric van Putten    const flipperUserAgent = req.headers['user-agent']?.match(/(Flipper)\/([^\s]+)/);
59d42dd5d4SCedric van Putten    if (flipperUserAgent) {
60d42dd5d4SCedric van Putten      return {
61d42dd5d4SCedric van Putten        name: flipperUserAgent[1].toLowerCase(),
62d42dd5d4SCedric van Putten        version: flipperUserAgent[2],
63d42dd5d4SCedric van Putten      };
64d42dd5d4SCedric van Putten    }
65d42dd5d4SCedric van Putten  }
66d42dd5d4SCedric van Putten
67d42dd5d4SCedric van Putten  return null;
68d42dd5d4SCedric van Putten}
69