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