15234fe38SCedric van Puttenimport type { DebuggerInfo, Device as MetroDevice } from 'metro-inspector-proxy'; 26d2ad561SCedric van Puttenimport fetch from 'node-fetch'; 35234fe38SCedric van Puttenimport type WS from 'ws'; 45234fe38SCedric van Putten 55234fe38SCedric van Puttenimport { NetworkResponseHandler } from './handlers/NetworkResponse'; 603d43e7dSCedric van Puttenimport { PageReloadHandler } from './handlers/PageReload'; 7033ea1fcSCedric van Puttenimport { VscodeDebuggerGetPossibleBreakpointsHandler } from './handlers/VscodeDebuggerGetPossibleBreakpoints'; 8033ea1fcSCedric van Puttenimport { VscodeDebuggerScriptParsedHandler } from './handlers/VscodeDebuggerScriptParsed'; 9033ea1fcSCedric van Puttenimport { VscodeDebuggerSetBreakpointByUrlHandler } from './handlers/VscodeDebuggerSetBreakpointByUrl'; 10033ea1fcSCedric van Puttenimport { VscodeRuntimeGetPropertiesHandler } from './handlers/VscodeRuntimeGetProperties'; 115234fe38SCedric van Puttenimport { DeviceRequest, InspectorHandler, DebuggerRequest } from './handlers/types'; 12*8a424bebSJames Ideimport { MetroBundlerDevServer } from '../MetroBundlerDevServer'; 135234fe38SCedric van Putten 14033ea1fcSCedric van Putten/** Export the supported debugger types this inspector proxy can handle */ 15033ea1fcSCedric van Puttenexport type DebuggerType = 'vscode' | 'generic'; 16033ea1fcSCedric van Putten 17033ea1fcSCedric van Putten/** The debugger information being tracked by this device class */ 18033ea1fcSCedric van Puttenexport type ExpoDebuggerInfo = DebuggerInfo & { debuggerType?: DebuggerType }; 19033ea1fcSCedric van Putten 2003d43e7dSCedric van Puttenexport function createInspectorDeviceClass( 2103d43e7dSCedric van Putten metroBundler: MetroBundlerDevServer, 2203d43e7dSCedric van Putten MetroDeviceClass: typeof MetroDevice 2303d43e7dSCedric van Putten) { 245234fe38SCedric van Putten return class ExpoInspectorDevice extends MetroDeviceClass implements InspectorHandler { 25033ea1fcSCedric van Putten /** Stores information about currently connected debugger (if any). */ 26033ea1fcSCedric van Putten _debuggerConnection: ExpoDebuggerInfo | null = null; 27033ea1fcSCedric van Putten 285234fe38SCedric van Putten /** All handlers that should be used to intercept or reply to CDP events */ 296d2ad561SCedric van Putten public handlers: InspectorHandler[] = [ 30033ea1fcSCedric van Putten // Generic handlers 316d2ad561SCedric van Putten new NetworkResponseHandler(), 3203d43e7dSCedric van Putten new PageReloadHandler(metroBundler), 33033ea1fcSCedric van Putten // Vscode-specific handlers 34033ea1fcSCedric van Putten new VscodeDebuggerGetPossibleBreakpointsHandler(), 35033ea1fcSCedric van Putten new VscodeDebuggerScriptParsedHandler(this), 36033ea1fcSCedric van Putten new VscodeDebuggerSetBreakpointByUrlHandler(), 37033ea1fcSCedric van Putten new VscodeRuntimeGetPropertiesHandler(), 386d2ad561SCedric van Putten ]; 395234fe38SCedric van Putten 405234fe38SCedric van Putten onDeviceMessage(message: any, info: DebuggerInfo): boolean { 415234fe38SCedric van Putten return this.handlers.some((handler) => handler.onDeviceMessage?.(message, info) ?? false); 425234fe38SCedric van Putten } 435234fe38SCedric van Putten 445234fe38SCedric van Putten onDebuggerMessage(message: any, info: DebuggerInfo): boolean { 455234fe38SCedric van Putten return this.handlers.some((handler) => handler.onDebuggerMessage?.(message, info) ?? false); 465234fe38SCedric van Putten } 475234fe38SCedric van Putten 48f5fa30f6SCedric van Putten /** 49f5fa30f6SCedric van Putten * Handle a new device connection with the same device identifier. 50f5fa30f6SCedric van Putten * When the app and device name matches, we can reuse the debugger connection. 51f5fa30f6SCedric van Putten * Else, we have to shut the debugger connection down. 52f5fa30f6SCedric van Putten */ 53f5fa30f6SCedric van Putten handleDuplicateDeviceConnection(newDevice: InstanceType<typeof MetroDeviceClass>) { 54f5fa30f6SCedric van Putten if (this._app !== newDevice._app || this._name !== newDevice._name) { 55f5fa30f6SCedric van Putten this._deviceSocket.close(); 56f5fa30f6SCedric van Putten this._debuggerConnection?.socket.close(); 57f5fa30f6SCedric van Putten return; 58f5fa30f6SCedric van Putten } 59f5fa30f6SCedric van Putten 60f5fa30f6SCedric van Putten const oldDebugger = this._debuggerConnection; 61f5fa30f6SCedric van Putten this._debuggerConnection = null; 62f5fa30f6SCedric van Putten 63f5fa30f6SCedric van Putten if (oldDebugger) { 64f5fa30f6SCedric van Putten oldDebugger.socket.removeAllListeners(); 65f5fa30f6SCedric van Putten this._deviceSocket.close(); 66f5fa30f6SCedric van Putten newDevice.handleDebuggerConnection(oldDebugger.socket, oldDebugger.pageId); 67f5fa30f6SCedric van Putten } 68f5fa30f6SCedric van Putten } 69f5fa30f6SCedric van Putten 70033ea1fcSCedric van Putten /** 71033ea1fcSCedric van Putten * Handle a new debugger connection to this device. 72033ea1fcSCedric van Putten * This adds the `debuggerType` property to the `DebuggerInfo` object. 73033ea1fcSCedric van Putten * With that information, we can enable or disable debugger-specific handlers. 74033ea1fcSCedric van Putten */ 75033ea1fcSCedric van Putten handleDebuggerConnectionWithType(socket: WS, pageId: string, debuggerType: DebuggerType): void { 76033ea1fcSCedric van Putten this.handleDebuggerConnection(socket, pageId); 77033ea1fcSCedric van Putten 78033ea1fcSCedric van Putten if (this._debuggerConnection) { 79033ea1fcSCedric van Putten this._debuggerConnection.debuggerType = debuggerType; 80033ea1fcSCedric van Putten } 81033ea1fcSCedric van Putten } 82033ea1fcSCedric van Putten 835234fe38SCedric van Putten /** Hook into the message life cycle to answer more complex CDP messages */ 845234fe38SCedric van Putten async _processMessageFromDevice(message: DeviceRequest<any>, info: DebuggerInfo) { 855234fe38SCedric van Putten if (!this.onDeviceMessage(message, info)) { 865234fe38SCedric van Putten await super._processMessageFromDevice(message, info); 875234fe38SCedric van Putten } 885234fe38SCedric van Putten } 895234fe38SCedric van Putten 905234fe38SCedric van Putten /** Hook into the message life cycle to answer more complex CDP messages */ 915234fe38SCedric van Putten _interceptMessageFromDebugger( 925234fe38SCedric van Putten request: DebuggerRequest, 935234fe38SCedric van Putten info: DebuggerInfo, 945234fe38SCedric van Putten socket: WS 955234fe38SCedric van Putten ): boolean { 965234fe38SCedric van Putten // Note, `socket` is the exact same as `info.socket` 975234fe38SCedric van Putten if (this.onDebuggerMessage(request, info)) { 985234fe38SCedric van Putten return true; 995234fe38SCedric van Putten } 1005234fe38SCedric van Putten 1015234fe38SCedric van Putten return super._interceptMessageFromDebugger(request, info, socket); 1025234fe38SCedric van Putten } 1036d2ad561SCedric van Putten 1046d2ad561SCedric van Putten /** 1056d2ad561SCedric van Putten * Overwrite the default text fetcher, to load sourcemaps from sources other than `localhost`. 1066d2ad561SCedric van Putten * @todo Cedric: remove the custom `DebuggerScriptSource` handler when switching over to `metro@>=0.75.1` 1076d2ad561SCedric van Putten * @see https://github.com/facebook/metro/blob/77f445f1bcd2264ad06174dbf8d542bc75834d29/packages/metro-inspector-proxy/src/Device.js#L573-L588 1086d2ad561SCedric van Putten * @since [email protected] 1096d2ad561SCedric van Putten */ 1106d2ad561SCedric van Putten async _fetchText(url: URL): Promise<string> { 1116d2ad561SCedric van Putten const LENGTH_LIMIT_BYTES = 350_000_000; // 350mb 1126d2ad561SCedric van Putten 1136d2ad561SCedric van Putten const response = await fetch(url); 1146d2ad561SCedric van Putten if (!response.ok) { 1156d2ad561SCedric van Putten throw new Error(`Received status ${response.status} while fetching: ${url}`); 1166d2ad561SCedric van Putten } 1176d2ad561SCedric van Putten 1186d2ad561SCedric van Putten const contentLength = response.headers.get('Content-Length'); 1196d2ad561SCedric van Putten if (contentLength && Number(contentLength) > LENGTH_LIMIT_BYTES) { 1206d2ad561SCedric van Putten throw new Error('Expected file size is too large (more than 350mb)'); 1216d2ad561SCedric van Putten } 1226d2ad561SCedric van Putten 1236d2ad561SCedric van Putten const text = await response.text(); 1246d2ad561SCedric van Putten if (Buffer.byteLength(text, 'utf8') > LENGTH_LIMIT_BYTES) { 1256d2ad561SCedric van Putten throw new Error('File size is too large (more than 350mb)'); 1266d2ad561SCedric van Putten } 1276d2ad561SCedric van Putten 1286d2ad561SCedric van Putten return text; 1296d2ad561SCedric van Putten } 1305234fe38SCedric van Putten }; 1315234fe38SCedric van Putten} 132