1import Protocol from 'devtools-protocol'; 2import { Device as MetroDevice } from 'metro-inspector-proxy'; 3 4import { CdpMessage, DeviceRequest, InspectorHandler } from './types'; 5import { ExpoDebuggerInfo } from '../device'; 6 7/** Android's stock emulator and other emulators such as genymotion use a standard localhost alias. */ 8const EMULATOR_LOCALHOST_ADDRESSES: Readonly<string[]> = ['10.0.2.2', '10.0.3.2']; 9/** Prefix for script URLs that are alphanumeric IDs. */ 10const FILE_PREFIX = 'file://'; 11 12/** 13 * Some debug clients does not support fetching source maps by URL. 14 * By default, the `metro-inspector-proxy` inlines the source map as base64 string. 15 * Unfortunately, that causes a multi-second delay in VS Code (±5s). 16 * This handler disables inlining the source maps for VS Code only. 17 */ 18export class VscodeDebuggerScriptParsedHandler implements InspectorHandler { 19 constructor(private readonly device: MetroDevice) {} 20 21 onDeviceMessage(message: DeviceRequest<DebuggerScriptParsed>, debuggerInfo: ExpoDebuggerInfo) { 22 if (debuggerInfo.debuggerType !== 'vscode' || message.method !== 'Debugger.scriptParsed') { 23 return false; 24 } 25 26 // See: https://github.com/facebook/metro/blob/f43caa371a813b257cb0b42028079645a1e85e0e/packages/metro-inspector-proxy/src/Device.js#L401-L410 27 if (message.params.sourceMapURL) { 28 for (let i = 0; i < EMULATOR_LOCALHOST_ADDRESSES.length; ++i) { 29 const address = EMULATOR_LOCALHOST_ADDRESSES[i]; 30 if (message.params.sourceMapURL.indexOf(address) >= 0) { 31 message.params.sourceMapURL = message.params.sourceMapURL.replace(address, 'localhost'); 32 debuggerInfo.originalSourceURLAddress = address; 33 } 34 } 35 } 36 37 // See: https://github.com/facebook/metro/blob/f43caa371a813b257cb0b42028079645a1e85e0e/packages/metro-inspector-proxy/src/Device.js#L431-L453 38 if (message.params.url) { 39 for (let i = 0; i < EMULATOR_LOCALHOST_ADDRESSES.length; ++i) { 40 const address = EMULATOR_LOCALHOST_ADDRESSES[i]; 41 if (message.params.url.indexOf(address) >= 0) { 42 message.params.url = message.params.url.replace(address, 'localhost'); 43 debuggerInfo.originalSourceURLAddress = address; 44 } 45 } 46 47 // Chrome doesn't download source maps if URL param is not a valid 48 // URL. Some frameworks pass alphanumeric script ID instead of URL which causes 49 // Chrome to not download source maps. In this case we want to prepend script ID 50 // with 'file://' prefix. 51 if (message.params.url.match(/^[0-9a-z]+$/)) { 52 message.params.url = FILE_PREFIX + message.params.url; 53 debuggerInfo.prependedFilePrefix = true; 54 } 55 56 if (message.params.scriptId != null) { 57 this.device._scriptIdToSourcePathMapping.set(message.params.scriptId, message.params.url); 58 } 59 } 60 61 // Block `metro-inspector-proxy`'s default source map inlining 62 return true; 63 } 64} 65 66/** @see https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#event-scriptParsed */ 67export type DebuggerScriptParsed = CdpMessage< 68 'Debugger.scriptParsed', 69 Protocol.Debugger.ScriptParsedEvent, 70 never 71>; 72