1import fetch from 'node-fetch'; 2 3import { launchBrowserAsync, type LaunchBrowserInstance } from './LaunchBrowser'; 4 5export interface MetroInspectorProxyApp { 6 description: string; 7 devtoolsFrontendUrl: string; 8 faviconUrl: string; 9 id: string; 10 title: string; 11 type: 'node'; 12 vm: 'Hermes' | "don't use"; 13 webSocketDebuggerUrl: string; 14} 15 16let openingBrowserInstance: LaunchBrowserInstance | null = null; 17 18export async function openJsInspector(app: MetroInspectorProxyApp) { 19 // To update devtoolsFrontendRev, find the full commit hash in the url: 20 // https://chromium.googlesource.com/chromium/src.git/+log/refs/tags/{CHROME_VERSION}/chrome/VERSION 21 // 22 // 1. Replace {CHROME_VERSION} with the target chrome version 23 // 2. Click the first log item in the webpage 24 // 3. The full commit hash is the desired revision 25 const devtoolsFrontendRev = 'd9568d04d7dd79269c5a655d7ada69650c5a8336'; // Chrome 100.0.4896.75 26 27 const urlBase = `https://chrome-devtools-frontend.appspot.com/serve_rev/@${devtoolsFrontendRev}/devtools_app.html`; 28 const ws = app.webSocketDebuggerUrl.replace(/^ws:\/\//, ''); 29 const url = `${urlBase}?panel=console&ws=${encodeURIComponent(ws)}`; 30 await closeJsInspector(); 31 openingBrowserInstance = await launchBrowserAsync(url); 32} 33 34export async function closeJsInspector() { 35 await openingBrowserInstance?.close(); 36 openingBrowserInstance = null; 37} 38 39export async function queryInspectorAppAsync( 40 metroServerOrigin: string, 41 appId: string 42): Promise<MetroInspectorProxyApp | null> { 43 const apps = await queryAllInspectorAppsAsync(metroServerOrigin); 44 return apps.find((app) => app.description === appId) ?? null; 45} 46 47export async function queryAllInspectorAppsAsync( 48 metroServerOrigin: string 49): Promise<MetroInspectorProxyApp[]> { 50 const resp = await fetch(`${metroServerOrigin}/json/list`); 51 const apps: MetroInspectorProxyApp[] = transformApps(await resp.json()); 52 // Only use targets with better reloading support 53 return apps.filter((app) => app.title === 'React Native Experimental (Improved Chrome Reloads)'); 54} 55 56// The description of `React Native Experimental (Improved Chrome Reloads)` target is `don't use` from metro. 57// This function tries to transform the unmeaningful description to appId 58function transformApps(apps: MetroInspectorProxyApp[]): MetroInspectorProxyApp[] { 59 const deviceIdToAppId: Record<string, string> = {}; 60 61 for (const app of apps) { 62 if (app.description !== "don't use") { 63 const deviceId = app.id.split('-')[0]; 64 const appId = app.description; 65 deviceIdToAppId[deviceId] = appId; 66 } 67 } 68 69 return apps.map((app) => { 70 if (app.description === "don't use") { 71 const deviceId = app.id.split('-')[0]; 72 app.description = deviceIdToAppId[deviceId] ?? app.description; 73 } 74 return app; 75 }); 76} 77