@react-native/dev-middleware 0.73.0 → 0.73.3

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 (49) hide show
  1. package/README.md +77 -3
  2. package/dist/createDevMiddleware.d.ts +53 -0
  3. package/dist/createDevMiddleware.js +53 -6
  4. package/dist/createDevMiddleware.js.flow +44 -15
  5. package/dist/index.d.ts +12 -0
  6. package/dist/index.flow.d.ts +14 -0
  7. package/dist/index.flow.js.flow +5 -2
  8. package/dist/index.js +14 -1
  9. package/dist/index.js.flow +1 -9
  10. package/dist/inspector-proxy/Device.d.ts +90 -0
  11. package/dist/inspector-proxy/Device.js +708 -0
  12. package/dist/inspector-proxy/Device.js.flow +97 -0
  13. package/dist/inspector-proxy/DeviceEventReporter.d.ts +53 -0
  14. package/dist/inspector-proxy/DeviceEventReporter.js +162 -0
  15. package/dist/inspector-proxy/DeviceEventReporter.js.flow +65 -0
  16. package/dist/inspector-proxy/InspectorProxy.d.ts +61 -0
  17. package/dist/inspector-proxy/InspectorProxy.js +212 -0
  18. package/dist/inspector-proxy/InspectorProxy.js.flow +65 -0
  19. package/dist/inspector-proxy/types.d.ts +78 -0
  20. package/dist/inspector-proxy/types.js +1 -0
  21. package/dist/inspector-proxy/types.js.flow +135 -0
  22. package/dist/middleware/openDebuggerMiddleware.d.ts +37 -0
  23. package/dist/middleware/openDebuggerMiddleware.js +78 -24
  24. package/dist/middleware/openDebuggerMiddleware.js.flow +15 -64
  25. package/dist/types/BrowserLauncher.d.ts +27 -0
  26. package/dist/types/BrowserLauncher.js +1 -0
  27. package/dist/types/BrowserLauncher.js.flow +31 -0
  28. package/dist/types/EventReporter.d.ts +69 -0
  29. package/dist/types/EventReporter.js +1 -0
  30. package/dist/types/EventReporter.js.flow +81 -0
  31. package/dist/types/Experiments.d.ts +25 -0
  32. package/dist/types/Experiments.js +1 -0
  33. package/dist/types/Experiments.js.flow +27 -0
  34. package/dist/types/Logger.d.ts +16 -0
  35. package/dist/utils/DefaultBrowserLauncher.d.ts +18 -0
  36. package/dist/utils/DefaultBrowserLauncher.js +77 -0
  37. package/dist/utils/DefaultBrowserLauncher.js.flow +20 -0
  38. package/dist/utils/getDevToolsFrontendUrl.d.ts +21 -0
  39. package/dist/utils/getDevToolsFrontendUrl.js +48 -0
  40. package/dist/utils/getDevToolsFrontendUrl.js.flow +21 -0
  41. package/package.json +6 -1
  42. package/dist/utils/getDevServerUrl.js +0 -37
  43. package/dist/utils/getDevServerUrl.js.flow +0 -32
  44. package/dist/utils/launchChromeDevTools.js +0 -44
  45. package/dist/utils/launchChromeDevTools.js.flow +0 -38
  46. package/dist/utils/launchDebuggerAppWindow.js +0 -58
  47. package/dist/utils/launchDebuggerAppWindow.js.flow +0 -56
  48. package/dist/utils/queryInspectorTargets.js +0 -35
  49. package/dist/utils/queryInspectorTargets.js.flow +0 -40
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @format
9
+ * @oncall react_native
10
+ */
11
+
12
+ import type {
13
+ DebuggerRequest,
14
+ GetScriptSourceRequest,
15
+ MessageFromDevice,
16
+ MessageToDevice,
17
+ Page,
18
+ SetBreakpointByUrlRequest,
19
+ } from "./types";
20
+
21
+ import DeviceEventReporter from "./DeviceEventReporter";
22
+ import WS from "ws";
23
+ import type { EventReporter } from "../types/EventReporter";
24
+
25
+ type DebuggerInfo = {
26
+ // Debugger web socket connection
27
+ socket: WS,
28
+ // If we replaced address (like '10.0.2.2') to localhost we need to store original
29
+ // address because Chrome uses URL or urlRegex params (instead of scriptId) to set breakpoints.
30
+ originalSourceURLAddress?: string,
31
+ prependedFilePrefix: boolean,
32
+ pageId: string,
33
+ userAgent: string | null,
34
+ };
35
+
36
+ /**
37
+ * Device class represents single device connection to Inspector Proxy. Each device
38
+ * can have multiple inspectable pages.
39
+ */
40
+ declare export default class Device {
41
+ _id: string;
42
+ _name: string;
43
+ _app: string;
44
+ _deviceSocket: WS;
45
+ _pages: Array<Page>;
46
+ _debuggerConnection: ?DebuggerInfo;
47
+ _lastConnectedReactNativePage: ?Page;
48
+ _isReloading: boolean;
49
+ _lastGetPagesMessage: string;
50
+ _scriptIdToSourcePathMapping: Map<string, string>;
51
+ _projectRoot: string;
52
+ _deviceEventReporter: ?DeviceEventReporter;
53
+ constructor(
54
+ id: string,
55
+ name: string,
56
+ app: string,
57
+ socket: WS,
58
+ projectRoot: string,
59
+ eventReporter: ?EventReporter
60
+ ): void;
61
+ getName(): string;
62
+ getApp(): string;
63
+ getPagesList(): Array<Page>;
64
+ handleDebuggerConnection(
65
+ socket: WS,
66
+ pageId: string,
67
+ metadata: $ReadOnly<{
68
+ userAgent: string | null,
69
+ }>
70
+ ): void;
71
+ handleDuplicateDeviceConnection(newDevice: Device): void;
72
+ _handleMessageFromDevice(message: MessageFromDevice): void;
73
+ _sendMessageToDevice(message: MessageToDevice): void;
74
+ _setPagesPolling(): void;
75
+ _newReactNativePage(page: Page): void;
76
+ _processMessageFromDevice(
77
+ payload: { method: string, params: { sourceMapURL: string, url: string } },
78
+ debuggerInfo: DebuggerInfo
79
+ ): void;
80
+ _interceptMessageFromDebugger(
81
+ req: DebuggerRequest,
82
+ debuggerInfo: DebuggerInfo,
83
+ socket: WS
84
+ ): boolean;
85
+ _processDebuggerSetBreakpointByUrl(
86
+ req: SetBreakpointByUrlRequest,
87
+ debuggerInfo: DebuggerInfo
88
+ ): void;
89
+ _processDebuggerGetScriptSource(
90
+ req: GetScriptSourceRequest,
91
+ socket: WS
92
+ ): void;
93
+ _mapToDevicePageId(pageId: string): string;
94
+ _tryParseHTTPURL(url: string): ?URL;
95
+ _fetchText(url: URL): Promise<string>;
96
+ _sendErrorToDebugger(message: string): void;
97
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ *
8
+ * @format
9
+ */
10
+
11
+ import type { EventReporter } from "../types/EventReporter";
12
+ import TTLCache from "@isaacs/ttlcache";
13
+ type PendingCommand = {
14
+ method: string;
15
+ requestOrigin: "proxy" | "debugger";
16
+ requestTime: number;
17
+ metadata: RequestMetadata;
18
+ };
19
+ type DeviceMetadata = Readonly<{
20
+ appId: string;
21
+ deviceId: string;
22
+ deviceName: string;
23
+ }>;
24
+ type RequestMetadata = Readonly<{
25
+ pageId: string | null;
26
+ frontendUserAgent: string | null;
27
+ }>;
28
+ declare class DeviceEventReporter {
29
+ _eventReporter: EventReporter;
30
+ _pendingCommands: TTLCache<number, PendingCommand>;
31
+ _metadata: DeviceMetadata;
32
+ constructor(eventReporter: EventReporter, metadata: DeviceMetadata);
33
+ logRequest(
34
+ req: Readonly<{ id: number; method: string }>,
35
+ origin: "debugger" | "proxy",
36
+ metadata: RequestMetadata
37
+ ): void;
38
+ logResponse(
39
+ res: Readonly<{ id: number; error?: { message: string; data?: unknown } }>,
40
+ origin: "device" | "proxy",
41
+ metadata: Readonly<{
42
+ pageId: string | null;
43
+ frontendUserAgent: string | null;
44
+ }>
45
+ ): void;
46
+ logConnection(
47
+ connectedEntity: "debugger",
48
+ metadata: Readonly<{ pageId: string; frontendUserAgent: string | null }>
49
+ ): void;
50
+ logDisconnection(disconnectedEntity: "device" | "debugger"): void;
51
+ _logExpiredCommand(pendingCommand: PendingCommand): void;
52
+ }
53
+ export default DeviceEventReporter;
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true,
5
+ });
6
+ exports.default = void 0;
7
+ var _ttlcache = _interopRequireDefault(require("@isaacs/ttlcache"));
8
+ function _interopRequireDefault(obj) {
9
+ return obj && obj.__esModule ? obj : { default: obj };
10
+ }
11
+ /**
12
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
13
+ *
14
+ * This source code is licensed under the MIT license found in the
15
+ * LICENSE file in the root directory of this source tree.
16
+ *
17
+ *
18
+ * @format
19
+ */
20
+
21
+ class DeviceEventReporter {
22
+ _pendingCommands = new _ttlcache.default({
23
+ ttl: 10000,
24
+ dispose: (command, id, reason) => {
25
+ if (reason === "delete" || reason === "set") {
26
+ // TODO: Report clobbering ('set') using a dedicated error code
27
+ return;
28
+ }
29
+ this._logExpiredCommand(command);
30
+ },
31
+ });
32
+ constructor(eventReporter, metadata) {
33
+ this._eventReporter = eventReporter;
34
+ this._metadata = metadata;
35
+ }
36
+ logRequest(req, origin, metadata) {
37
+ this._pendingCommands.set(req.id, {
38
+ method: req.method,
39
+ requestOrigin: origin,
40
+ requestTime: Date.now(),
41
+ metadata,
42
+ });
43
+ }
44
+ logResponse(res, origin, metadata) {
45
+ const pendingCommand = this._pendingCommands.get(res.id);
46
+ if (!pendingCommand) {
47
+ this._eventReporter.logEvent({
48
+ type: "debugger_command",
49
+ protocol: "CDP",
50
+ requestOrigin: null,
51
+ method: null,
52
+ status: "coded_error",
53
+ errorCode: "UNMATCHED_REQUEST_ID",
54
+ responseOrigin: "proxy",
55
+ timeSinceStart: null,
56
+ appId: this._metadata.appId,
57
+ deviceId: this._metadata.deviceId,
58
+ deviceName: this._metadata.deviceName,
59
+ pageId: metadata.pageId,
60
+ frontendUserAgent: metadata.frontendUserAgent,
61
+ });
62
+ return;
63
+ }
64
+ const timeSinceStart = Date.now() - pendingCommand.requestTime;
65
+ this._pendingCommands.delete(res.id);
66
+ if (res.error) {
67
+ let { message } = res.error;
68
+ if ("data" in res.error) {
69
+ message += ` (${String(res.error.data)})`;
70
+ }
71
+ this._eventReporter.logEvent({
72
+ type: "debugger_command",
73
+ requestOrigin: pendingCommand.requestOrigin,
74
+ method: pendingCommand.method,
75
+ protocol: "CDP",
76
+ status: "coded_error",
77
+ errorCode: "PROTOCOL_ERROR",
78
+ errorDetails: message,
79
+ responseOrigin: origin,
80
+ timeSinceStart,
81
+ appId: this._metadata.appId,
82
+ deviceId: this._metadata.deviceId,
83
+ deviceName: this._metadata.deviceName,
84
+ pageId: pendingCommand.metadata.pageId,
85
+ frontendUserAgent: pendingCommand.metadata.frontendUserAgent,
86
+ });
87
+ return;
88
+ }
89
+ this._eventReporter.logEvent({
90
+ type: "debugger_command",
91
+ protocol: "CDP",
92
+ requestOrigin: pendingCommand.requestOrigin,
93
+ method: pendingCommand.method,
94
+ status: "success",
95
+ responseOrigin: origin,
96
+ timeSinceStart,
97
+ appId: this._metadata.appId,
98
+ deviceId: this._metadata.deviceId,
99
+ deviceName: this._metadata.deviceName,
100
+ pageId: pendingCommand.metadata.pageId,
101
+ frontendUserAgent: pendingCommand.metadata.frontendUserAgent,
102
+ });
103
+ }
104
+ logConnection(connectedEntity, metadata) {
105
+ this._eventReporter.logEvent({
106
+ type: "connect_debugger_frontend",
107
+ status: "success",
108
+ appId: this._metadata.appId,
109
+ deviceName: this._metadata.deviceName,
110
+ deviceId: this._metadata.deviceId,
111
+ pageId: metadata.pageId,
112
+ frontendUserAgent: metadata.frontendUserAgent,
113
+ });
114
+ }
115
+ logDisconnection(disconnectedEntity) {
116
+ const eventReporter = this._eventReporter;
117
+ if (!eventReporter) {
118
+ return;
119
+ }
120
+ const errorCode =
121
+ disconnectedEntity === "device"
122
+ ? "DEVICE_DISCONNECTED"
123
+ : "DEBUGGER_DISCONNECTED";
124
+ for (const pendingCommand of this._pendingCommands.values()) {
125
+ this._eventReporter.logEvent({
126
+ type: "debugger_command",
127
+ protocol: "CDP",
128
+ requestOrigin: pendingCommand.requestOrigin,
129
+ method: pendingCommand.method,
130
+ status: "coded_error",
131
+ errorCode,
132
+ responseOrigin: "proxy",
133
+ timeSinceStart: Date.now() - pendingCommand.requestTime,
134
+ appId: this._metadata.appId,
135
+ deviceId: this._metadata.deviceId,
136
+ deviceName: this._metadata.deviceName,
137
+ pageId: pendingCommand.metadata.pageId,
138
+ frontendUserAgent: pendingCommand.metadata.frontendUserAgent,
139
+ });
140
+ }
141
+ this._pendingCommands.clear();
142
+ }
143
+ _logExpiredCommand(pendingCommand) {
144
+ this._eventReporter.logEvent({
145
+ type: "debugger_command",
146
+ protocol: "CDP",
147
+ requestOrigin: pendingCommand.requestOrigin,
148
+ method: pendingCommand.method,
149
+ status: "coded_error",
150
+ errorCode: "TIMED_OUT",
151
+ responseOrigin: "proxy",
152
+ timeSinceStart: Date.now() - pendingCommand.requestTime,
153
+ appId: this._metadata.appId,
154
+ deviceId: this._metadata.deviceId,
155
+ deviceName: this._metadata.deviceName,
156
+ pageId: pendingCommand.metadata.pageId,
157
+ frontendUserAgent: pendingCommand.metadata.frontendUserAgent,
158
+ });
159
+ }
160
+ }
161
+ var _default = DeviceEventReporter;
162
+ exports.default = _default;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict-local
8
+ * @format
9
+ */
10
+
11
+ import type { EventReporter } from "../types/EventReporter";
12
+ import TTLCache from "@isaacs/ttlcache";
13
+
14
+ type PendingCommand = {
15
+ method: string,
16
+ requestOrigin: "proxy" | "debugger",
17
+ requestTime: number,
18
+ metadata: RequestMetadata,
19
+ };
20
+
21
+ type DeviceMetadata = $ReadOnly<{
22
+ appId: string,
23
+ deviceId: string,
24
+ deviceName: string,
25
+ }>;
26
+
27
+ type RequestMetadata = $ReadOnly<{
28
+ pageId: string | null,
29
+ frontendUserAgent: string | null,
30
+ }>;
31
+
32
+ declare class DeviceEventReporter {
33
+ _eventReporter: EventReporter;
34
+ _pendingCommands: TTLCache<number, PendingCommand>;
35
+ _metadata: DeviceMetadata;
36
+ constructor(eventReporter: EventReporter, metadata: DeviceMetadata): void;
37
+ logRequest(
38
+ req: $ReadOnly<{ id: number, method: string, ... }>,
39
+ origin: "debugger" | "proxy",
40
+ metadata: RequestMetadata
41
+ ): void;
42
+ logResponse(
43
+ res: $ReadOnly<{
44
+ id: number,
45
+ error?: { message: string, data?: mixed },
46
+ ...
47
+ }>,
48
+ origin: "device" | "proxy",
49
+ metadata: $ReadOnly<{
50
+ pageId: string | null,
51
+ frontendUserAgent: string | null,
52
+ }>
53
+ ): void;
54
+ logConnection(
55
+ connectedEntity: "debugger",
56
+ metadata: $ReadOnly<{
57
+ pageId: string,
58
+ frontendUserAgent: string | null,
59
+ }>
60
+ ): void;
61
+ logDisconnection(disconnectedEntity: "device" | "debugger"): void;
62
+ _logExpiredCommand(pendingCommand: PendingCommand): void;
63
+ }
64
+
65
+ declare export default DeviceEventReporter;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ *
8
+ * @format
9
+ * @oncall react_native
10
+ */
11
+
12
+ import type {
13
+ JsonPagesListResponse,
14
+ JsonVersionResponse,
15
+ Page,
16
+ PageDescription,
17
+ } from "./types";
18
+ import type { EventReporter } from "../types/EventReporter";
19
+ import type { Experiments } from "../types/Experiments";
20
+ import type { IncomingMessage, ServerResponse } from "http";
21
+ import WS from "ws";
22
+ import Device from "./Device";
23
+ export interface InspectorProxyQueries {
24
+ getPageDescriptions(): Array<PageDescription>;
25
+ }
26
+ /**
27
+ * Main Inspector Proxy class that connects JavaScript VM inside Android/iOS apps and JS debugger.
28
+ */
29
+ declare class InspectorProxy implements InspectorProxyQueries {
30
+ _projectRoot: string;
31
+ _serverBaseUrl: string;
32
+ _devices: Map<string, Device>;
33
+ _deviceCounter: number;
34
+ _eventReporter: null | undefined | EventReporter;
35
+ _experiments: Experiments;
36
+ constructor(
37
+ projectRoot: string,
38
+ serverBaseUrl: string,
39
+ eventReporter: null | undefined | EventReporter,
40
+ experiments: Experiments
41
+ );
42
+ getPageDescriptions(): Array<PageDescription>;
43
+ processRequest(
44
+ request: IncomingMessage,
45
+ response: ServerResponse,
46
+ next: ($$PARAM_0$$: null | undefined | Error) => unknown
47
+ ): void;
48
+ createWebSocketListeners(): { [path: string]: WS.Server };
49
+ _buildPageDescription(
50
+ deviceId: string,
51
+ device: Device,
52
+ page: Page
53
+ ): PageDescription;
54
+ _sendJsonResponse(
55
+ response: ServerResponse,
56
+ object: JsonPagesListResponse | JsonVersionResponse
57
+ ): void;
58
+ _createDeviceConnectionWSServer(): ws$WebSocketServer;
59
+ _createDebuggerConnectionWSServer(): ws$WebSocketServer;
60
+ }
61
+ export default InspectorProxy;
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true,
5
+ });
6
+ exports.default = void 0;
7
+ var _url = _interopRequireDefault(require("url"));
8
+ var _ws = _interopRequireDefault(require("ws"));
9
+ var _Device = _interopRequireDefault(require("./Device"));
10
+ function _interopRequireDefault(obj) {
11
+ return obj && obj.__esModule ? obj : { default: obj };
12
+ }
13
+ /**
14
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
15
+ *
16
+ * This source code is licensed under the MIT license found in the
17
+ * LICENSE file in the root directory of this source tree.
18
+ *
19
+ *
20
+ * @format
21
+ * @oncall react_native
22
+ */
23
+
24
+ const debug = require("debug")("Metro:InspectorProxy");
25
+ const WS_DEVICE_URL = "/inspector/device";
26
+ const WS_DEBUGGER_URL = "/inspector/debug";
27
+ const PAGES_LIST_JSON_URL = "/json";
28
+ const PAGES_LIST_JSON_URL_2 = "/json/list";
29
+ const PAGES_LIST_JSON_VERSION_URL = "/json/version";
30
+ const INTERNAL_ERROR_CODE = 1011;
31
+ /**
32
+ * Main Inspector Proxy class that connects JavaScript VM inside Android/iOS apps and JS debugger.
33
+ */
34
+ class InspectorProxy {
35
+ // Root of the project used for relative to absolute source path conversion.
36
+
37
+ /** The base URL to the dev server from the developer machine. */
38
+
39
+ // Maps device ID to Device instance.
40
+
41
+ // Internal counter for device IDs -- just gets incremented for each new device.
42
+ _deviceCounter = 0;
43
+ constructor(projectRoot, serverBaseUrl, eventReporter, experiments) {
44
+ this._projectRoot = projectRoot;
45
+ this._serverBaseUrl = serverBaseUrl;
46
+ this._devices = new Map();
47
+ this._eventReporter = eventReporter;
48
+ this._experiments = experiments;
49
+ }
50
+ getPageDescriptions() {
51
+ // Build list of pages from all devices.
52
+ let result = [];
53
+ Array.from(this._devices.entries()).forEach(([deviceId, device]) => {
54
+ result = result.concat(
55
+ device
56
+ .getPagesList()
57
+ .map((page) => this._buildPageDescription(deviceId, device, page))
58
+ );
59
+ });
60
+ return result;
61
+ }
62
+
63
+ // Process HTTP request sent to server. We only respond to 2 HTTP requests:
64
+ // 1. /json/version returns Chrome debugger protocol version that we use
65
+ // 2. /json and /json/list returns list of page descriptions (list of inspectable apps).
66
+ // This list is combined from all the connected devices.
67
+ processRequest(request, response, next) {
68
+ if (
69
+ request.url === PAGES_LIST_JSON_URL ||
70
+ request.url === PAGES_LIST_JSON_URL_2
71
+ ) {
72
+ this._sendJsonResponse(response, this.getPageDescriptions());
73
+ } else if (request.url === PAGES_LIST_JSON_VERSION_URL) {
74
+ this._sendJsonResponse(response, {
75
+ Browser: "Mobile JavaScript",
76
+ "Protocol-Version": "1.1",
77
+ });
78
+ } else {
79
+ next();
80
+ }
81
+ }
82
+ createWebSocketListeners() {
83
+ return {
84
+ [WS_DEVICE_URL]: this._createDeviceConnectionWSServer(),
85
+ [WS_DEBUGGER_URL]: this._createDebuggerConnectionWSServer(),
86
+ };
87
+ }
88
+
89
+ // Converts page information received from device into PageDescription object
90
+ // that is sent to debugger.
91
+ _buildPageDescription(deviceId, device, page) {
92
+ const { host, protocol } = new URL(this._serverBaseUrl);
93
+ const webSocketScheme = protocol === "https:" ? "wss" : "ws";
94
+ const webSocketUrlWithoutProtocol = `${host}${WS_DEBUGGER_URL}?device=${deviceId}&page=${page.id}`;
95
+ // For now, `/json/list` returns the legacy built-in `devtools://` URL, to
96
+ // preserve existing handling by Flipper. This may return a placeholder in
97
+ // future -- please use the `/open-debugger` endpoint.
98
+ const devtoolsFrontendUrl =
99
+ `devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&${webSocketScheme}=` +
100
+ encodeURIComponent(webSocketUrlWithoutProtocol);
101
+ return {
102
+ id: `${deviceId}-${page.id}`,
103
+ description: page.app,
104
+ title: page.title,
105
+ faviconUrl: "https://reactjs.org/favicon.ico",
106
+ devtoolsFrontendUrl,
107
+ type: "node",
108
+ webSocketDebuggerUrl: `${webSocketScheme}://${webSocketUrlWithoutProtocol}`,
109
+ vm: page.vm,
110
+ deviceName: device.getName(),
111
+ };
112
+ }
113
+
114
+ // Sends object as response to HTTP request.
115
+ // Just serializes object using JSON and sets required headers.
116
+ _sendJsonResponse(response, object) {
117
+ const data = JSON.stringify(object, null, 2);
118
+ response.writeHead(200, {
119
+ "Content-Type": "application/json; charset=UTF-8",
120
+ "Cache-Control": "no-cache",
121
+ "Content-Length": data.length.toString(),
122
+ Connection: "close",
123
+ });
124
+ response.end(data);
125
+ }
126
+
127
+ // Adds websocket handler for device connections.
128
+ // Device connects to /inspector/device and passes device and app names as
129
+ // HTTP GET params.
130
+ // For each new websocket connection we parse device and app names and create
131
+ // new instance of Device class.
132
+ _createDeviceConnectionWSServer() {
133
+ const wss = new _ws.default.Server({
134
+ noServer: true,
135
+ perMessageDeflate: true,
136
+ });
137
+ // $FlowFixMe[value-as-type]
138
+ wss.on("connection", async (socket, req) => {
139
+ try {
140
+ const fallbackDeviceId = String(this._deviceCounter++);
141
+ const query = _url.default.parse(req.url || "", true).query || {};
142
+ const deviceId = query.device || fallbackDeviceId;
143
+ const deviceName = query.name || "Unknown";
144
+ const appName = query.app || "Unknown";
145
+ const oldDevice = this._devices.get(deviceId);
146
+ const newDevice = new _Device.default(
147
+ deviceId,
148
+ deviceName,
149
+ appName,
150
+ socket,
151
+ this._projectRoot,
152
+ this._eventReporter
153
+ );
154
+ if (oldDevice) {
155
+ oldDevice.handleDuplicateDeviceConnection(newDevice);
156
+ }
157
+ this._devices.set(deviceId, newDevice);
158
+ debug(
159
+ `Got new connection: name=${deviceName}, app=${appName}, device=${deviceId}`
160
+ );
161
+ socket.on("close", () => {
162
+ this._devices.delete(deviceId);
163
+ debug(`Device ${deviceName} disconnected.`);
164
+ });
165
+ } catch (e) {
166
+ console.error("error", e);
167
+ socket.close(INTERNAL_ERROR_CODE, e?.toString() ?? "Unknown error");
168
+ }
169
+ });
170
+ return wss;
171
+ }
172
+
173
+ // Returns websocket handler for debugger connections.
174
+ // Debugger connects to webSocketDebuggerUrl that we return as part of page description
175
+ // in /json response.
176
+ // When debugger connects we try to parse device and page IDs from the query and pass
177
+ // websocket object to corresponding Device instance.
178
+ _createDebuggerConnectionWSServer() {
179
+ const wss = new _ws.default.Server({
180
+ noServer: true,
181
+ perMessageDeflate: false,
182
+ });
183
+ // $FlowFixMe[value-as-type]
184
+ wss.on("connection", async (socket, req) => {
185
+ try {
186
+ const query = _url.default.parse(req.url || "", true).query || {};
187
+ const deviceId = query.device;
188
+ const pageId = query.page;
189
+ if (deviceId == null || pageId == null) {
190
+ throw new Error("Incorrect URL - must provide device and page IDs");
191
+ }
192
+ const device = this._devices.get(deviceId);
193
+ if (device == null) {
194
+ throw new Error("Unknown device with ID " + deviceId);
195
+ }
196
+ device.handleDebuggerConnection(socket, pageId, {
197
+ userAgent: req.headers["user-agent"] ?? null,
198
+ });
199
+ } catch (e) {
200
+ console.error(e);
201
+ socket.close(INTERNAL_ERROR_CODE, e?.toString() ?? "Unknown error");
202
+ this._eventReporter?.logEvent({
203
+ type: "connect_debugger_frontend",
204
+ status: "error",
205
+ error: e,
206
+ });
207
+ }
208
+ });
209
+ return wss;
210
+ }
211
+ }
212
+ exports.default = InspectorProxy;