@expo/cli 54.0.23 → 54.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/build/bin/cli +1 -1
  2. package/build/src/api/rest/client.js +27 -12
  3. package/build/src/api/rest/client.js.map +1 -1
  4. package/build/src/api/user/UserSettings.js +4 -2
  5. package/build/src/api/user/UserSettings.js.map +1 -1
  6. package/build/src/export/embed/exportEmbedAsync.js +3 -3
  7. package/build/src/export/embed/exportEmbedAsync.js.map +1 -1
  8. package/build/src/export/embed/exportServer.js +1 -1
  9. package/build/src/export/embed/exportServer.js.map +1 -1
  10. package/build/src/export/exportApp.js +1 -1
  11. package/build/src/export/exportApp.js.map +1 -1
  12. package/build/src/export/exportAssets.js +10 -9
  13. package/build/src/export/exportAssets.js.map +1 -1
  14. package/build/src/export/publicFolder.js +19 -1
  15. package/build/src/export/publicFolder.js.map +1 -1
  16. package/build/src/prebuild/resolveTemplate.js +10 -5
  17. package/build/src/prebuild/resolveTemplate.js.map +1 -1
  18. package/build/src/run/android/resolveLaunchProps.js +11 -6
  19. package/build/src/run/android/resolveLaunchProps.js.map +1 -1
  20. package/build/src/run/android/runAndroidAsync.js.map +1 -1
  21. package/build/src/run/ios/appleDevice/client/LockdowndClient.js +1 -1
  22. package/build/src/run/ios/appleDevice/client/LockdowndClient.js.map +1 -1
  23. package/build/src/run/ios/launchApp.js +1 -1
  24. package/build/src/run/ios/launchApp.js.map +1 -1
  25. package/build/src/start/doctor/apple/SimulatorAppPrerequisite.js +39 -2
  26. package/build/src/start/doctor/apple/SimulatorAppPrerequisite.js.map +1 -1
  27. package/build/src/start/platforms/android/AndroidPlatformManager.js.map +1 -1
  28. package/build/src/start/platforms/android/adb.js +16 -15
  29. package/build/src/start/platforms/android/adb.js.map +1 -1
  30. package/build/src/start/platforms/ios/ApplePlatformManager.js.map +1 -1
  31. package/build/src/start/server/BundlerDevServer.js +18 -4
  32. package/build/src/start/server/BundlerDevServer.js.map +1 -1
  33. package/build/src/start/server/DevToolsPluginManager.js.map +1 -1
  34. package/build/src/start/server/getStaticRenderFunctions.js +2 -1
  35. package/build/src/start/server/getStaticRenderFunctions.js.map +1 -1
  36. package/build/src/start/server/metro/debugging/createDebugMiddleware.js +6 -5
  37. package/build/src/start/server/metro/debugging/createDebugMiddleware.js.map +1 -1
  38. package/build/src/start/server/metro/debugging/messageHandlers/NetworkResponse.js +17 -1
  39. package/build/src/start/server/metro/debugging/messageHandlers/NetworkResponse.js.map +1 -1
  40. package/build/src/start/server/metro/dev-server/createMessageSocket.js +13 -2
  41. package/build/src/start/server/metro/dev-server/createMessageSocket.js.map +1 -1
  42. package/build/src/start/server/metro/dev-server/createMetroMiddleware.js +85 -15
  43. package/build/src/start/server/metro/dev-server/createMetroMiddleware.js.map +1 -1
  44. package/build/src/start/server/metro/instantiateMetro.js +7 -3
  45. package/build/src/start/server/metro/instantiateMetro.js.map +1 -1
  46. package/build/src/start/server/metro/metroErrorInterface.js +5 -2
  47. package/build/src/start/server/metro/metroErrorInterface.js.map +1 -1
  48. package/build/src/start/server/metro/router.js +10 -1
  49. package/build/src/start/server/metro/router.js.map +1 -1
  50. package/build/src/start/server/middleware/InterstitialPageMiddleware.js +7 -4
  51. package/build/src/start/server/middleware/InterstitialPageMiddleware.js.map +1 -1
  52. package/build/src/start/server/middleware/ServeStaticMiddleware.js +2 -9
  53. package/build/src/start/server/middleware/ServeStaticMiddleware.js.map +1 -1
  54. package/build/src/start/server/middleware/inspector/createJsInspectorMiddleware.js +14 -24
  55. package/build/src/start/server/middleware/inspector/createJsInspectorMiddleware.js.map +1 -1
  56. package/build/src/start/server/webTemplate.js +3 -5
  57. package/build/src/start/server/webTemplate.js.map +1 -1
  58. package/build/src/utils/codesigning.js +6 -0
  59. package/build/src/utils/codesigning.js.map +1 -1
  60. package/build/src/utils/net.js +13 -0
  61. package/build/src/utils/net.js.map +1 -1
  62. package/build/src/utils/telemetry/clients/FetchClient.js +1 -1
  63. package/build/src/utils/telemetry/utils/context.js +1 -1
  64. package/build/src/utils/url.js +0 -12
  65. package/build/src/utils/url.js.map +1 -1
  66. package/package.json +9 -9
  67. package/static/loading-page/index.html +10 -2
@@ -59,6 +59,7 @@ const _metroErrorInterface = require("./metro/metroErrorInterface");
59
59
  const _metroOptions = require("./middleware/metroOptions");
60
60
  const _serverLogLikeMetro = require("./serverLogLikeMetro");
61
61
  const _delay = require("../../utils/delay");
62
+ const _dir = require("../../utils/dir");
62
63
  const _errors = require("../../utils/errors");
63
64
  const _filePath = require("../../utils/filePath");
64
65
  const _profile = require("../../utils/profile");
@@ -85,7 +86,7 @@ if (!process.isBun) {
85
86
  async function ensureFileInRootDirectory(projectRoot, otherFile) {
86
87
  // Cannot be accessed using Metro's server API, we need to move the file
87
88
  // into the project root and try again.
88
- if (!_path().default.relative(projectRoot, otherFile).startsWith('..' + _path().default.sep)) {
89
+ if (!(0, _dir.isPathInside)(otherFile, projectRoot)) {
89
90
  return otherFile;
90
91
  }
91
92
  // Copy the file into the project to ensure it works in monorepos.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/start/server/getStaticRenderFunctions.ts"],"sourcesContent":["/**\n * Copyright © 2022 650 Industries.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport { getMetroServerRoot } from '@expo/config/paths';\nimport fs from 'fs';\nimport path from 'path';\nimport requireString from 'require-from-string';\n\nimport { IS_METRO_BUNDLE_ERROR_SYMBOL, logMetroError } from './metro/metroErrorInterface';\nimport { createBundleUrlPath, ExpoMetroOptions } from './middleware/metroOptions';\nimport { augmentLogs } from './serverLogLikeMetro';\nimport { delayAsync } from '../../utils/delay';\nimport { SilentError } from '../../utils/errors';\nimport { toPosixPath } from '../../utils/filePath';\nimport { profile } from '../../utils/profile';\n\nconst debug = require('debug')('expo:start:server:getStaticRenderFunctions') as typeof console.log;\n\n/** The list of input keys will become optional, everything else will remain the same. */\nexport type PickPartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;\n\nexport const cachedSourceMaps: Map<string, { url: string; map: string }> = new Map();\n\n// Support unhandled rejections\n// Detect if running in Bun\n\n// @ts-expect-error: This is a global variable that is set by Bun.\nif (!process.isBun) {\n require('source-map-support').install({\n retrieveSourceMap(source: string) {\n if (cachedSourceMaps.has(source)) {\n return cachedSourceMaps.get(source);\n }\n return null;\n },\n });\n}\n\nasync function ensureFileInRootDirectory(projectRoot: string, otherFile: string) {\n // Cannot be accessed using Metro's server API, we need to move the file\n // into the project root and try again.\n if (!path.relative(projectRoot, otherFile).startsWith('..' + path.sep)) {\n return otherFile;\n }\n\n // Copy the file into the project to ensure it works in monorepos.\n // This means the file cannot have any relative imports.\n const tempDir = path.join(projectRoot, '.expo/static-tmp');\n await fs.promises.mkdir(tempDir, { recursive: true });\n const moduleId = path.join(tempDir, path.basename(otherFile));\n await fs.promises.writeFile(moduleId, await fs.promises.readFile(otherFile, 'utf8'));\n // Sleep to give watchman time to register the file.\n await delayAsync(50);\n return moduleId;\n}\n\nexport async function createMetroEndpointAsync(\n projectRoot: string,\n devServerUrl: string,\n absoluteFilePath: string,\n props: PickPartial<ExpoMetroOptions, 'mainModuleName' | 'bytecode'>\n): Promise<string> {\n const root = getMetroServerRoot(projectRoot);\n const safeOtherFile = await ensureFileInRootDirectory(projectRoot, absoluteFilePath);\n const serverPath = path.relative(root, safeOtherFile).replace(/\\.[jt]sx?$/, '');\n\n const urlFragment = createBundleUrlPath({\n mainModuleName: serverPath,\n lazy: false,\n asyncRoutes: false,\n inlineSourceMap: false,\n engine: 'hermes',\n minify: false,\n bytecode: false,\n ...props,\n });\n\n let url: string;\n if (devServerUrl) {\n url = new URL(urlFragment.replace(/^\\//, ''), devServerUrl).toString();\n } else {\n url = '/' + urlFragment.replace(/^\\/+/, '');\n }\n return url;\n}\n\nexport function evalMetroAndWrapFunctions<T = Record<string, any>>(\n projectRoot: string,\n script: string,\n filename: string,\n isExporting: boolean\n): T {\n // TODO: Add back stack trace logic that hides traces from metro-runtime and other internal modules.\n const contents = evalMetroNoHandling(projectRoot, script, filename);\n\n if (!contents) {\n // This can happen if ErrorUtils isn't working correctly on web and failing to throw an error when a module throws.\n // This is unexpected behavior and should not be pretty formatted, therefore we're avoiding CommandError.\n throw new Error(\n '[Expo SSR] Module returned undefined, this could be due to a misconfiguration in Metro error handling'\n );\n }\n // wrap each function with a try/catch that uses Metro's error formatter\n return Object.keys(contents).reduce((acc, key) => {\n const fn = contents[key];\n if (typeof fn !== 'function') {\n return { ...acc, [key]: fn };\n }\n\n acc[key] = async function (...props: any[]) {\n try {\n return await fn.apply(this, props);\n } catch (error: any) {\n await logMetroError(projectRoot, { error });\n\n if (isExporting || error[IS_METRO_BUNDLE_ERROR_SYMBOL]) {\n throw error;\n } else {\n // TODO: When does this happen?\n throw new SilentError(error);\n }\n }\n };\n return acc;\n }, {} as any);\n}\n\nexport function evalMetroNoHandling(projectRoot: string, src: string, filename: string) {\n augmentLogs(projectRoot);\n\n // NOTE(@kitten): `require-from-string` derives a base path from the filename we pass it,\n // but doesn't validate that the filename exists. These debug messages should help identify\n // these problems, if they occur in user projects without reproductions\n if (!fs.existsSync(path.dirname(filename))) {\n debug(`evalMetroNoHandling received filename in a directory that does not exist: ${filename}`);\n } else if (!toPosixPath(path.dirname(filename)).startsWith(toPosixPath(projectRoot))) {\n debug(`evalMetroNoHandling received filename outside of the project root: ${filename}`);\n }\n\n return profile(requireString, 'eval-metro-bundle')(src, filename);\n}\n"],"names":["cachedSourceMaps","createMetroEndpointAsync","evalMetroAndWrapFunctions","evalMetroNoHandling","debug","require","Map","process","isBun","install","retrieveSourceMap","source","has","get","ensureFileInRootDirectory","projectRoot","otherFile","path","relative","startsWith","sep","tempDir","join","fs","promises","mkdir","recursive","moduleId","basename","writeFile","readFile","delayAsync","devServerUrl","absoluteFilePath","props","root","getMetroServerRoot","safeOtherFile","serverPath","replace","urlFragment","createBundleUrlPath","mainModuleName","lazy","asyncRoutes","inlineSourceMap","engine","minify","bytecode","url","URL","toString","script","filename","isExporting","contents","Error","Object","keys","reduce","acc","key","fn","apply","error","logMetroError","IS_METRO_BUNDLE_ERROR_SYMBOL","SilentError","src","augmentLogs","existsSync","dirname","toPosixPath","profile","requireString"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../src/start/server/getStaticRenderFunctions.ts"],"sourcesContent":["/**\n * Copyright © 2022 650 Industries.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport { getMetroServerRoot } from '@expo/config/paths';\nimport fs from 'fs';\nimport path from 'path';\nimport requireString from 'require-from-string';\n\nimport { IS_METRO_BUNDLE_ERROR_SYMBOL, logMetroError } from './metro/metroErrorInterface';\nimport { createBundleUrlPath, ExpoMetroOptions } from './middleware/metroOptions';\nimport { augmentLogs } from './serverLogLikeMetro';\nimport { delayAsync } from '../../utils/delay';\nimport { isPathInside } from '../../utils/dir';\nimport { SilentError } from '../../utils/errors';\nimport { toPosixPath } from '../../utils/filePath';\nimport { profile } from '../../utils/profile';\n\nconst debug = require('debug')('expo:start:server:getStaticRenderFunctions') as typeof console.log;\n\n/** The list of input keys will become optional, everything else will remain the same. */\nexport type PickPartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;\n\nexport const cachedSourceMaps: Map<string, { url: string; map: string }> = new Map();\n\n// Support unhandled rejections\n// Detect if running in Bun\n\n// @ts-expect-error: This is a global variable that is set by Bun.\nif (!process.isBun) {\n require('source-map-support').install({\n retrieveSourceMap(source: string) {\n if (cachedSourceMaps.has(source)) {\n return cachedSourceMaps.get(source);\n }\n return null;\n },\n });\n}\n\nasync function ensureFileInRootDirectory(projectRoot: string, otherFile: string) {\n // Cannot be accessed using Metro's server API, we need to move the file\n // into the project root and try again.\n if (!isPathInside(otherFile, projectRoot)) {\n return otherFile;\n }\n\n // Copy the file into the project to ensure it works in monorepos.\n // This means the file cannot have any relative imports.\n const tempDir = path.join(projectRoot, '.expo/static-tmp');\n await fs.promises.mkdir(tempDir, { recursive: true });\n const moduleId = path.join(tempDir, path.basename(otherFile));\n await fs.promises.writeFile(moduleId, await fs.promises.readFile(otherFile, 'utf8'));\n // Sleep to give watchman time to register the file.\n await delayAsync(50);\n return moduleId;\n}\n\nexport async function createMetroEndpointAsync(\n projectRoot: string,\n devServerUrl: string,\n absoluteFilePath: string,\n props: PickPartial<ExpoMetroOptions, 'mainModuleName' | 'bytecode'>\n): Promise<string> {\n const root = getMetroServerRoot(projectRoot);\n const safeOtherFile = await ensureFileInRootDirectory(projectRoot, absoluteFilePath);\n const serverPath = path.relative(root, safeOtherFile).replace(/\\.[jt]sx?$/, '');\n\n const urlFragment = createBundleUrlPath({\n mainModuleName: serverPath,\n lazy: false,\n asyncRoutes: false,\n inlineSourceMap: false,\n engine: 'hermes',\n minify: false,\n bytecode: false,\n ...props,\n });\n\n let url: string;\n if (devServerUrl) {\n url = new URL(urlFragment.replace(/^\\//, ''), devServerUrl).toString();\n } else {\n url = '/' + urlFragment.replace(/^\\/+/, '');\n }\n return url;\n}\n\nexport function evalMetroAndWrapFunctions<T = Record<string, any>>(\n projectRoot: string,\n script: string,\n filename: string,\n isExporting: boolean\n): T {\n // TODO: Add back stack trace logic that hides traces from metro-runtime and other internal modules.\n const contents = evalMetroNoHandling(projectRoot, script, filename);\n\n if (!contents) {\n // This can happen if ErrorUtils isn't working correctly on web and failing to throw an error when a module throws.\n // This is unexpected behavior and should not be pretty formatted, therefore we're avoiding CommandError.\n throw new Error(\n '[Expo SSR] Module returned undefined, this could be due to a misconfiguration in Metro error handling'\n );\n }\n // wrap each function with a try/catch that uses Metro's error formatter\n return Object.keys(contents).reduce((acc, key) => {\n const fn = contents[key];\n if (typeof fn !== 'function') {\n return { ...acc, [key]: fn };\n }\n\n acc[key] = async function (...props: any[]) {\n try {\n return await fn.apply(this, props);\n } catch (error: any) {\n await logMetroError(projectRoot, { error });\n\n if (isExporting || error[IS_METRO_BUNDLE_ERROR_SYMBOL]) {\n throw error;\n } else {\n // TODO: When does this happen?\n throw new SilentError(error);\n }\n }\n };\n return acc;\n }, {} as any);\n}\n\nexport function evalMetroNoHandling(projectRoot: string, src: string, filename: string) {\n augmentLogs(projectRoot);\n\n // NOTE(@kitten): `require-from-string` derives a base path from the filename we pass it,\n // but doesn't validate that the filename exists. These debug messages should help identify\n // these problems, if they occur in user projects without reproductions\n if (!fs.existsSync(path.dirname(filename))) {\n debug(`evalMetroNoHandling received filename in a directory that does not exist: ${filename}`);\n } else if (!toPosixPath(path.dirname(filename)).startsWith(toPosixPath(projectRoot))) {\n debug(`evalMetroNoHandling received filename outside of the project root: ${filename}`);\n }\n\n return profile(requireString, 'eval-metro-bundle')(src, filename);\n}\n"],"names":["cachedSourceMaps","createMetroEndpointAsync","evalMetroAndWrapFunctions","evalMetroNoHandling","debug","require","Map","process","isBun","install","retrieveSourceMap","source","has","get","ensureFileInRootDirectory","projectRoot","otherFile","isPathInside","tempDir","path","join","fs","promises","mkdir","recursive","moduleId","basename","writeFile","readFile","delayAsync","devServerUrl","absoluteFilePath","props","root","getMetroServerRoot","safeOtherFile","serverPath","relative","replace","urlFragment","createBundleUrlPath","mainModuleName","lazy","asyncRoutes","inlineSourceMap","engine","minify","bytecode","url","URL","toString","script","filename","isExporting","contents","Error","Object","keys","reduce","acc","key","fn","apply","error","logMetroError","IS_METRO_BUNDLE_ERROR_SYMBOL","SilentError","src","augmentLogs","existsSync","dirname","toPosixPath","startsWith","profile","requireString"],"mappings":"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"}
@@ -16,9 +16,9 @@ function _ws() {
16
16
  return data;
17
17
  }
18
18
  const _createHandlersFactory = require("./createHandlersFactory");
19
+ const _NetworkResponse = require("./messageHandlers/NetworkResponse");
19
20
  const _env = require("../../../../utils/env");
20
21
  const _net = require("../../../../utils/net");
21
- const _NetworkResponse = require("./messageHandlers/NetworkResponse");
22
22
  const debug = require('debug')('expo:metro:debugging:middleware');
23
23
  function createDebugMiddleware({ projectRoot, serverBaseUrl, reporter }) {
24
24
  // Load the React Native debugging tools from project
@@ -89,9 +89,10 @@ function makeLogger(reporter, level) {
89
89
  const wss = new (_ws()).WebSocketServer({
90
90
  noServer: true,
91
91
  perMessageDeflate: true,
92
- // Don't crash on exceptionally large messages - assume the device is
93
- // well-behaved and the debugger is prepared to handle large messages.
94
- maxPayload: 0
92
+ // Bounded so an unauthenticated client cannot exhaust dev-server memory
93
+ // with one oversized frame. 16 MiB covers realistic response bodies
94
+ // (image previews, large JSON dumps, base64-inflated assets).
95
+ maxPayload: 16 * 1024 * 1024
95
96
  });
96
97
  wss.on('connection', (networkSocket)=>{
97
98
  networkSocket.on('message', (data)=>{
@@ -101,7 +102,7 @@ function makeLogger(reporter, level) {
101
102
  if (message.method === 'Expo(Network.receivedResponseBody)' && message.params) {
102
103
  // If its a response body, write it to the global storage
103
104
  const { requestId, ...requestInfo } = message.params;
104
- _NetworkResponse.NETWORK_RESPONSE_STORAGE.set(requestId, requestInfo);
105
+ (0, _NetworkResponse.recordNetworkResponse)(requestId, requestInfo);
105
106
  } else if (message.method.startsWith('Network.')) {
106
107
  // Otherwise, directly re-broadcast the Network events to all connected debuggers
107
108
  debuggerWebsocket.clients.forEach((debuggerSocket)=>{
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../src/start/server/metro/debugging/createDebugMiddleware.ts"],"sourcesContent":["import type { NextHandleFunction } from 'connect';\nimport { WebSocketServer } from 'ws';\n\nimport { createHandlersFactory } from './createHandlersFactory';\nimport { env } from '../../../../utils/env';\nimport { isLocalSocket, isMatchingOrigin } from '../../../../utils/net';\nimport { TerminalReporter } from '../TerminalReporter';\nimport { NETWORK_RESPONSE_STORAGE } from './messageHandlers/NetworkResponse';\n\nconst debug = require('debug')('expo:metro:debugging:middleware') as typeof console.log;\n\ninterface DebugMiddleware {\n debugMiddleware: NextHandleFunction;\n debugWebsocketEndpoints: Record<string, WebSocketServer>;\n}\n\ninterface DebugMiddlewareParams {\n projectRoot: string;\n serverBaseUrl: string;\n reporter: TerminalReporter;\n}\n\nexport function createDebugMiddleware({\n projectRoot,\n serverBaseUrl,\n reporter,\n}: DebugMiddlewareParams): DebugMiddleware {\n // Load the React Native debugging tools from project\n // TODO: check if this works with isolated modules\n const { createDevMiddleware } =\n require('@react-native/dev-middleware') as typeof import('@react-native/dev-middleware');\n\n const { middleware, websocketEndpoints } = createDevMiddleware({\n projectRoot,\n serverBaseUrl,\n logger: createLogger(reporter),\n unstable_customInspectorMessageHandler: createHandlersFactory(),\n // TODO: Forward all events to the shared Metro log reporter. Do this when we have opinions on how all logs should be presented.\n // unstable_eventReporter: {\n // logEvent(event) {\n // reporter.update(event);\n // },\n // },\n unstable_experiments: {\n // Enable the Network tab in React Native DevTools\n enableNetworkInspector: true,\n // Only enable opening the browser version of React Native DevTools when debugging.\n // This is useful when debugging the React Native DevTools by going to `/open-debugger` in the browser.\n enableOpenDebuggerRedirect: env.EXPO_DEBUG,\n },\n });\n\n const debuggerWebsocketEndpoint = websocketEndpoints['/inspector/debug'] as WebSocketServer;\n\n // NOTE(cedric): add a temporary websocket to handle Network-related CDP events\n websocketEndpoints['/inspector/network'] = createNetworkWebsocket(debuggerWebsocketEndpoint);\n\n // Explicitly limit debugger websocket to loopback requests\n debuggerWebsocketEndpoint.on('connection', (socket, request) => {\n if (!isLocalSocket(request.socket) || !isMatchingOrigin(request, serverBaseUrl)) {\n // NOTE: `socket.close` nicely closes the websocket, which will still allow incoming messages\n // `socket.terminate` instead forcefully closes down the socket\n socket.terminate();\n }\n });\n\n return {\n debugMiddleware(req, res, next) {\n // The debugger middleware is skipped entirely if the connection isn't a loopback request\n if (isLocalSocket(req.socket)) {\n return middleware(req, res, next);\n } else {\n return next();\n }\n },\n debugWebsocketEndpoints: websocketEndpoints,\n };\n}\n\nfunction createLogger(\n reporter: TerminalReporter\n): Parameters<typeof import('@react-native/dev-middleware').createDevMiddleware>[0]['logger'] {\n return {\n info: makeLogger(reporter, 'info'),\n warn: makeLogger(reporter, 'warn'),\n error: makeLogger(reporter, 'error'),\n };\n}\n\nfunction makeLogger(reporter: TerminalReporter, level: 'info' | 'warn' | 'error') {\n return (...data: any[]) =>\n reporter.update({\n type: 'unstable_server_log',\n level,\n data,\n });\n}\n\n/**\n * This adds a dedicated websocket connection that handles Network-related CDP events.\n * It's a temporary solution until Fusebox either implements the Network CDP domain,\n * or allows external domain agents that can send messages over the CDP socket to the debugger.\n * The Network websocket rebroadcasts events on the debugger CDP connections.\n */\nfunction createNetworkWebsocket(debuggerWebsocket: WebSocketServer) {\n const wss = new WebSocketServer({\n noServer: true,\n perMessageDeflate: true,\n // Don't crash on exceptionally large messages - assume the device is\n // well-behaved and the debugger is prepared to handle large messages.\n maxPayload: 0,\n });\n\n wss.on('connection', (networkSocket) => {\n networkSocket.on('message', (data) => {\n try {\n // Parse the network message, to determine how the message should be handled\n const message = JSON.parse(data.toString());\n\n if (message.method === 'Expo(Network.receivedResponseBody)' && message.params) {\n // If its a response body, write it to the global storage\n const { requestId, ...requestInfo } = message.params;\n NETWORK_RESPONSE_STORAGE.set(requestId, requestInfo);\n } else if (message.method.startsWith('Network.')) {\n // Otherwise, directly re-broadcast the Network events to all connected debuggers\n debuggerWebsocket.clients.forEach((debuggerSocket) => {\n if (debuggerSocket.readyState === debuggerSocket.OPEN) {\n debuggerSocket.send(data.toString());\n }\n });\n }\n } catch (error) {\n debug('Failed to handle Network CDP event', error);\n }\n });\n });\n\n return wss;\n}\n"],"names":["createDebugMiddleware","debug","require","projectRoot","serverBaseUrl","reporter","createDevMiddleware","middleware","websocketEndpoints","logger","createLogger","unstable_customInspectorMessageHandler","createHandlersFactory","unstable_experiments","enableNetworkInspector","enableOpenDebuggerRedirect","env","EXPO_DEBUG","debuggerWebsocketEndpoint","createNetworkWebsocket","on","socket","request","isLocalSocket","isMatchingOrigin","terminate","debugMiddleware","req","res","next","debugWebsocketEndpoints","info","makeLogger","warn","error","level","data","update","type","debuggerWebsocket","wss","WebSocketServer","noServer","perMessageDeflate","maxPayload","networkSocket","message","JSON","parse","toString","method","params","requestId","requestInfo","NETWORK_RESPONSE_STORAGE","set","startsWith","clients","forEach","debuggerSocket","readyState","OPEN","send"],"mappings":";;;;+BAsBgBA;;;eAAAA;;;;yBArBgB;;;;;;uCAEM;qBAClB;qBAC4B;iCAEP;AAEzC,MAAMC,QAAQC,QAAQ,SAAS;AAaxB,SAASF,sBAAsB,EACpCG,WAAW,EACXC,aAAa,EACbC,QAAQ,EACc;IACtB,qDAAqD;IACrD,kDAAkD;IAClD,MAAM,EAAEC,mBAAmB,EAAE,GAC3BJ,QAAQ;IAEV,MAAM,EAAEK,UAAU,EAAEC,kBAAkB,EAAE,GAAGF,oBAAoB;QAC7DH;QACAC;QACAK,QAAQC,aAAaL;QACrBM,wCAAwCC,IAAAA,4CAAqB;QAC7D,gIAAgI;QAChI,4BAA4B;QAC5B,sBAAsB;QACtB,8BAA8B;QAC9B,OAAO;QACP,KAAK;QACLC,sBAAsB;YACpB,kDAAkD;YAClDC,wBAAwB;YACxB,mFAAmF;YACnF,uGAAuG;YACvGC,4BAA4BC,QAAG,CAACC,UAAU;QAC5C;IACF;IAEA,MAAMC,4BAA4BV,kBAAkB,CAAC,mBAAmB;IAExE,+EAA+E;IAC/EA,kBAAkB,CAAC,qBAAqB,GAAGW,uBAAuBD;IAElE,2DAA2D;IAC3DA,0BAA0BE,EAAE,CAAC,cAAc,CAACC,QAAQC;QAClD,IAAI,CAACC,IAAAA,kBAAa,EAACD,QAAQD,MAAM,KAAK,CAACG,IAAAA,qBAAgB,EAACF,SAASlB,gBAAgB;YAC/E,6FAA6F;YAC7F,+DAA+D;YAC/DiB,OAAOI,SAAS;QAClB;IACF;IAEA,OAAO;QACLC,iBAAgBC,GAAG,EAAEC,GAAG,EAAEC,IAAI;YAC5B,yFAAyF;YACzF,IAAIN,IAAAA,kBAAa,EAACI,IAAIN,MAAM,GAAG;gBAC7B,OAAOd,WAAWoB,KAAKC,KAAKC;YAC9B,OAAO;gBACL,OAAOA;YACT;QACF;QACAC,yBAAyBtB;IAC3B;AACF;AAEA,SAASE,aACPL,QAA0B;IAE1B,OAAO;QACL0B,MAAMC,WAAW3B,UAAU;QAC3B4B,MAAMD,WAAW3B,UAAU;QAC3B6B,OAAOF,WAAW3B,UAAU;IAC9B;AACF;AAEA,SAAS2B,WAAW3B,QAA0B,EAAE8B,KAAgC;IAC9E,OAAO,CAAC,GAAGC,OACT/B,SAASgC,MAAM,CAAC;YACdC,MAAM;YACNH;YACAC;QACF;AACJ;AAEA;;;;;CAKC,GACD,SAASjB,uBAAuBoB,iBAAkC;IAChE,MAAMC,MAAM,IAAIC,CAAAA,KAAc,iBAAC,CAAC;QAC9BC,UAAU;QACVC,mBAAmB;QACnB,qEAAqE;QACrE,sEAAsE;QACtEC,YAAY;IACd;IAEAJ,IAAIpB,EAAE,CAAC,cAAc,CAACyB;QACpBA,cAAczB,EAAE,CAAC,WAAW,CAACgB;YAC3B,IAAI;gBACF,4EAA4E;gBAC5E,MAAMU,UAAUC,KAAKC,KAAK,CAACZ,KAAKa,QAAQ;gBAExC,IAAIH,QAAQI,MAAM,KAAK,wCAAwCJ,QAAQK,MAAM,EAAE;oBAC7E,yDAAyD;oBACzD,MAAM,EAAEC,SAAS,EAAE,GAAGC,aAAa,GAAGP,QAAQK,MAAM;oBACpDG,yCAAwB,CAACC,GAAG,CAACH,WAAWC;gBAC1C,OAAO,IAAIP,QAAQI,MAAM,CAACM,UAAU,CAAC,aAAa;oBAChD,iFAAiF;oBACjFjB,kBAAkBkB,OAAO,CAACC,OAAO,CAAC,CAACC;wBACjC,IAAIA,eAAeC,UAAU,KAAKD,eAAeE,IAAI,EAAE;4BACrDF,eAAeG,IAAI,CAAC1B,KAAKa,QAAQ;wBACnC;oBACF;gBACF;YACF,EAAE,OAAOf,OAAO;gBACdjC,MAAM,sCAAsCiC;YAC9C;QACF;IACF;IAEA,OAAOM;AACT"}
1
+ {"version":3,"sources":["../../../../../../src/start/server/metro/debugging/createDebugMiddleware.ts"],"sourcesContent":["import type { NextHandleFunction } from 'connect';\nimport { WebSocketServer } from 'ws';\n\nimport { createHandlersFactory } from './createHandlersFactory';\nimport { recordNetworkResponse } from './messageHandlers/NetworkResponse';\nimport { env } from '../../../../utils/env';\nimport { isLocalSocket, isMatchingOrigin } from '../../../../utils/net';\nimport { TerminalReporter } from '../TerminalReporter';\n\nconst debug = require('debug')('expo:metro:debugging:middleware') as typeof console.log;\n\ninterface DebugMiddleware {\n debugMiddleware: NextHandleFunction;\n debugWebsocketEndpoints: Record<string, WebSocketServer>;\n}\n\ninterface DebugMiddlewareParams {\n projectRoot: string;\n serverBaseUrl: string;\n reporter: TerminalReporter;\n}\n\nexport function createDebugMiddleware({\n projectRoot,\n serverBaseUrl,\n reporter,\n}: DebugMiddlewareParams): DebugMiddleware {\n // Load the React Native debugging tools from project\n // TODO: check if this works with isolated modules\n const { createDevMiddleware } =\n require('@react-native/dev-middleware') as typeof import('@react-native/dev-middleware');\n\n const { middleware, websocketEndpoints } = createDevMiddleware({\n projectRoot,\n serverBaseUrl,\n logger: createLogger(reporter),\n unstable_customInspectorMessageHandler: createHandlersFactory(),\n // TODO: Forward all events to the shared Metro log reporter. Do this when we have opinions on how all logs should be presented.\n // unstable_eventReporter: {\n // logEvent(event) {\n // reporter.update(event);\n // },\n // },\n unstable_experiments: {\n // Enable the Network tab in React Native DevTools\n enableNetworkInspector: true,\n // Only enable opening the browser version of React Native DevTools when debugging.\n // This is useful when debugging the React Native DevTools by going to `/open-debugger` in the browser.\n enableOpenDebuggerRedirect: env.EXPO_DEBUG,\n },\n });\n\n const debuggerWebsocketEndpoint = websocketEndpoints['/inspector/debug'] as WebSocketServer;\n\n // NOTE(cedric): add a temporary websocket to handle Network-related CDP events\n websocketEndpoints['/inspector/network'] = createNetworkWebsocket(debuggerWebsocketEndpoint);\n\n // Explicitly limit debugger websocket to loopback requests\n debuggerWebsocketEndpoint.on('connection', (socket, request) => {\n if (!isLocalSocket(request.socket) || !isMatchingOrigin(request, serverBaseUrl)) {\n // NOTE: `socket.close` nicely closes the websocket, which will still allow incoming messages\n // `socket.terminate` instead forcefully closes down the socket\n socket.terminate();\n }\n });\n\n return {\n debugMiddleware(req, res, next) {\n // The debugger middleware is skipped entirely if the connection isn't a loopback request\n if (isLocalSocket(req.socket)) {\n return middleware(req, res, next);\n } else {\n return next();\n }\n },\n debugWebsocketEndpoints: websocketEndpoints,\n };\n}\n\nfunction createLogger(\n reporter: TerminalReporter\n): Parameters<typeof import('@react-native/dev-middleware').createDevMiddleware>[0]['logger'] {\n return {\n info: makeLogger(reporter, 'info'),\n warn: makeLogger(reporter, 'warn'),\n error: makeLogger(reporter, 'error'),\n };\n}\n\nfunction makeLogger(reporter: TerminalReporter, level: 'info' | 'warn' | 'error') {\n return (...data: any[]) =>\n reporter.update({\n type: 'unstable_server_log',\n level,\n data,\n });\n}\n\n/**\n * This adds a dedicated websocket connection that handles Network-related CDP events.\n * It's a temporary solution until Fusebox either implements the Network CDP domain,\n * or allows external domain agents that can send messages over the CDP socket to the debugger.\n * The Network websocket rebroadcasts events on the debugger CDP connections.\n */\nfunction createNetworkWebsocket(debuggerWebsocket: WebSocketServer) {\n const wss = new WebSocketServer({\n noServer: true,\n perMessageDeflate: true,\n // Bounded so an unauthenticated client cannot exhaust dev-server memory\n // with one oversized frame. 16 MiB covers realistic response bodies\n // (image previews, large JSON dumps, base64-inflated assets).\n maxPayload: 16 * 1024 * 1024,\n });\n\n wss.on('connection', (networkSocket) => {\n networkSocket.on('message', (data) => {\n try {\n // Parse the network message, to determine how the message should be handled\n const message = JSON.parse(data.toString());\n\n if (message.method === 'Expo(Network.receivedResponseBody)' && message.params) {\n // If its a response body, write it to the global storage\n const { requestId, ...requestInfo } = message.params;\n recordNetworkResponse(requestId, requestInfo);\n } else if (message.method.startsWith('Network.')) {\n // Otherwise, directly re-broadcast the Network events to all connected debuggers\n debuggerWebsocket.clients.forEach((debuggerSocket) => {\n if (debuggerSocket.readyState === debuggerSocket.OPEN) {\n debuggerSocket.send(data.toString());\n }\n });\n }\n } catch (error) {\n debug('Failed to handle Network CDP event', error);\n }\n });\n });\n\n return wss;\n}\n"],"names":["createDebugMiddleware","debug","require","projectRoot","serverBaseUrl","reporter","createDevMiddleware","middleware","websocketEndpoints","logger","createLogger","unstable_customInspectorMessageHandler","createHandlersFactory","unstable_experiments","enableNetworkInspector","enableOpenDebuggerRedirect","env","EXPO_DEBUG","debuggerWebsocketEndpoint","createNetworkWebsocket","on","socket","request","isLocalSocket","isMatchingOrigin","terminate","debugMiddleware","req","res","next","debugWebsocketEndpoints","info","makeLogger","warn","error","level","data","update","type","debuggerWebsocket","wss","WebSocketServer","noServer","perMessageDeflate","maxPayload","networkSocket","message","JSON","parse","toString","method","params","requestId","requestInfo","recordNetworkResponse","startsWith","clients","forEach","debuggerSocket","readyState","OPEN","send"],"mappings":"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"}
@@ -14,15 +14,31 @@ _export(exports, {
14
14
  },
15
15
  NetworkResponseHandler: function() {
16
16
  return NetworkResponseHandler;
17
+ },
18
+ recordNetworkResponse: function() {
19
+ return recordNetworkResponse;
17
20
  }
18
21
  });
19
22
  const _MessageHandler = require("../MessageHandler");
20
23
  const NETWORK_RESPONSE_STORAGE = new Map();
24
+ // Bounded so an unauthenticated client on `/inspector/network` cannot grow this
25
+ // map without limit. `Map` preserves insertion order, so the first key is the
26
+ // oldest entry — drop it on overflow (FIFO).
27
+ const MAX_NETWORK_RESPONSES = 512;
28
+ function recordNetworkResponse(requestId, info) {
29
+ NETWORK_RESPONSE_STORAGE.set(requestId, info);
30
+ if (NETWORK_RESPONSE_STORAGE.size > MAX_NETWORK_RESPONSES) {
31
+ const oldestKey = NETWORK_RESPONSE_STORAGE.keys().next().value;
32
+ if (oldestKey !== undefined) {
33
+ NETWORK_RESPONSE_STORAGE.delete(oldestKey);
34
+ }
35
+ }
36
+ }
21
37
  class NetworkResponseHandler extends _MessageHandler.MessageHandler {
22
38
  handleDeviceMessage(message) {
23
39
  if (message.method === 'Expo(Network.receivedResponseBody)') {
24
40
  const { requestId, ...requestInfo } = message.params;
25
- this.storage.set(requestId, requestInfo);
41
+ recordNetworkResponse(requestId, requestInfo);
26
42
  return true;
27
43
  }
28
44
  return false;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../src/start/server/metro/debugging/messageHandlers/NetworkResponse.ts"],"sourcesContent":["import type { Protocol } from 'devtools-protocol';\n\nimport { MessageHandler } from '../MessageHandler';\nimport type {\n CdpMessage,\n DeviceRequest,\n DebuggerRequest,\n DebuggerResponse,\n DeviceResponse,\n} from '../types';\n\n/**\n * The global network response storage, as a workaround for the network inspector.\n * @see createDebugMiddleware#createNetworkWebsocket\n */\nexport const NETWORK_RESPONSE_STORAGE = new Map<\n string,\n DebuggerResponse<NetworkGetResponseBody>['result']\n>();\n\nexport class NetworkResponseHandler extends MessageHandler {\n /** All known responses, mapped by request id */\n storage = NETWORK_RESPONSE_STORAGE;\n\n handleDeviceMessage(message: DeviceRequest<NetworkReceivedResponseBody>) {\n if (message.method === 'Expo(Network.receivedResponseBody)') {\n const { requestId, ...requestInfo } = message.params;\n this.storage.set(requestId, requestInfo);\n return true;\n }\n\n return false;\n }\n\n handleDebuggerMessage(message: DebuggerRequest<NetworkGetResponseBody>) {\n if (\n message.method === 'Network.getResponseBody' &&\n this.storage.has(message.params.requestId)\n ) {\n return this.sendToDebugger<DeviceResponse<NetworkGetResponseBody>>({\n id: message.id,\n result: this.storage.get(message.params.requestId)!,\n });\n }\n\n return false;\n }\n}\n\n/** Custom message to transfer the response body data to the proxy */\nexport type NetworkReceivedResponseBody = CdpMessage<\n 'Expo(Network.receivedResponseBody)',\n Protocol.Network.GetResponseBodyRequest & Protocol.Network.GetResponseBodyResponse,\n never\n>;\n\n/** @see https://chromedevtools.github.io/devtools-protocol/1-2/Network/#method-getResponseBody */\nexport type NetworkGetResponseBody = CdpMessage<\n 'Network.getResponseBody',\n Protocol.Network.GetResponseBodyRequest,\n Protocol.Network.GetResponseBodyResponse\n>;\n"],"names":["NETWORK_RESPONSE_STORAGE","NetworkResponseHandler","Map","MessageHandler","handleDeviceMessage","message","method","requestId","requestInfo","params","storage","set","handleDebuggerMessage","has","sendToDebugger","id","result","get"],"mappings":";;;;;;;;;;;IAeaA,wBAAwB;eAAxBA;;IAKAC,sBAAsB;eAAtBA;;;gCAlBkB;AAaxB,MAAMD,2BAA2B,IAAIE;AAKrC,MAAMD,+BAA+BE,8BAAc;IAIxDC,oBAAoBC,OAAmD,EAAE;QACvE,IAAIA,QAAQC,MAAM,KAAK,sCAAsC;YAC3D,MAAM,EAAEC,SAAS,EAAE,GAAGC,aAAa,GAAGH,QAAQI,MAAM;YACpD,IAAI,CAACC,OAAO,CAACC,GAAG,CAACJ,WAAWC;YAC5B,OAAO;QACT;QAEA,OAAO;IACT;IAEAI,sBAAsBP,OAAgD,EAAE;QACtE,IACEA,QAAQC,MAAM,KAAK,6BACnB,IAAI,CAACI,OAAO,CAACG,GAAG,CAACR,QAAQI,MAAM,CAACF,SAAS,GACzC;YACA,OAAO,IAAI,CAACO,cAAc,CAAyC;gBACjEC,IAAIV,QAAQU,EAAE;gBACdC,QAAQ,IAAI,CAACN,OAAO,CAACO,GAAG,CAACZ,QAAQI,MAAM,CAACF,SAAS;YACnD;QACF;QAEA,OAAO;IACT;;QA1BK,gBACL,8CAA8C,QAC9CG,UAAUV;;AAyBZ"}
1
+ {"version":3,"sources":["../../../../../../../src/start/server/metro/debugging/messageHandlers/NetworkResponse.ts"],"sourcesContent":["import type { Protocol } from 'devtools-protocol';\n\nimport { MessageHandler } from '../MessageHandler';\nimport type {\n CdpMessage,\n DeviceRequest,\n DebuggerRequest,\n DebuggerResponse,\n DeviceResponse,\n} from '../types';\n\n/**\n * The global network response storage, as a workaround for the network inspector.\n * @see createDebugMiddleware#createNetworkWebsocket\n */\nexport const NETWORK_RESPONSE_STORAGE = new Map<\n string,\n DebuggerResponse<NetworkGetResponseBody>['result']\n>();\n\n// Bounded so an unauthenticated client on `/inspector/network` cannot grow this\n// map without limit. `Map` preserves insertion order, so the first key is the\n// oldest entry — drop it on overflow (FIFO).\nconst MAX_NETWORK_RESPONSES = 512;\n\nexport function recordNetworkResponse(\n requestId: string,\n info: DebuggerResponse<NetworkGetResponseBody>['result']\n) {\n NETWORK_RESPONSE_STORAGE.set(requestId, info);\n if (NETWORK_RESPONSE_STORAGE.size > MAX_NETWORK_RESPONSES) {\n const oldestKey = NETWORK_RESPONSE_STORAGE.keys().next().value;\n if (oldestKey !== undefined) {\n NETWORK_RESPONSE_STORAGE.delete(oldestKey);\n }\n }\n}\n\nexport class NetworkResponseHandler extends MessageHandler {\n /** All known responses, mapped by request id */\n storage = NETWORK_RESPONSE_STORAGE;\n\n handleDeviceMessage(message: DeviceRequest<NetworkReceivedResponseBody>) {\n if (message.method === 'Expo(Network.receivedResponseBody)') {\n const { requestId, ...requestInfo } = message.params;\n recordNetworkResponse(requestId, requestInfo);\n return true;\n }\n\n return false;\n }\n\n handleDebuggerMessage(message: DebuggerRequest<NetworkGetResponseBody>) {\n if (\n message.method === 'Network.getResponseBody' &&\n this.storage.has(message.params.requestId)\n ) {\n return this.sendToDebugger<DeviceResponse<NetworkGetResponseBody>>({\n id: message.id,\n result: this.storage.get(message.params.requestId)!,\n });\n }\n\n return false;\n }\n}\n\n/** Custom message to transfer the response body data to the proxy */\nexport type NetworkReceivedResponseBody = CdpMessage<\n 'Expo(Network.receivedResponseBody)',\n Protocol.Network.GetResponseBodyRequest & Protocol.Network.GetResponseBodyResponse,\n never\n>;\n\n/** @see https://chromedevtools.github.io/devtools-protocol/1-2/Network/#method-getResponseBody */\nexport type NetworkGetResponseBody = CdpMessage<\n 'Network.getResponseBody',\n Protocol.Network.GetResponseBodyRequest,\n Protocol.Network.GetResponseBodyResponse\n>;\n"],"names":["NETWORK_RESPONSE_STORAGE","NetworkResponseHandler","recordNetworkResponse","Map","MAX_NETWORK_RESPONSES","requestId","info","set","size","oldestKey","keys","next","value","undefined","delete","MessageHandler","handleDeviceMessage","message","method","requestInfo","params","handleDebuggerMessage","storage","has","sendToDebugger","id","result","get"],"mappings":";;;;;;;;;;;IAeaA,wBAAwB;eAAxBA;;IAuBAC,sBAAsB;eAAtBA;;IAbGC,qBAAqB;eAArBA;;;gCAvBe;AAaxB,MAAMF,2BAA2B,IAAIG;AAK5C,gFAAgF;AAChF,8EAA8E;AAC9E,6CAA6C;AAC7C,MAAMC,wBAAwB;AAEvB,SAASF,sBACdG,SAAiB,EACjBC,IAAwD;IAExDN,yBAAyBO,GAAG,CAACF,WAAWC;IACxC,IAAIN,yBAAyBQ,IAAI,GAAGJ,uBAAuB;QACzD,MAAMK,YAAYT,yBAAyBU,IAAI,GAAGC,IAAI,GAAGC,KAAK;QAC9D,IAAIH,cAAcI,WAAW;YAC3Bb,yBAAyBc,MAAM,CAACL;QAClC;IACF;AACF;AAEO,MAAMR,+BAA+Bc,8BAAc;IAIxDC,oBAAoBC,OAAmD,EAAE;QACvE,IAAIA,QAAQC,MAAM,KAAK,sCAAsC;YAC3D,MAAM,EAAEb,SAAS,EAAE,GAAGc,aAAa,GAAGF,QAAQG,MAAM;YACpDlB,sBAAsBG,WAAWc;YACjC,OAAO;QACT;QAEA,OAAO;IACT;IAEAE,sBAAsBJ,OAAgD,EAAE;QACtE,IACEA,QAAQC,MAAM,KAAK,6BACnB,IAAI,CAACI,OAAO,CAACC,GAAG,CAACN,QAAQG,MAAM,CAACf,SAAS,GACzC;YACA,OAAO,IAAI,CAACmB,cAAc,CAAyC;gBACjEC,IAAIR,QAAQQ,EAAE;gBACdC,QAAQ,IAAI,CAACJ,OAAO,CAACK,GAAG,CAACV,QAAQG,MAAM,CAACf,SAAS;YACnD;QACF;QAEA,OAAO;IACT;;QA1BK,gBACL,8CAA8C,QAC9CiB,UAAUtB;;AAyBZ"}
@@ -25,6 +25,12 @@ function _ws() {
25
25
  const _createSocketBroadcaster = require("./utils/createSocketBroadcaster");
26
26
  const _createSocketMap = require("./utils/createSocketMap");
27
27
  const _socketMessages = require("./utils/socketMessages");
28
+ const _net = require("../../../../utils/net");
29
+ const debug = require('debug')('expo:metro:devserver:messageSocket');
30
+ const CLIENT_BROADCAST_ALLOWED_METHODS = new Set([
31
+ 'reload',
32
+ 'devMenu'
33
+ ]);
28
34
  function createMessagesSocket(options) {
29
35
  const clients = (0, _createSocketMap.createSocketMap)();
30
36
  const broadcast = (0, _createSocketBroadcaster.createBroadcaster)(clients.map);
@@ -33,6 +39,7 @@ function createMessagesSocket(options) {
33
39
  });
34
40
  server.on('connection', (socket, req)=>{
35
41
  const client = clients.registerSocket(socket);
42
+ const isTrustedClient = (0, _net.isLocalSocket)(req.socket) && (0, _net.isMatchingOrigin)(req, options.serverBaseUrl);
36
43
  // Assign the query parameters to the socket, used for `getpeers` requests
37
44
  // NOTE(cedric): this looks like a legacy feature, might be able to drop it
38
45
  if (req.url) {
@@ -44,7 +51,7 @@ function createMessagesSocket(options) {
44
51
  socket.on('close', client.terminate);
45
52
  socket.on('error', client.terminate);
46
53
  // Register message handler
47
- socket.on('message', createClientMessageHandler(socket, client.id, clients, broadcast));
54
+ socket.on('message', createClientMessageHandler(socket, client.id, clients, broadcast, isTrustedClient));
48
55
  });
49
56
  return {
50
57
  endpoint: '/message',
@@ -60,7 +67,7 @@ function createMessagesSocket(options) {
60
67
  }
61
68
  };
62
69
  }
63
- function createClientMessageHandler(socket, clientId, clients, broadcast) {
70
+ function createClientMessageHandler(socket, clientId, clients, broadcast, isTrustedClient) {
64
71
  function handleServerRequest(message) {
65
72
  // Ignore messages without identifiers, unable to link responses
66
73
  if (!message.id) return;
@@ -88,6 +95,10 @@ function createClientMessageHandler(socket, clientId, clients, broadcast) {
88
95
  if (!message) return;
89
96
  // Handle broadcast messages
90
97
  if (messageIsBroadcast(message)) {
98
+ if (!isTrustedClient || !CLIENT_BROADCAST_ALLOWED_METHODS.has(message.method)) {
99
+ debug(`Refused broadcast message from untrusted client (method: ${message.method})`);
100
+ return;
101
+ }
91
102
  return broadcast(null, data.toString());
92
103
  }
93
104
  // Handle incoming requests from clients
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../src/start/server/metro/dev-server/createMessageSocket.ts"],"sourcesContent":["import { parse } from 'node:url';\nimport { type WebSocket, WebSocketServer, type RawData as WebSocketRawData } from 'ws';\n\nimport { createBroadcaster } from './utils/createSocketBroadcaster';\nimport { createSocketMap, type SocketId } from './utils/createSocketMap';\nimport { parseRawMessage, serializeMessage } from './utils/socketMessages';\n\ntype MessageSocketOptions = {\n logger: {\n warn: (message: string) => any;\n };\n};\n\n/**\n * Client \"command\" server that dispatches basic commands to connected clients.\n * This basic client to client communication, reload, or open dev menu cli commands.\n */\nexport function createMessagesSocket(options: MessageSocketOptions) {\n const clients = createSocketMap();\n const broadcast = createBroadcaster(clients.map);\n\n const server = new WebSocketServer({ noServer: true });\n\n server.on('connection', (socket, req) => {\n const client = clients.registerSocket(socket);\n\n // Assign the query parameters to the socket, used for `getpeers` requests\n // NOTE(cedric): this looks like a legacy feature, might be able to drop it\n if (req.url) {\n Object.defineProperty(socket, '_upgradeQuery', {\n value: parse(req.url).query,\n });\n }\n\n // Register disconnect handlers\n socket.on('close', client.terminate);\n socket.on('error', client.terminate);\n // Register message handler\n socket.on('message', createClientMessageHandler(socket, client.id, clients, broadcast));\n });\n\n return {\n endpoint: '/message' as const,\n server,\n broadcast: (method: BroadcastMessage['method'], params?: BroadcastMessage['params']) => {\n if (clients.map.size === 0) {\n return options.logger.warn(\n `No apps connected. Sending \"${method}\" to all React Native apps failed. Make sure your app is running in the simulator or on a phone connected via USB.`\n );\n }\n\n broadcast(null, serializeMessage({ method, params }));\n },\n };\n}\n\nfunction createClientMessageHandler(\n socket: WebSocket,\n clientId: SocketId,\n clients: ReturnType<typeof createSocketMap>,\n broadcast: ReturnType<typeof createBroadcaster>\n) {\n function handleServerRequest(message: RequestMessage) {\n // Ignore messages without identifiers, unable to link responses\n if (!message.id) return;\n\n if (message.method === 'getid') {\n return socket.send(serializeMessage({ id: message.id, result: clientId }));\n }\n\n if (message.method === 'getpeers') {\n const peers: Record<string, any> = {};\n clients.map.forEach((peerSocket, peerSocketId) => {\n if (peerSocketId !== clientId) {\n peers[peerSocketId] = '_upgradeQuery' in peerSocket ? peerSocket._upgradeQuery : {};\n }\n });\n return socket.send(serializeMessage({ id: message.id, result: peers }));\n }\n }\n\n return (data: WebSocketRawData, isBinary: boolean) => {\n const message = parseRawMessage<IncomingMessage>(data, isBinary);\n if (!message) return;\n\n // Handle broadcast messages\n if (messageIsBroadcast(message)) {\n return broadcast(null, data.toString());\n }\n\n // Handle incoming requests from clients\n if (messageIsRequest(message)) {\n if (message.target === 'server') {\n return handleServerRequest(message);\n }\n\n return clients.findSocket(message.target)?.send(\n serializeMessage({\n method: message.method,\n params: message.params,\n id: !message.id\n ? undefined\n : {\n requestId: message.id,\n clientId,\n },\n })\n );\n }\n\n // Handle incoming responses\n if (messageIsResponse(message)) {\n return clients.findSocket(message.id.clientId)?.send(\n serializeMessage({\n id: message.id.requestId,\n result: message.result,\n error: message.error,\n })\n );\n }\n };\n}\n\ntype MessageId = {\n requestId: string;\n clientId: SocketId;\n};\n\ntype IncomingMessage = BroadcastMessage | RequestMessage | ResponseMessage;\n\ntype BroadcastMessage = {\n method: string;\n params?: Record<string, any>;\n};\n\ntype RequestMessage = {\n method: string;\n params?: Record<string, any>;\n target: string;\n id?: string;\n};\n\ntype ResponseMessage = {\n result?: any;\n error?: Error;\n id: MessageId;\n};\n\nfunction messageIsBroadcast(message: IncomingMessage): message is BroadcastMessage {\n return (\n 'method' in message &&\n typeof message.method === 'string' &&\n (!('id' in message) || message.id === undefined) &&\n (!('target' in message) || message.target === undefined)\n );\n}\n\nfunction messageIsRequest(message: IncomingMessage): message is RequestMessage {\n return (\n 'method' in message &&\n typeof message.method === 'string' &&\n 'target' in message &&\n typeof message.target === 'string'\n );\n}\n\nfunction messageIsResponse(message: IncomingMessage): message is ResponseMessage {\n return (\n 'id' in message &&\n typeof message.id === 'object' &&\n typeof message.id.requestId !== 'undefined' &&\n typeof message.id.clientId === 'string' &&\n (('result' in message && !!message.result) || ('error' in message && !!message.error))\n );\n}\n"],"names":["createMessagesSocket","options","clients","createSocketMap","broadcast","createBroadcaster","map","server","WebSocketServer","noServer","on","socket","req","client","registerSocket","url","Object","defineProperty","value","parse","query","terminate","createClientMessageHandler","id","endpoint","method","params","size","logger","warn","serializeMessage","clientId","handleServerRequest","message","send","result","peers","forEach","peerSocket","peerSocketId","_upgradeQuery","data","isBinary","parseRawMessage","messageIsBroadcast","toString","messageIsRequest","target","findSocket","undefined","requestId","messageIsResponse","error"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../src/start/server/metro/dev-server/createMessageSocket.ts"],"sourcesContent":["import { parse } from 'node:url';\nimport { type WebSocket, WebSocketServer, type RawData as WebSocketRawData } from 'ws';\n\nimport { createBroadcaster } from './utils/createSocketBroadcaster';\nimport { createSocketMap, type SocketId } from './utils/createSocketMap';\nimport { parseRawMessage, serializeMessage } from './utils/socketMessages';\nimport { isLocalSocket, isMatchingOrigin } from '../../../../utils/net';\n\ntype MessageSocketOptions = {\n logger: {\n warn: (message: string) => any;\n };\n serverBaseUrl: string;\n};\n\nconst debug = require('debug')('expo:metro:devserver:messageSocket') as typeof console.log;\n\nconst CLIENT_BROADCAST_ALLOWED_METHODS = new Set(['reload', 'devMenu']);\n\n/**\n * Client \"command\" server that dispatches basic commands to connected clients.\n * This basic client to client communication, reload, or open dev menu cli commands.\n */\nexport function createMessagesSocket(options: MessageSocketOptions) {\n const clients = createSocketMap();\n const broadcast = createBroadcaster(clients.map);\n\n const server = new WebSocketServer({ noServer: true });\n\n server.on('connection', (socket, req) => {\n const client = clients.registerSocket(socket);\n const isTrustedClient =\n isLocalSocket(req.socket) && isMatchingOrigin(req, options.serverBaseUrl);\n\n // Assign the query parameters to the socket, used for `getpeers` requests\n // NOTE(cedric): this looks like a legacy feature, might be able to drop it\n if (req.url) {\n Object.defineProperty(socket, '_upgradeQuery', {\n value: parse(req.url).query,\n });\n }\n\n // Register disconnect handlers\n socket.on('close', client.terminate);\n socket.on('error', client.terminate);\n // Register message handler\n socket.on(\n 'message',\n createClientMessageHandler(socket, client.id, clients, broadcast, isTrustedClient)\n );\n });\n\n return {\n endpoint: '/message' as const,\n server,\n broadcast: (method: BroadcastMessage['method'], params?: BroadcastMessage['params']) => {\n if (clients.map.size === 0) {\n return options.logger.warn(\n `No apps connected. Sending \"${method}\" to all React Native apps failed. Make sure your app is running in the simulator or on a phone connected via USB.`\n );\n }\n\n broadcast(null, serializeMessage({ method, params }));\n },\n };\n}\n\nfunction createClientMessageHandler(\n socket: WebSocket,\n clientId: SocketId,\n clients: ReturnType<typeof createSocketMap>,\n broadcast: ReturnType<typeof createBroadcaster>,\n isTrustedClient: boolean\n) {\n function handleServerRequest(message: RequestMessage) {\n // Ignore messages without identifiers, unable to link responses\n if (!message.id) return;\n\n if (message.method === 'getid') {\n return socket.send(serializeMessage({ id: message.id, result: clientId }));\n }\n\n if (message.method === 'getpeers') {\n const peers: Record<string, any> = {};\n clients.map.forEach((peerSocket, peerSocketId) => {\n if (peerSocketId !== clientId) {\n peers[peerSocketId] = '_upgradeQuery' in peerSocket ? peerSocket._upgradeQuery : {};\n }\n });\n return socket.send(serializeMessage({ id: message.id, result: peers }));\n }\n }\n\n return (data: WebSocketRawData, isBinary: boolean) => {\n const message = parseRawMessage<IncomingClientMessage>(data, isBinary);\n if (!message) return;\n\n // Handle broadcast messages\n if (messageIsBroadcast(message)) {\n if (!isTrustedClient || !CLIENT_BROADCAST_ALLOWED_METHODS.has(message.method)) {\n debug(`Refused broadcast message from untrusted client (method: ${message.method})`);\n return;\n }\n return broadcast(null, data.toString());\n }\n\n // Handle incoming requests from clients\n if (messageIsRequest(message)) {\n if (message.target === 'server') {\n return handleServerRequest(message);\n }\n\n return clients.findSocket(message.target)?.send(\n serializeMessage({\n method: message.method,\n params: message.params,\n id: !message.id\n ? undefined\n : {\n requestId: message.id,\n clientId,\n },\n })\n );\n }\n\n // Handle incoming responses\n if (messageIsResponse(message)) {\n return clients.findSocket(message.id.clientId)?.send(\n serializeMessage({\n id: message.id.requestId,\n result: message.result,\n error: message.error,\n })\n );\n }\n };\n}\n\ntype MessageId = {\n requestId: string;\n clientId: SocketId;\n};\n\ntype IncomingClientMessage = BroadcastMessage | RequestMessage | ResponseMessage;\n\ntype BroadcastMessage = {\n method: string;\n params?: Record<string, any>;\n};\n\ntype RequestMessage = {\n method: string;\n params?: Record<string, any>;\n target: string;\n id?: string;\n};\n\ntype ResponseMessage = {\n result?: any;\n error?: Error;\n id: MessageId;\n};\n\nfunction messageIsBroadcast(message: IncomingClientMessage): message is BroadcastMessage {\n return (\n 'method' in message &&\n typeof message.method === 'string' &&\n (!('id' in message) || message.id === undefined) &&\n (!('target' in message) || message.target === undefined)\n );\n}\n\nfunction messageIsRequest(message: IncomingClientMessage): message is RequestMessage {\n return (\n 'method' in message &&\n typeof message.method === 'string' &&\n 'target' in message &&\n typeof message.target === 'string'\n );\n}\n\nfunction messageIsResponse(message: IncomingClientMessage): message is ResponseMessage {\n return (\n 'id' in message &&\n typeof message.id === 'object' &&\n typeof message.id.requestId !== 'undefined' &&\n typeof message.id.clientId === 'string' &&\n (('result' in message && !!message.result) || ('error' in message && !!message.error))\n );\n}\n"],"names":["createMessagesSocket","debug","require","CLIENT_BROADCAST_ALLOWED_METHODS","Set","options","clients","createSocketMap","broadcast","createBroadcaster","map","server","WebSocketServer","noServer","on","socket","req","client","registerSocket","isTrustedClient","isLocalSocket","isMatchingOrigin","serverBaseUrl","url","Object","defineProperty","value","parse","query","terminate","createClientMessageHandler","id","endpoint","method","params","size","logger","warn","serializeMessage","clientId","handleServerRequest","message","send","result","peers","forEach","peerSocket","peerSocketId","_upgradeQuery","data","isBinary","parseRawMessage","messageIsBroadcast","has","toString","messageIsRequest","target","findSocket","undefined","requestId","messageIsResponse","error"],"mappings":"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"}
@@ -8,6 +8,13 @@ Object.defineProperty(exports, "createMetroMiddleware", {
8
8
  return createMetroMiddleware;
9
9
  }
10
10
  });
11
+ function _paths() {
12
+ const data = require("@expo/config/paths");
13
+ _paths = function() {
14
+ return data;
15
+ };
16
+ return data;
17
+ }
11
18
  function _connect() {
12
19
  const data = /*#__PURE__*/ _interop_require_default(require("connect"));
13
20
  _connect = function() {
@@ -15,23 +22,40 @@ function _connect() {
15
22
  };
16
23
  return data;
17
24
  }
25
+ function _nodefs() {
26
+ const data = /*#__PURE__*/ _interop_require_default(require("node:fs"));
27
+ _nodefs = function() {
28
+ return data;
29
+ };
30
+ return data;
31
+ }
32
+ function _nodepath() {
33
+ const data = /*#__PURE__*/ _interop_require_default(require("node:path"));
34
+ _nodepath = function() {
35
+ return data;
36
+ };
37
+ return data;
38
+ }
18
39
  const _compression = require("./compression");
19
40
  const _createEventSocket = require("./createEventSocket");
20
41
  const _createMessageSocket = require("./createMessageSocket");
21
42
  const _log = require("../../../../log");
43
+ const _dir = require("../../../../utils/dir");
22
44
  const _editor = require("../../../../utils/editor");
45
+ const _net = require("../../../../utils/net");
23
46
  function _interop_require_default(obj) {
24
47
  return obj && obj.__esModule ? obj : {
25
48
  default: obj
26
49
  };
27
50
  }
28
- function createMetroMiddleware(metroConfig) {
51
+ function createMetroMiddleware(metroConfig, options) {
29
52
  const messages = (0, _createMessageSocket.createMessagesSocket)({
30
- logger: _log.Log
53
+ logger: _log.Log,
54
+ serverBaseUrl: options.serverBaseUrl
31
55
  });
32
56
  const events = (0, _createEventSocket.createEventsSocket)(messages);
33
57
  const middleware = (0, _connect().default)().use(noCacheMiddleware).use(_compression.compression)// Support opening stack frames from clients directly in the editor
34
- .use('/open-stack-frame', rawBodyMiddleware).use('/open-stack-frame', metroOpenStackFrameMiddleware)// Support the symbolication endpoint of Metro
58
+ .use('/open-stack-frame', rawBodyMiddleware).use('/open-stack-frame', createMetroOpenStackFrameMiddleware(metroConfig))// Support the symbolication endpoint of Metro
35
59
  // See: https://github.com/facebook/metro/blob/a792d85ffde3c21c3fbf64ac9404ab0afe5ff957/packages/metro/src/Server.js#L1266
36
60
  .use('/symbolicate', rawBodyMiddleware)// Support status check to detect if the packager needs to be started from the native side
37
61
  .use('/status', createMetroStatusMiddleware(metroConfig));
@@ -45,7 +69,7 @@ function createMetroMiddleware(metroConfig) {
45
69
  }
46
70
  };
47
71
  }
48
- const noCacheMiddleware = (req, res, next)=>{
72
+ const noCacheMiddleware = (_req, res, next)=>{
49
73
  res.setHeader('Surrogate-Control', 'no-store');
50
74
  res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
51
75
  res.setHeader('Pragma', 'no-cache');
@@ -59,22 +83,68 @@ const rawBodyMiddleware = (req, _res, next)=>{
59
83
  reqWithBody.on('data', (chunk)=>reqWithBody.rawBody += chunk);
60
84
  reqWithBody.on('end', next);
61
85
  };
62
- const metroOpenStackFrameMiddleware = (req, res, next)=>{
63
- // Only accept POST requests
64
- if (req.method !== 'POST') return next();
65
- // Only handle requests with a raw body
66
- if (!('rawBody' in req) || !req.rawBody) {
67
- res.statusCode = 406;
68
- return res.end('Open stack frame requires the JSON stack frame as request body');
69
- }
70
- const frame = JSON.parse(req.rawBody);
71
- (0, _editor.openInEditorAsync)(frame.file, frame.lineNumber).finally(()=>res.end('OK'));
72
- };
73
86
  function createMetroStatusMiddleware(metroConfig) {
74
87
  return (_req, res)=>{
75
88
  res.setHeader('X-React-Native-Project-Root', encodeURI(metroConfig.projectRoot));
76
89
  res.end('packager-status:running');
77
90
  };
78
91
  }
92
+ function createMetroOpenStackFrameMiddleware(metroConfig) {
93
+ return async (req, res, next)=>{
94
+ if (req.method !== 'POST') {
95
+ return next();
96
+ }
97
+ if (!('rawBody' in req) || !req.rawBody) {
98
+ res.statusCode = 406;
99
+ return res.end('Open stack frame requires the JSON stack frame as request body');
100
+ }
101
+ let frame;
102
+ try {
103
+ const json = JSON.parse(req.rawBody);
104
+ if (typeof json === 'object' && json != null && typeof json.file === 'string') {
105
+ frame = {
106
+ file: json.file,
107
+ lineNumber: typeof json.lineNumber === 'number' && Number.isSafeInteger(json.lineNumber) ? json.lineNumber : undefined
108
+ };
109
+ }
110
+ } catch {}
111
+ if (!frame) {
112
+ res.statusCode = 400;
113
+ return res.end('Open stack frame requires the JSON stack frame as request body');
114
+ }
115
+ const root = (0, _paths().getMetroServerRoot)(metroConfig.projectRoot);
116
+ const file = await ensureFileInRootDirectory(root, frame.file);
117
+ if (!file) {
118
+ res.statusCode = 400;
119
+ return res.end('Open stack frame requires target file to be in server root');
120
+ }
121
+ if ((0, _net.shouldThrottleRemoteDevCall)()) {
122
+ res.statusCode = 429;
123
+ return res.end();
124
+ }
125
+ try {
126
+ await (0, _editor.openInEditorAsync)(file, frame.lineNumber);
127
+ return res.end('OK');
128
+ } catch {
129
+ res.statusCode = 5006;
130
+ return res.end('Open stack frame failed to open local editor');
131
+ }
132
+ };
133
+ }
134
+ const ensureFileInRootDirectory = async (root, file)=>{
135
+ try {
136
+ file = _nodepath().default.resolve(root, file);
137
+ file = await _nodefs().default.promises.realpath(file);
138
+ // Cannot be accessed using Metro's server API, we need to move the file
139
+ // into the project root and try again.
140
+ if ((0, _dir.isPathInside)(file, root)) {
141
+ return file;
142
+ } else {
143
+ return null;
144
+ }
145
+ } catch {
146
+ return null;
147
+ }
148
+ };
79
149
 
80
150
  //# sourceMappingURL=createMetroMiddleware.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../src/start/server/metro/dev-server/createMetroMiddleware.ts"],"sourcesContent":["import type { MetroConfig } from '@expo/metro/metro';\nimport connect from 'connect';\n\nimport { compression } from './compression';\nimport { createEventsSocket } from './createEventSocket';\nimport { createMessagesSocket } from './createMessageSocket';\nimport { Log } from '../../../../log';\nimport { openInEditorAsync } from '../../../../utils/editor';\n\nexport function createMetroMiddleware(metroConfig: Pick<MetroConfig, 'projectRoot'>) {\n const messages = createMessagesSocket({ logger: Log });\n const events = createEventsSocket(messages);\n\n const middleware = connect()\n .use(noCacheMiddleware)\n .use(compression)\n // Support opening stack frames from clients directly in the editor\n .use('/open-stack-frame', rawBodyMiddleware)\n .use('/open-stack-frame', metroOpenStackFrameMiddleware)\n // Support the symbolication endpoint of Metro\n // See: https://github.com/facebook/metro/blob/a792d85ffde3c21c3fbf64ac9404ab0afe5ff957/packages/metro/src/Server.js#L1266\n .use('/symbolicate', rawBodyMiddleware)\n // Support status check to detect if the packager needs to be started from the native side\n .use('/status', createMetroStatusMiddleware(metroConfig));\n\n return {\n middleware,\n messagesSocket: messages,\n eventsSocket: events,\n websocketEndpoints: {\n [messages.endpoint]: messages.server,\n [events.endpoint]: events.server,\n },\n };\n}\n\nconst noCacheMiddleware: connect.NextHandleFunction = (req, res, next) => {\n res.setHeader('Surrogate-Control', 'no-store');\n res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');\n res.setHeader('Pragma', 'no-cache');\n res.setHeader('Expires', '0');\n next();\n};\n\nconst rawBodyMiddleware: connect.NextHandleFunction = (req, _res, next) => {\n const reqWithBody = req as typeof req & { rawBody: string };\n reqWithBody.setEncoding('utf8');\n reqWithBody.rawBody = '';\n reqWithBody.on('data', (chunk) => (reqWithBody.rawBody += chunk));\n reqWithBody.on('end', next);\n};\n\nconst metroOpenStackFrameMiddleware: connect.NextHandleFunction = (req, res, next) => {\n // Only accept POST requests\n if (req.method !== 'POST') return next();\n // Only handle requests with a raw body\n if (!('rawBody' in req) || !req.rawBody) {\n res.statusCode = 406;\n return res.end('Open stack frame requires the JSON stack frame as request body');\n }\n\n const frame = JSON.parse(req.rawBody as string);\n openInEditorAsync(frame.file, frame.lineNumber).finally(() => res.end('OK'));\n};\n\nfunction createMetroStatusMiddleware(\n metroConfig: Pick<MetroConfig, 'projectRoot'>\n): connect.NextHandleFunction {\n return (_req, res) => {\n res.setHeader('X-React-Native-Project-Root', encodeURI(metroConfig.projectRoot!));\n res.end('packager-status:running');\n };\n}\n"],"names":["createMetroMiddleware","metroConfig","messages","createMessagesSocket","logger","Log","events","createEventsSocket","middleware","connect","use","noCacheMiddleware","compression","rawBodyMiddleware","metroOpenStackFrameMiddleware","createMetroStatusMiddleware","messagesSocket","eventsSocket","websocketEndpoints","endpoint","server","req","res","next","setHeader","_res","reqWithBody","setEncoding","rawBody","on","chunk","method","statusCode","end","frame","JSON","parse","openInEditorAsync","file","lineNumber","finally","_req","encodeURI","projectRoot"],"mappings":";;;;+BASgBA;;;eAAAA;;;;gEARI;;;;;;6BAEQ;mCACO;qCACE;qBACjB;wBACc;;;;;;AAE3B,SAASA,sBAAsBC,WAA6C;IACjF,MAAMC,WAAWC,IAAAA,yCAAoB,EAAC;QAAEC,QAAQC,QAAG;IAAC;IACpD,MAAMC,SAASC,IAAAA,qCAAkB,EAACL;IAElC,MAAMM,aAAaC,IAAAA,kBAAO,IACvBC,GAAG,CAACC,mBACJD,GAAG,CAACE,wBAAW,CAChB,mEAAmE;KAClEF,GAAG,CAAC,qBAAqBG,mBACzBH,GAAG,CAAC,qBAAqBI,8BAC1B,8CAA8C;IAC9C,0HAA0H;KACzHJ,GAAG,CAAC,gBAAgBG,kBACrB,0FAA0F;KACzFH,GAAG,CAAC,WAAWK,4BAA4Bd;IAE9C,OAAO;QACLO;QACAQ,gBAAgBd;QAChBe,cAAcX;QACdY,oBAAoB;YAClB,CAAChB,SAASiB,QAAQ,CAAC,EAAEjB,SAASkB,MAAM;YACpC,CAACd,OAAOa,QAAQ,CAAC,EAAEb,OAAOc,MAAM;QAClC;IACF;AACF;AAEA,MAAMT,oBAAgD,CAACU,KAAKC,KAAKC;IAC/DD,IAAIE,SAAS,CAAC,qBAAqB;IACnCF,IAAIE,SAAS,CAAC,iBAAiB;IAC/BF,IAAIE,SAAS,CAAC,UAAU;IACxBF,IAAIE,SAAS,CAAC,WAAW;IACzBD;AACF;AAEA,MAAMV,oBAAgD,CAACQ,KAAKI,MAAMF;IAChE,MAAMG,cAAcL;IACpBK,YAAYC,WAAW,CAAC;IACxBD,YAAYE,OAAO,GAAG;IACtBF,YAAYG,EAAE,CAAC,QAAQ,CAACC,QAAWJ,YAAYE,OAAO,IAAIE;IAC1DJ,YAAYG,EAAE,CAAC,OAAON;AACxB;AAEA,MAAMT,gCAA4D,CAACO,KAAKC,KAAKC;IAC3E,4BAA4B;IAC5B,IAAIF,IAAIU,MAAM,KAAK,QAAQ,OAAOR;IAClC,uCAAuC;IACvC,IAAI,CAAE,CAAA,aAAaF,GAAE,KAAM,CAACA,IAAIO,OAAO,EAAE;QACvCN,IAAIU,UAAU,GAAG;QACjB,OAAOV,IAAIW,GAAG,CAAC;IACjB;IAEA,MAAMC,QAAQC,KAAKC,KAAK,CAACf,IAAIO,OAAO;IACpCS,IAAAA,yBAAiB,EAACH,MAAMI,IAAI,EAAEJ,MAAMK,UAAU,EAAEC,OAAO,CAAC,IAAMlB,IAAIW,GAAG,CAAC;AACxE;AAEA,SAASlB,4BACPd,WAA6C;IAE7C,OAAO,CAACwC,MAAMnB;QACZA,IAAIE,SAAS,CAAC,+BAA+BkB,UAAUzC,YAAY0C,WAAW;QAC9ErB,IAAIW,GAAG,CAAC;IACV;AACF"}
1
+ {"version":3,"sources":["../../../../../../src/start/server/metro/dev-server/createMetroMiddleware.ts"],"sourcesContent":["import { getMetroServerRoot } from '@expo/config/paths';\nimport type { MetroConfig } from '@expo/metro/metro';\nimport connect from 'connect';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { compression } from './compression';\nimport { createEventsSocket } from './createEventSocket';\nimport { createMessagesSocket } from './createMessageSocket';\nimport { Log } from '../../../../log';\nimport { isPathInside } from '../../../../utils/dir';\nimport { openInEditorAsync } from '../../../../utils/editor';\nimport { shouldThrottleRemoteDevCall } from '../../../../utils/net';\n\ninterface MetroMiddlewareOptions {\n serverBaseUrl: string;\n}\n\ninterface StackFrame {\n file: string;\n lineNumber?: number | undefined;\n}\n\nexport function createMetroMiddleware(\n metroConfig: Pick<MetroConfig, 'projectRoot'>,\n options: MetroMiddlewareOptions\n) {\n const messages = createMessagesSocket({ logger: Log, serverBaseUrl: options.serverBaseUrl });\n const events = createEventsSocket(messages);\n\n const middleware = connect()\n .use(noCacheMiddleware)\n .use(compression)\n // Support opening stack frames from clients directly in the editor\n .use('/open-stack-frame', rawBodyMiddleware)\n .use('/open-stack-frame', createMetroOpenStackFrameMiddleware(metroConfig))\n // Support the symbolication endpoint of Metro\n // See: https://github.com/facebook/metro/blob/a792d85ffde3c21c3fbf64ac9404ab0afe5ff957/packages/metro/src/Server.js#L1266\n .use('/symbolicate', rawBodyMiddleware)\n // Support status check to detect if the packager needs to be started from the native side\n .use('/status', createMetroStatusMiddleware(metroConfig));\n\n return {\n middleware,\n messagesSocket: messages,\n eventsSocket: events,\n websocketEndpoints: {\n [messages.endpoint]: messages.server,\n [events.endpoint]: events.server,\n },\n };\n}\n\nconst noCacheMiddleware: connect.NextHandleFunction = (_req, res, next) => {\n res.setHeader('Surrogate-Control', 'no-store');\n res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');\n res.setHeader('Pragma', 'no-cache');\n res.setHeader('Expires', '0');\n next();\n};\n\nconst rawBodyMiddleware: connect.NextHandleFunction = (req, _res, next) => {\n const reqWithBody = req as typeof req & { rawBody: string };\n reqWithBody.setEncoding('utf8');\n reqWithBody.rawBody = '';\n reqWithBody.on('data', (chunk) => (reqWithBody.rawBody += chunk));\n reqWithBody.on('end', next);\n};\n\nfunction createMetroStatusMiddleware(\n metroConfig: Pick<MetroConfig, 'projectRoot'>\n): connect.NextHandleFunction {\n return (_req, res) => {\n res.setHeader('X-React-Native-Project-Root', encodeURI(metroConfig.projectRoot!));\n res.end('packager-status:running');\n };\n}\n\nfunction createMetroOpenStackFrameMiddleware(\n metroConfig: Pick<MetroConfig, 'projectRoot'>\n): connect.NextHandleFunction {\n return async (req, res, next) => {\n if (req.method !== 'POST') {\n return next();\n }\n\n if (!('rawBody' in req) || !req.rawBody) {\n res.statusCode = 406;\n return res.end('Open stack frame requires the JSON stack frame as request body');\n }\n\n let frame: StackFrame | undefined;\n try {\n const json = JSON.parse(req.rawBody as string);\n if (typeof json === 'object' && json != null && typeof json.file === 'string') {\n frame = {\n file: json.file,\n lineNumber:\n typeof json.lineNumber === 'number' && Number.isSafeInteger(json.lineNumber)\n ? json.lineNumber\n : undefined,\n };\n }\n } catch {}\n if (!frame) {\n res.statusCode = 400;\n return res.end('Open stack frame requires the JSON stack frame as request body');\n }\n\n const root = getMetroServerRoot(metroConfig.projectRoot!);\n const file = await ensureFileInRootDirectory(root, frame.file);\n if (!file) {\n res.statusCode = 400;\n return res.end('Open stack frame requires target file to be in server root');\n }\n\n if (shouldThrottleRemoteDevCall()) {\n res.statusCode = 429;\n return res.end();\n }\n\n try {\n await openInEditorAsync(file, frame.lineNumber);\n return res.end('OK');\n } catch {\n res.statusCode = 5006;\n return res.end('Open stack frame failed to open local editor');\n }\n };\n}\n\nconst ensureFileInRootDirectory = async (root: string, file: string): Promise<string | null> => {\n try {\n file = path.resolve(root, file);\n file = await fs.promises.realpath(file);\n // Cannot be accessed using Metro's server API, we need to move the file\n // into the project root and try again.\n if (isPathInside(file, root)) {\n return file;\n } else {\n return null;\n }\n } catch {\n return null;\n }\n};\n"],"names":["createMetroMiddleware","metroConfig","options","messages","createMessagesSocket","logger","Log","serverBaseUrl","events","createEventsSocket","middleware","connect","use","noCacheMiddleware","compression","rawBodyMiddleware","createMetroOpenStackFrameMiddleware","createMetroStatusMiddleware","messagesSocket","eventsSocket","websocketEndpoints","endpoint","server","_req","res","next","setHeader","req","_res","reqWithBody","setEncoding","rawBody","on","chunk","encodeURI","projectRoot","end","method","statusCode","frame","json","JSON","parse","file","lineNumber","Number","isSafeInteger","undefined","root","getMetroServerRoot","ensureFileInRootDirectory","shouldThrottleRemoteDevCall","openInEditorAsync","path","resolve","fs","promises","realpath","isPathInside"],"mappings":"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"}
@@ -232,13 +232,15 @@ async function instantiateMetroAsync(metroBundler, options, { isExporting, exp =
232
232
  return metro.getBundler().getBundler();
233
233
  }
234
234
  });
235
- // Create the core middleware stack for Metro, including websocket listeners
236
- const { middleware, messagesSocket, eventsSocket, websocketEndpoints } = (0, _createMetroMiddleware.createMetroMiddleware)(metroConfig);
237
235
  // Get local URL to Metro bundler server (typically configured as 127.0.0.1:8081)
238
236
  const serverBaseUrl = metroBundler.getUrlCreator().constructUrl({
239
237
  scheme: 'http',
240
238
  hostType: 'localhost'
241
239
  });
240
+ // Create the core middleware stack for Metro, including websocket listeners
241
+ const { middleware, messagesSocket, eventsSocket, websocketEndpoints } = (0, _createMetroMiddleware.createMetroMiddleware)(metroConfig, {
242
+ serverBaseUrl
243
+ });
242
244
  if (!isExporting) {
243
245
  // Enable correct CORS headers for Expo Router features
244
246
  (0, _mutations.prependMiddleware)(middleware, (0, _CorsMiddleware.createCorsMiddleware)(exp));
@@ -250,7 +252,9 @@ async function instantiateMetroAsync(metroBundler, options, { isExporting, exp =
250
252
  });
251
253
  Object.assign(websocketEndpoints, debugWebsocketEndpoints);
252
254
  middleware.use(debugMiddleware);
253
- middleware.use('/_expo/debugger', (0, _createJsInspectorMiddleware.createJsInspectorMiddleware)());
255
+ middleware.use('/_expo/debugger', (0, _createJsInspectorMiddleware.createJsInspectorMiddleware)({
256
+ serverBaseUrl
257
+ }));
254
258
  // TODO(cedric): `enhanceMiddleware` is deprecated, but is currently used to unify the middleware stacks
255
259
  // See: https://github.com/facebook/metro/commit/22e85fde85ec454792a1b70eba4253747a2587a9
256
260
  // See: https://github.com/facebook/metro/commit/d0d554381f119bb80ab09dbd6a1d310b54737e52