/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow strict-local * @format * @oncall react_native */ import type {InspectorProxyQueries} from '../inspector-proxy/InspectorProxy'; import type {BrowserLauncher} from '../types/BrowserLauncher'; import type {EventReporter} from '../types/EventReporter'; import type {Experiments} from '../types/Experiments'; import type {Logger} from '../types/Logger'; import type {NextHandleFunction} from 'connect'; import type {IncomingMessage, ServerResponse} from 'http'; import getDevToolsFrontendUrl from '../utils/getDevToolsFrontendUrl'; import url from 'url'; const LEGACY_SYNTHETIC_PAGE_TITLE = 'React Native Experimental (Improved Chrome Reloads)'; type Options = $ReadOnly<{ serverBaseUrl: string, logger?: Logger, browserLauncher: BrowserLauncher, eventReporter?: EventReporter, experiments: Experiments, inspectorProxy: InspectorProxyQueries, }>; /** * Open the debugger frontend for a given CDP target. * * Currently supports React Native DevTools (rn_fusebox.html) and legacy Hermes * (rn_inspector.html) targets. * * @see https://chromedevtools.github.io/devtools-protocol/ */ export default function openDebuggerMiddleware({ serverBaseUrl, logger, browserLauncher, eventReporter, experiments, inspectorProxy, }: Options): NextHandleFunction { return async ( req: IncomingMessage, res: ServerResponse, next: (err?: Error) => void, ) => { if ( req.method === 'POST' || (experiments.enableOpenDebuggerRedirect && req.method === 'GET') ) { const {query} = url.parse(req.url, true); const { appId, device, launchId, target: targetId, }: { /** @deprecated Will only match legacy Hermes targets */ appId?: string, device?: string, launchId?: string, target?: string, ... } = query; const targets = inspectorProxy .getPageDescriptions(new URL(serverBaseUrl)) .filter( // Only use targets with better reloading support app => app.title === LEGACY_SYNTHETIC_PAGE_TITLE || app.reactNative.capabilities?.nativePageReloads === true, ); let target; const launchType: 'launch' | 'redirect' = req.method === 'POST' ? 'launch' : 'redirect'; if ( typeof targetId === 'string' || typeof appId === 'string' || typeof device === 'string' ) { logger?.info( (launchType === 'launch' ? 'Launching' : 'Redirecting to') + ' DevTools...', ); target = targets.find( _target => (targetId == null || _target.id === targetId) && (appId == null || (_target.appId === appId && _target.title === LEGACY_SYNTHETIC_PAGE_TITLE)) && (device == null || _target.reactNative.logicalDeviceId === device), ); } else if (targets.length > 0) { logger?.info( (launchType === 'launch' ? 'Launching' : 'Redirecting to') + ` DevTools${targets.length === 1 ? '' : ' for most recently connected target'}...`, ); target = targets[targets.length - 1]; } if (!target) { res.writeHead(404); res.end('Unable to find debugger target'); logger?.warn( 'No compatible apps connected. React Native DevTools can only be used with the Hermes engine.', ); eventReporter?.logEvent({ type: 'launch_debugger_frontend', launchType, status: 'coded_error', errorCode: 'NO_APPS_FOUND', }); return; } const useFuseboxEntryPoint = target.reactNative.capabilities?.prefersFuseboxFrontend; try { switch (launchType) { case 'launch': await browserLauncher.launchDebuggerAppWindow( getDevToolsFrontendUrl( experiments, target.webSocketDebuggerUrl, serverBaseUrl, {launchId, useFuseboxEntryPoint}, ), ); res.writeHead(200); res.end(); break; case 'redirect': res.writeHead(302, { Location: getDevToolsFrontendUrl( experiments, target.webSocketDebuggerUrl, serverBaseUrl, {relative: true, launchId, useFuseboxEntryPoint}, ), }); res.end(); break; default: (launchType: empty); } eventReporter?.logEvent({ type: 'launch_debugger_frontend', launchType, status: 'success', appId: appId ?? null, deviceId: device ?? null, resolvedTargetDescription: target.description, resolvedTargetAppId: target.appId, prefersFuseboxFrontend: useFuseboxEntryPoint ?? false, }); return; } catch (e) { logger?.error( 'Error launching DevTools: ' + e.message ?? 'Unknown error', ); res.writeHead(500); res.end(); eventReporter?.logEvent({ type: 'launch_debugger_frontend', launchType, status: 'error', error: e, prefersFuseboxFrontend: useFuseboxEntryPoint ?? false, }); return; } } next(); }; }