1import type { DebuggerInfo, Device as MetroDevice } from 'metro-inspector-proxy'; 2import fetch from 'node-fetch'; 3import type WS from 'ws'; 4 5import { DebuggerScriptSourceHandler } from './handlers/DebuggerScriptSource'; 6import { NetworkResponseHandler } from './handlers/NetworkResponse'; 7import { VscodeCompatHandler } from './handlers/VscodeCompat'; 8import { DeviceRequest, InspectorHandler, DebuggerRequest } from './handlers/types'; 9 10export function createInspectorDeviceClass(MetroDeviceClass: typeof MetroDevice) { 11 return class ExpoInspectorDevice extends MetroDeviceClass implements InspectorHandler { 12 /** All handlers that should be used to intercept or reply to CDP events */ 13 public handlers: InspectorHandler[] = [ 14 new NetworkResponseHandler(), 15 new DebuggerScriptSourceHandler(this), 16 new VscodeCompatHandler(), 17 ]; 18 19 onDeviceMessage(message: any, info: DebuggerInfo): boolean { 20 return this.handlers.some((handler) => handler.onDeviceMessage?.(message, info) ?? false); 21 } 22 23 onDebuggerMessage(message: any, info: DebuggerInfo): boolean { 24 return this.handlers.some((handler) => handler.onDebuggerMessage?.(message, info) ?? false); 25 } 26 27 /** Hook into the message life cycle to answer more complex CDP messages */ 28 async _processMessageFromDevice(message: DeviceRequest<any>, info: DebuggerInfo) { 29 if (!this.onDeviceMessage(message, info)) { 30 await super._processMessageFromDevice(message, info); 31 } 32 } 33 34 /** Hook into the message life cycle to answer more complex CDP messages */ 35 _interceptMessageFromDebugger( 36 request: DebuggerRequest, 37 info: DebuggerInfo, 38 socket: WS 39 ): boolean { 40 // Note, `socket` is the exact same as `info.socket` 41 if (this.onDebuggerMessage(request, info)) { 42 return true; 43 } 44 45 return super._interceptMessageFromDebugger(request, info, socket); 46 } 47 48 /** 49 * Overwrite the default text fetcher, to load sourcemaps from sources other than `localhost`. 50 * @todo Cedric: remove the custom `DebuggerScriptSource` handler when switching over to `metro@>=0.75.1` 51 * @see https://github.com/facebook/metro/blob/77f445f1bcd2264ad06174dbf8d542bc75834d29/packages/metro-inspector-proxy/src/Device.js#L573-L588 52 * @since [email protected] 53 */ 54 async _fetchText(url: URL): Promise<string> { 55 const LENGTH_LIMIT_BYTES = 350_000_000; // 350mb 56 57 const response = await fetch(url); 58 if (!response.ok) { 59 throw new Error(`Received status ${response.status} while fetching: ${url}`); 60 } 61 62 const contentLength = response.headers.get('Content-Length'); 63 if (contentLength && Number(contentLength) > LENGTH_LIMIT_BYTES) { 64 throw new Error('Expected file size is too large (more than 350mb)'); 65 } 66 67 const text = await response.text(); 68 if (Buffer.byteLength(text, 'utf8') > LENGTH_LIMIT_BYTES) { 69 throw new Error('File size is too large (more than 350mb)'); 70 } 71 72 return text; 73 } 74 }; 75} 76