From a991ff383727c4c9f05c1b621b62609680181384 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Fri, 7 Jul 2023 09:22:09 -0700 Subject: [PATCH] Create dev-middleware package (#38194) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/38194 ## Context RFC: Decoupling Flipper from React Native core: https://github.com/react-native-community/discussions-and-proposals/pull/641 ## Changes Inits the `react-native/dev-middleware` package. Contains an initial implementation of `/open-debugger`, migrated from https://github.com/react-native-community/cli/commit/2535dbe2346a390b2c5034acf0b348347fce0b73. ## Attribution This implementation is greatly inspired by `expo/dev-server`: https://github.com/expo/expo/blob/1120c716f35cb28d88800e8f5d963d2b2ac94705/packages/%40expo/dev-server/src/JsInspector.ts#L18 Changelog: [Internal] Reviewed By: motiz88 Differential Revision: D46283818 fbshipit-source-id: 7b38ad2f6d7346366a7c599d16e289e04b7bd88d --- flow-typed/npm/chrome-launcher_v0.15.x.js | 49 +++++ flow-typed/npm/connect_v3.x.x.js | 50 +++++ flow-typed/npm/node-fetch_v2.x.x.js | 189 ++++++++++++++++++ flow-typed/npm/temp-dir_2.x.x.js | 14 ++ packages/dev-middleware/.babelrc | 13 ++ packages/dev-middleware/.gitignore | 5 + packages/dev-middleware/README.md | 11 + packages/dev-middleware/index.js.flow | 12 ++ packages/dev-middleware/package.json | 43 ++++ .../dev-middleware/src/createDevMiddleware.js | 31 +++ packages/dev-middleware/src/index.js | 12 ++ .../src/middleware/openDebuggerMiddleware.js | 87 ++++++++ packages/dev-middleware/src/types/Logger.js | 17 ++ .../src/utils/getDevServerUrl.js | 32 +++ .../src/utils/launchChromeDevTools.js | 38 ++++ .../src/utils/launchDebuggerAppWindow.js | 56 ++++++ .../src/utils/queryInspectorTargets.js | 40 ++++ yarn.lock | 42 +++- 18 files changed, 740 insertions(+), 1 deletion(-) create mode 100644 flow-typed/npm/chrome-launcher_v0.15.x.js create mode 100644 flow-typed/npm/connect_v3.x.x.js create mode 100644 flow-typed/npm/node-fetch_v2.x.x.js create mode 100644 flow-typed/npm/temp-dir_2.x.x.js create mode 100644 packages/dev-middleware/.babelrc create mode 100644 packages/dev-middleware/.gitignore create mode 100644 packages/dev-middleware/README.md create mode 100644 packages/dev-middleware/index.js.flow create mode 100644 packages/dev-middleware/package.json create mode 100644 packages/dev-middleware/src/createDevMiddleware.js create mode 100644 packages/dev-middleware/src/index.js create mode 100644 packages/dev-middleware/src/middleware/openDebuggerMiddleware.js create mode 100644 packages/dev-middleware/src/types/Logger.js create mode 100644 packages/dev-middleware/src/utils/getDevServerUrl.js create mode 100644 packages/dev-middleware/src/utils/launchChromeDevTools.js create mode 100644 packages/dev-middleware/src/utils/launchDebuggerAppWindow.js create mode 100644 packages/dev-middleware/src/utils/queryInspectorTargets.js diff --git a/flow-typed/npm/chrome-launcher_v0.15.x.js b/flow-typed/npm/chrome-launcher_v0.15.x.js new file mode 100644 index 0000000000..a0826994e9 --- /dev/null +++ b/flow-typed/npm/chrome-launcher_v0.15.x.js @@ -0,0 +1,49 @@ +/** + * 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 + * @format + * @oncall react_native + */ + +declare module 'chrome-launcher' { + import typeof fs from 'fs'; + import typeof childProcess from 'child_process'; + import type {ChildProcess} from 'child_process'; + + declare export type Options = { + startingUrl?: string, + chromeFlags?: Array, + prefs?: mixed, + port?: number, + handleSIGINT?: boolean, + chromePath?: string, + userDataDir?: string | boolean, + logLevel?: 'verbose' | 'info' | 'error' | 'warn' | 'silent', + ignoreDefaultFlags?: boolean, + connectionPollInterval?: number, + maxConnectionRetries?: number, + envVars?: {[key: string]: ?string}, + }; + + declare export type LaunchedChrome = { + pid: number, + port: number, + process: ChildProcess, + kill: () => void, + }; + + declare export type ModuleOverrides = { + fs?: fs, + spawn?: childProcess['spawn'], + }; + + declare class Launcher { + launch(options: Options): Promise; + } + + declare module.exports: Launcher; +} diff --git a/flow-typed/npm/connect_v3.x.x.js b/flow-typed/npm/connect_v3.x.x.js new file mode 100644 index 0000000000..08cda352be --- /dev/null +++ b/flow-typed/npm/connect_v3.x.x.js @@ -0,0 +1,50 @@ +/** + * 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 + * @format + * @oncall react_native + */ + +declare module 'connect' { + import type http from 'http'; + + declare export type ServerHandle = HandleFunction | http.Server; + + declare type NextFunction = (err?: mixed) => void; + + declare export type NextHandleFunction = ( + req: IncomingMessage, + res: http.ServerResponse, + next: NextFunction, + ) => void | Promise; + declare export type HandleFunction = NextHandleFunction; + + declare export interface IncomingMessage extends http.IncomingMessage { + originalUrl?: http.IncomingMessage['url']; + } + + declare export interface Server extends events$EventEmitter { + (req: IncomingMessage, res: http.ServerResponse): void; + + use(fn: HandleFunction): Server; + use(route: string, fn: HandleFunction): Server; + + listen( + port: number, + hostname?: string, + backlog?: number, + callback?: Function, + ): http.Server; + listen(port: number, hostname?: string, callback?: Function): http.Server; + listen(path: string, callback?: Function): http.Server; + listen(handle: any, listeningListener?: Function): http.Server; + } + + declare type createServer = () => Server; + + declare module.exports: createServer; +} diff --git a/flow-typed/npm/node-fetch_v2.x.x.js b/flow-typed/npm/node-fetch_v2.x.x.js new file mode 100644 index 0000000000..9b715a7596 --- /dev/null +++ b/flow-typed/npm/node-fetch_v2.x.x.js @@ -0,0 +1,189 @@ +/** + * 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 + * @format + * @oncall react_native + */ + +// Modified from flow-typed repo: +// https://github.com/flow-typed/flow-typed/blob/master/definitions/npm/node-fetch_v2.x.x/flow_v0.104.x-/node-fetch_v2.x.x.js + +declare module 'node-fetch' { + import type http from 'http'; + import type https from 'https'; + import type {Readable} from 'stream'; + + declare type AbortSignal = { + +aborted: boolean, + +onabort: (event?: {...}) => void, + + +addEventListener: (name: string, cb: () => mixed) => void, + +removeEventListener: (name: string, cb: () => mixed) => void, + +dispatchEvent: (event: {...}) => void, + ... + }; + + declare class Request mixins Body { + constructor( + input: string | {href: string, ...} | Request, + init?: RequestInit, + ): this; + context: RequestContext; + headers: Headers; + method: string; + redirect: RequestRedirect; + referrer: string; + url: string; + + // node-fetch extensions + agent: http.Agent | https.Agent; + compress: boolean; + counter: number; + follow: number; + hostname: string; + port: number; + protocol: string; + size: number; + timeout: number; + } + + declare type HeaderObject = {[index: string]: string | number, ...}; + + declare type RequestInit = {| + body?: BodyInit, + headers?: HeaderObject | null, + method?: string, + redirect?: RequestRedirect, + signal?: AbortSignal | null, + + // node-fetch extensions + agent?: (URL => http.Agent | https.Agent) | http.Agent | https.Agent | null, + compress?: boolean, + follow?: number, + size?: number, + timeout?: number, + |}; + + declare interface FetchError extends Error { + // cannot set name due to incompatible extend error + // name: 'FetchError'; + type: string; + code: ?number; + errno: ?number; + } + + declare interface AbortError extends Error { + // cannot set name due to incompatible extend error + // name: 'AbortError'; + type: 'aborted'; + } + + declare type RequestContext = + | 'audio' + | 'beacon' + | 'cspreport' + | 'download' + | 'embed' + | 'eventsource' + | 'favicon' + | 'fetch' + | 'font' + | 'form' + | 'frame' + | 'hyperlink' + | 'iframe' + | 'image' + | 'imageset' + | 'import' + | 'internal' + | 'location' + | 'manifest' + | 'object' + | 'ping' + | 'plugin' + | 'prefetch' + | 'script' + | 'serviceworker' + | 'sharedworker' + | 'subresource' + | 'style' + | 'track' + | 'video' + | 'worker' + | 'xmlhttprequest' + | 'xslt'; + declare type RequestRedirect = 'error' | 'follow' | 'manual'; + + declare class Headers { + append(name: string, value: string): void; + delete(name: string): void; + forEach(callback: (value: string, name: string) => void): void; + get(name: string): string; + getAll(name: string): Array; + has(name: string): boolean; + raw(): {[k: string]: string[], ...}; + set(name: string, value: string): void; + entries(): Iterator<[string, string]>; + keys(): Iterator; + values(): Iterator; + @@iterator(): Iterator<[string, string]>; + } + + declare class Body { + buffer(): Promise; + json(): Promise; + json(): Promise; + text(): Promise; + body: stream$Readable; + bodyUsed: boolean; + } + + declare class Response mixins Body { + constructor(body?: BodyInit, init?: ResponseInit): this; + clone(): Response; + error(): Response; + redirect(url: string, status: number): Response; + headers: Headers; + ok: boolean; + status: number; + statusText: string; + size: number; + timeout: number; + type: ResponseType; + url: string; + } + + declare type ResponseType = + | 'basic' + | 'cors' + | 'default' + | 'error' + | 'opaque' + | 'opaqueredirect'; + + declare interface ResponseInit { + headers?: HeaderInit; + status: number; + statusText?: string; + } + + declare type HeaderInit = Headers | Array; + declare type BodyInit = + | string + | null + | Buffer + | Blob + | Readable + | URLSearchParams; + + declare function fetch( + url: string | URL | Request, + init?: RequestInit, + ): Promise; + + declare module.exports: typeof fetch; +} diff --git a/flow-typed/npm/temp-dir_2.x.x.js b/flow-typed/npm/temp-dir_2.x.x.js new file mode 100644 index 0000000000..23b41d09bb --- /dev/null +++ b/flow-typed/npm/temp-dir_2.x.x.js @@ -0,0 +1,14 @@ +/** + * 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 + * @format + * @oncall react_native + */ + +declare module 'temp-dir' { + declare module.exports: string; +} diff --git a/packages/dev-middleware/.babelrc b/packages/dev-middleware/.babelrc new file mode 100644 index 0000000000..e6ab061db7 --- /dev/null +++ b/packages/dev-middleware/.babelrc @@ -0,0 +1,13 @@ +{ + "presets": [ + "@babel/preset-flow", + [ + "@babel/preset-env", + { + "targets": { + "node": "16" + } + } + ] + ] +} diff --git a/packages/dev-middleware/.gitignore b/packages/dev-middleware/.gitignore new file mode 100644 index 0000000000..40d93a0332 --- /dev/null +++ b/packages/dev-middleware/.gitignore @@ -0,0 +1,5 @@ +# Dependencies +/node_modules + +# Build output +/dist diff --git a/packages/dev-middleware/README.md b/packages/dev-middleware/README.md new file mode 100644 index 0000000000..0f4ee2b432 --- /dev/null +++ b/packages/dev-middleware/README.md @@ -0,0 +1,11 @@ +# @react-native/dev-middleware + +![https://img.shields.io/npm/v/@react-native/dev-middleware?color=brightgreen&label=npm%20package](https://www.npmjs.com/package/@react-native/dev-middleware) + +Dev server middleware supporting core React Native development features. This package is preconfigured in all React Native projects. + +## Endpoints + +### `/open-debugger` + +Open the JavaScript debugger for a given CDP target (direct Hermes debugging). diff --git a/packages/dev-middleware/index.js.flow b/packages/dev-middleware/index.js.flow new file mode 100644 index 0000000000..5bccccabb8 --- /dev/null +++ b/packages/dev-middleware/index.js.flow @@ -0,0 +1,12 @@ +/** + * 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 + * @format + * @oncall react_native + */ + +export * from './src'; diff --git a/packages/dev-middleware/package.json b/packages/dev-middleware/package.json new file mode 100644 index 0000000000..5fc0b5b79b --- /dev/null +++ b/packages/dev-middleware/package.json @@ -0,0 +1,43 @@ +{ + "name": "@react-native/dev-middleware", + "version": "0.73.0", + "description": "Dev server middleware for React Native", + "keywords": [ + "react-native", + "tools" + ], + "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/dev-middleware#readme", + "bugs": "https://github.com/facebook/react-native/issues", + "repository": { + "type": "git", + "url": "https://github.com/facebook/react-native.git", + "directory": "packages/dev-middleware" + }, + "license": "MIT", + "exports": "./dist/index.js", + "files": [ + "dist" + ], + "scripts": { + "build": "yarn clean && babel src --out-dir dist", + "dev": "babel src --out-dir dist --source-maps --watch", + "clean": "rimraf dist", + "prepare": "yarn build" + }, + "dependencies": { + "chrome-launcher": "^0.15.2", + "connect": "^3.6.5", + "node-fetch": "^2.2.0", + "temp-dir": "^2.0.0" + }, + "devDependencies": { + "@babel/cli": "^7.20.0", + "@babel/core": "^7.20.0", + "@babel/preset-env": "^7.20.0", + "@babel/preset-flow": "^7.20.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=18" + } +} diff --git a/packages/dev-middleware/src/createDevMiddleware.js b/packages/dev-middleware/src/createDevMiddleware.js new file mode 100644 index 0000000000..982f87bc54 --- /dev/null +++ b/packages/dev-middleware/src/createDevMiddleware.js @@ -0,0 +1,31 @@ +/** + * 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 + * @format + * @oncall react_native + */ + +import type {NextHandleFunction} from 'connect'; +import type {Logger} from './types/Logger'; + +import connect from 'connect'; +import openDebuggerMiddleware from './middleware/openDebuggerMiddleware'; + +type Options = $ReadOnly<{ + logger?: Logger, +}>; + +export default function createDevMiddleware({logger}: Options = {}): { + middleware: NextHandleFunction, +} { + const middleware = connect().use( + '/open-debugger', + openDebuggerMiddleware({logger}), + ); + + return {middleware}; +} diff --git a/packages/dev-middleware/src/index.js b/packages/dev-middleware/src/index.js new file mode 100644 index 0000000000..26f17c2499 --- /dev/null +++ b/packages/dev-middleware/src/index.js @@ -0,0 +1,12 @@ +/** + * 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 + * @format + * @oncall react_native + */ + +export {default as createDevMiddleware} from './createDevMiddleware'; diff --git a/packages/dev-middleware/src/middleware/openDebuggerMiddleware.js b/packages/dev-middleware/src/middleware/openDebuggerMiddleware.js new file mode 100644 index 0000000000..89f0fef9ef --- /dev/null +++ b/packages/dev-middleware/src/middleware/openDebuggerMiddleware.js @@ -0,0 +1,87 @@ +/** + * 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 + * @format + * @oncall react_native + */ + +import type {LaunchedChrome} from 'chrome-launcher'; +import type {NextHandleFunction} from 'connect'; +import type {IncomingMessage, ServerResponse} from 'http'; +import type {Logger} from '../types/Logger'; + +import url from 'url'; +import getDevServerUrl from '../utils/getDevServerUrl'; +import launchChromeDevTools from '../utils/launchChromeDevTools'; +import queryInspectorTargets from '../utils/queryInspectorTargets'; + +const debuggerInstances = new Map(); + +type Options = $ReadOnly<{ + logger?: Logger, +}>; + +/** + * Open the JavaScript debugger for a given CDP target (direct Hermes debugging). + * + * Currently supports Hermes targets, opening debugger websocket URL in Chrome + * DevTools. + * + * @see https://chromedevtools.github.io/devtools-protocol/ + */ +export default function openDebuggerMiddleware({ + logger, +}: Options): NextHandleFunction { + return async ( + req: IncomingMessage, + res: ServerResponse, + next: (err?: Error) => void, + ) => { + if (req.method === 'POST') { + const {query} = url.parse(req.url, true); + const {appId} = query; + + if (typeof appId !== 'string') { + res.writeHead(400); + res.end(); + return; + } + + const targets = await queryInspectorTargets(getDevServerUrl(req)); + const target = targets.find(_target => _target.description === appId); + + if (!target) { + res.writeHead(404); + res.end('Unable to find Chrome DevTools inspector target'); + logger?.warn( + 'No compatible apps connected. JavaScript debugging can only be used with the Hermes engine.', + ); + return; + } + + try { + logger?.info('Launching JS debugger...'); + debuggerInstances.get(appId)?.kill(); + debuggerInstances.set( + appId, + await launchChromeDevTools(target.webSocketDebuggerUrl), + ); + res.end(); + return; + } catch (e) { + logger?.error( + 'Error launching JS debugger: ' + e.message ?? 'Unknown error', + ); + res.writeHead(500); + res.end(); + return; + } + } + + next(); + }; +} diff --git a/packages/dev-middleware/src/types/Logger.js b/packages/dev-middleware/src/types/Logger.js new file mode 100644 index 0000000000..6bd30b9a93 --- /dev/null +++ b/packages/dev-middleware/src/types/Logger.js @@ -0,0 +1,17 @@ +/** + * 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 + * @format + * @oncall react_native + */ + +export type Logger = $ReadOnly<{ + error: (...message: Array) => void, + info: (...message: Array) => void, + warn: (...message: Array) => void, + ... +}>; diff --git a/packages/dev-middleware/src/utils/getDevServerUrl.js b/packages/dev-middleware/src/utils/getDevServerUrl.js new file mode 100644 index 0000000000..370bfe2e4e --- /dev/null +++ b/packages/dev-middleware/src/utils/getDevServerUrl.js @@ -0,0 +1,32 @@ +/** + * 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 + * @format + * @oncall react_native + */ + +import type {IncomingMessage} from 'http'; + +import net from 'net'; +import {TLSSocket} from 'tls'; + +/** + * Get the base URL to address the current development server. + */ +export default function getDevServerUrl(req: IncomingMessage): string { + const scheme = + req.socket instanceof TLSSocket && req.socket.encrypted === true + ? 'https' + : 'http'; + const {localAddress, localPort} = req.socket; + const address = + localAddress && net.isIPv6(localAddress) + ? `[${localAddress}]` + : localAddress; + + return `${scheme}:${address}:${localPort}`; +} diff --git a/packages/dev-middleware/src/utils/launchChromeDevTools.js b/packages/dev-middleware/src/utils/launchChromeDevTools.js new file mode 100644 index 0000000000..9da4844a40 --- /dev/null +++ b/packages/dev-middleware/src/utils/launchChromeDevTools.js @@ -0,0 +1,38 @@ +/** + * 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 + * @format + * @oncall react_native + */ + +import type {LaunchedChrome} from 'chrome-launcher'; + +import launchDebuggerAppWindow from './launchDebuggerAppWindow'; + +/** + * The Chrome DevTools frontend revision to use. This should be set to the + * latest version known to be compatible with Hermes. + * + * Revision should be the full identifier from: + * https://chromium.googlesource.com/chromium/src.git + */ +const DEVTOOLS_FRONTEND_REV = 'd9568d04d7dd79269c5a655d7ada69650c5a8336'; // Chrome 100.0.4896.75 + +/** + * Attempt to launch Chrome DevTools on the host machine for a given CDP target. + */ +export default async function launchChromeDevTools( + webSocketDebuggerUrl: string, +): Promise { + const urlBase = `https://chrome-devtools-frontend.appspot.com/serve_rev/@${DEVTOOLS_FRONTEND_REV}/devtools_app.html`; + const ws = webSocketDebuggerUrl.replace(/^ws:\/\//, ''); + + return launchDebuggerAppWindow( + `${urlBase}?panel=console&ws=${encodeURIComponent(ws)}`, + 'open-debugger', + ); +} diff --git a/packages/dev-middleware/src/utils/launchDebuggerAppWindow.js b/packages/dev-middleware/src/utils/launchDebuggerAppWindow.js new file mode 100644 index 0000000000..a2ae765ba3 --- /dev/null +++ b/packages/dev-middleware/src/utils/launchDebuggerAppWindow.js @@ -0,0 +1,56 @@ +/** + * 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 + * @format + * @oncall react_native + */ + +import type {LaunchedChrome} from 'chrome-launcher'; +import {promises as fs} from 'fs'; +import path from 'path'; +import osTempDir from 'temp-dir'; + +const ChromeLauncher = require('chrome-launcher'); + +/** + * Attempt to open a debugger frontend URL as a Google Chrome app window. + */ +export default async function launchDebuggerAppWindow( + url: string, + /** + * Used to construct the temp browser dir to preserve settings such as window + * position. + */ + intent: 'open-debugger', +): Promise { + const browserType = 'chrome'; + const userDataDir = await createTempDir( + `react-native-${intent}-${browserType}`, + ); + + try { + return ChromeLauncher.launch({ + chromeFlags: [ + `--app=${url}`, + `--user-data-dir=${userDataDir}`, + '--window-size=1200,600', + ], + }); + } catch (e) { + throw new Error( + 'Unable to find a browser on the host to open the debugger. Supported browsers: Google Chrome', + ); + } +} + +async function createTempDir(dirName: string): Promise { + const tempDir = path.join(osTempDir, dirName); + + await fs.mkdir(tempDir, {recursive: true}); + + return tempDir; +} diff --git a/packages/dev-middleware/src/utils/queryInspectorTargets.js b/packages/dev-middleware/src/utils/queryInspectorTargets.js new file mode 100644 index 0000000000..3aa0f72443 --- /dev/null +++ b/packages/dev-middleware/src/utils/queryInspectorTargets.js @@ -0,0 +1,40 @@ +/** + * 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 + * @format + * @oncall react_native + */ + +import fetch from 'node-fetch'; + +type ReactNativeCDPTarget = { + id: string, + description: string, + title: string, + type: string, + devtoolsFrontendUrl: string, + webSocketDebuggerUrl: string, + vm: string, + deviceName?: string, +}; + +/** + * Get the list of available debug targets from the React Native dev server. + * + * @see https://chromedevtools.github.io/devtools-protocol/ + */ +export default async function queryInspectorTargets( + devServerUrl: string, +): Promise { + const res = await fetch(`${devServerUrl}/json/list`); + const apps = (await res.json(): Array); + + // Only use targets with better reloading support + return apps.filter( + app => app.title === 'React Native Experimental (Improved Chrome Reloads)', + ); +} diff --git a/yarn.lock b/yarn.lock index 5d963caa84..9c40fce68e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3694,6 +3694,16 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== +chrome-launcher@^0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-0.15.2.tgz#4e6404e32200095fdce7f6a1e1004f9bd36fa5da" + integrity sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ== + dependencies: + "@types/node" "*" + escape-string-regexp "^4.0.0" + is-wsl "^2.2.0" + lighthouse-logger "^1.0.0" + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -4032,7 +4042,7 @@ dayjs@^1.8.15: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.15.tgz#7121bc04e6a7f2621ed6db566be4a8aaf8c3913e" integrity sha512-HYHCI1nohG52B45vCQg8Re3hNDZbMroWPkhz50yaX7Lu0ATyjGsTdoYZBpjED9ar6chqTx2dmSmM8A51mojnAg== -debug@2.6.9, debug@^2.2.0: +debug@2.6.9, debug@^2.2.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -5625,6 +5635,11 @@ is-directory@^0.3.1: resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + is-extendable@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -5759,6 +5774,13 @@ is-wsl@^1.1.0: resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -6552,6 +6574,14 @@ lie@~3.3.0: dependencies: immediate "~3.0.5" +lighthouse-logger@^1.0.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz#aef90f9e97cd81db367c7634292ee22079280aaa" + integrity sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g== + dependencies: + debug "^2.6.9" + marky "^1.2.2" + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -6709,6 +6739,11 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" +marky@^1.2.2: + version "1.2.5" + resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0" + integrity sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q== + memfs-or-file-map-to-github-branch@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/memfs-or-file-map-to-github-branch/-/memfs-or-file-map-to-github-branch-1.2.1.tgz#fdb9a85408262316a9bd5567409bf89be7d72f96" @@ -8729,6 +8764,11 @@ tar@^6.1.11: mkdirp "^1.0.3" yallist "^4.0.0" +temp-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" + integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== + temp@^0.8.4: version "0.8.4" resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.4.tgz#8c97a33a4770072e0a05f919396c7665a7dd59f2"