@depup/react-native__dev-middleware 0.84.1-depup.1
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.
- package/README.md +38 -0
- package/changes.json +38 -0
- package/dist/createDevMiddleware.d.ts +62 -0
- package/dist/createDevMiddleware.js +140 -0
- package/dist/createDevMiddleware.js.flow +72 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +26 -0
- package/dist/index.js.flow +24 -0
- package/dist/inspector-proxy/CdpDebugLogging.d.ts +20 -0
- package/dist/inspector-proxy/CdpDebugLogging.js +117 -0
- package/dist/inspector-proxy/CdpDebugLogging.js.flow +20 -0
- package/dist/inspector-proxy/CustomMessageHandler.d.ts +48 -0
- package/dist/inspector-proxy/CustomMessageHandler.js +1 -0
- package/dist/inspector-proxy/CustomMessageHandler.js.flow +54 -0
- package/dist/inspector-proxy/Device.d.ts +62 -0
- package/dist/inspector-proxy/Device.js +810 -0
- package/dist/inspector-proxy/Device.js.flow +70 -0
- package/dist/inspector-proxy/DeviceEventReporter.d.ts +54 -0
- package/dist/inspector-proxy/DeviceEventReporter.js +194 -0
- package/dist/inspector-proxy/DeviceEventReporter.js.flow +62 -0
- package/dist/inspector-proxy/EventLoopPerfTracker.d.ts +31 -0
- package/dist/inspector-proxy/EventLoopPerfTracker.js +50 -0
- package/dist/inspector-proxy/EventLoopPerfTracker.js.flow +34 -0
- package/dist/inspector-proxy/InspectorProxy.d.ts +53 -0
- package/dist/inspector-proxy/InspectorProxy.js +477 -0
- package/dist/inspector-proxy/InspectorProxy.js.flow +62 -0
- package/dist/inspector-proxy/InspectorProxyHeartbeat.d.ts +24 -0
- package/dist/inspector-proxy/InspectorProxyHeartbeat.js +64 -0
- package/dist/inspector-proxy/InspectorProxyHeartbeat.js.flow +25 -0
- package/dist/inspector-proxy/cdp-types/messages.d.ts +41 -0
- package/dist/inspector-proxy/cdp-types/messages.js +1 -0
- package/dist/inspector-proxy/cdp-types/messages.js.flow +54 -0
- package/dist/inspector-proxy/cdp-types/protocol.d.ts +106 -0
- package/dist/inspector-proxy/cdp-types/protocol.js +1 -0
- package/dist/inspector-proxy/cdp-types/protocol.js.flow +124 -0
- package/dist/inspector-proxy/types.d.ts +115 -0
- package/dist/inspector-proxy/types.js +1 -0
- package/dist/inspector-proxy/types.js.flow +152 -0
- package/dist/middleware/openDebuggerMiddleware.d.ts +36 -0
- package/dist/middleware/openDebuggerMiddleware.js +216 -0
- package/dist/middleware/openDebuggerMiddleware.js.flow +36 -0
- package/dist/types/BrowserLauncher.d.ts +62 -0
- package/dist/types/BrowserLauncher.js +1 -0
- package/dist/types/BrowserLauncher.js.flow +66 -0
- package/dist/types/EventReporter.d.ts +124 -0
- package/dist/types/EventReporter.js +1 -0
- package/dist/types/EventReporter.js.flow +151 -0
- package/dist/types/Experiments.d.ts +30 -0
- package/dist/types/Experiments.js +1 -0
- package/dist/types/Experiments.js.flow +34 -0
- package/dist/types/Logger.d.ts +15 -0
- package/dist/types/Logger.js +1 -0
- package/dist/types/Logger.js.flow +16 -0
- package/dist/utils/DefaultBrowserLauncher.d.ts +30 -0
- package/dist/utils/DefaultBrowserLauncher.js +57 -0
- package/dist/utils/DefaultBrowserLauncher.js.flow +29 -0
- package/dist/utils/getBaseUrlFromRequest.d.ts +14 -0
- package/dist/utils/getBaseUrlFromRequest.js +19 -0
- package/dist/utils/getBaseUrlFromRequest.js.flow +17 -0
- package/dist/utils/getDevToolsFrontendUrl.d.ts +29 -0
- package/dist/utils/getDevToolsFrontendUrl.js +58 -0
- package/dist/utils/getDevToolsFrontendUrl.js.flow +29 -0
- package/package.json +97 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true,
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _getBaseUrlFromRequest = _interopRequireDefault(
|
|
8
|
+
require("../utils/getBaseUrlFromRequest"),
|
|
9
|
+
);
|
|
10
|
+
var _getDevToolsFrontendUrl = _interopRequireDefault(
|
|
11
|
+
require("../utils/getDevToolsFrontendUrl"),
|
|
12
|
+
);
|
|
13
|
+
var _Device = _interopRequireDefault(require("./Device"));
|
|
14
|
+
var _EventLoopPerfTracker = _interopRequireDefault(
|
|
15
|
+
require("./EventLoopPerfTracker"),
|
|
16
|
+
);
|
|
17
|
+
var _InspectorProxyHeartbeat = _interopRequireDefault(
|
|
18
|
+
require("./InspectorProxyHeartbeat"),
|
|
19
|
+
);
|
|
20
|
+
var _nullthrows = _interopRequireDefault(require("nullthrows"));
|
|
21
|
+
var _url = _interopRequireDefault(require("url"));
|
|
22
|
+
var _ws = _interopRequireDefault(require("ws"));
|
|
23
|
+
function _interopRequireDefault(e) {
|
|
24
|
+
return e && e.__esModule ? e : { default: e };
|
|
25
|
+
}
|
|
26
|
+
const debug = require("debug")("Metro:InspectorProxy");
|
|
27
|
+
const WS_DEVICE_URL = "/inspector/device";
|
|
28
|
+
const WS_DEBUGGER_URL = "/inspector/debug";
|
|
29
|
+
const PAGES_LIST_JSON_URL = "/json";
|
|
30
|
+
const PAGES_LIST_JSON_URL_2 = "/json/list";
|
|
31
|
+
const PAGES_LIST_JSON_VERSION_URL = "/json/version";
|
|
32
|
+
const HEARTBEAT_TIME_BETWEEN_PINGS_MS = 5000;
|
|
33
|
+
const HEARTBEAT_TIMEOUT_MS = 60000;
|
|
34
|
+
const MIN_PING_TO_REPORT = 500;
|
|
35
|
+
const EVENT_LOOP_PERF_MEASUREMENT_MS = 5000;
|
|
36
|
+
const MIN_EVENT_LOOP_DELAY_PERCENT_TO_REPORT = 20;
|
|
37
|
+
const INTERNAL_ERROR_CODE = 1011;
|
|
38
|
+
const INTERNAL_ERROR_MESSAGES = {
|
|
39
|
+
UNREGISTERED_DEVICE:
|
|
40
|
+
"[UNREGISTERED_DEVICE] Debugger connection attempted for a device that was not registered",
|
|
41
|
+
INCORRECT_URL:
|
|
42
|
+
"[INCORRECT_URL] Incorrect URL - device and page IDs must be provided",
|
|
43
|
+
};
|
|
44
|
+
class InspectorProxy {
|
|
45
|
+
#serverBaseUrl;
|
|
46
|
+
#devices;
|
|
47
|
+
#deviceCounter = 0;
|
|
48
|
+
#eventReporter;
|
|
49
|
+
#experiments;
|
|
50
|
+
#customMessageHandler;
|
|
51
|
+
#logger;
|
|
52
|
+
#lastMessageTimestamp = null;
|
|
53
|
+
#eventLoopPerfTracker;
|
|
54
|
+
constructor(
|
|
55
|
+
serverBaseUrl,
|
|
56
|
+
eventReporter,
|
|
57
|
+
experiments,
|
|
58
|
+
logger,
|
|
59
|
+
customMessageHandler,
|
|
60
|
+
trackEventLoopPerf = false,
|
|
61
|
+
) {
|
|
62
|
+
this.#serverBaseUrl = new URL(serverBaseUrl);
|
|
63
|
+
this.#devices = new Map();
|
|
64
|
+
this.#eventReporter = eventReporter;
|
|
65
|
+
this.#experiments = experiments;
|
|
66
|
+
this.#logger = logger;
|
|
67
|
+
this.#customMessageHandler = customMessageHandler;
|
|
68
|
+
if (trackEventLoopPerf) {
|
|
69
|
+
this.#eventLoopPerfTracker = new _EventLoopPerfTracker.default({
|
|
70
|
+
perfMeasurementDuration: EVENT_LOOP_PERF_MEASUREMENT_MS,
|
|
71
|
+
minDelayPercentToReport: MIN_EVENT_LOOP_DELAY_PERCENT_TO_REPORT,
|
|
72
|
+
onHighDelay: ({
|
|
73
|
+
eventLoopUtilization,
|
|
74
|
+
maxEventLoopDelayPercent,
|
|
75
|
+
duration,
|
|
76
|
+
debuggerSessionIDs,
|
|
77
|
+
connectionUptime,
|
|
78
|
+
}) => {
|
|
79
|
+
debug(
|
|
80
|
+
"[perf] high event loop delay in the last %ds- event loop utilization='%d%' max event loop delay percent='%d%'",
|
|
81
|
+
duration / 1000,
|
|
82
|
+
eventLoopUtilization,
|
|
83
|
+
maxEventLoopDelayPercent,
|
|
84
|
+
);
|
|
85
|
+
this.#eventReporter?.logEvent({
|
|
86
|
+
type: "high_event_loop_delay",
|
|
87
|
+
eventLoopUtilization,
|
|
88
|
+
maxEventLoopDelayPercent,
|
|
89
|
+
duration,
|
|
90
|
+
connectionUptime,
|
|
91
|
+
...debuggerSessionIDs,
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
getPageDescriptions({
|
|
98
|
+
requestorRelativeBaseUrl,
|
|
99
|
+
logNoPagesForConnectedDevice = false,
|
|
100
|
+
}) {
|
|
101
|
+
let result = [];
|
|
102
|
+
Array.from(this.#devices.entries()).forEach(([deviceId, device]) => {
|
|
103
|
+
const devicePages = device
|
|
104
|
+
.getPagesList()
|
|
105
|
+
.map((page) =>
|
|
106
|
+
this.#buildPageDescription(
|
|
107
|
+
deviceId,
|
|
108
|
+
device,
|
|
109
|
+
page,
|
|
110
|
+
requestorRelativeBaseUrl,
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
if (
|
|
114
|
+
logNoPagesForConnectedDevice &&
|
|
115
|
+
devicePages.length === 0 &&
|
|
116
|
+
device.dangerouslyGetSocket()?.readyState === _ws.default.OPEN
|
|
117
|
+
) {
|
|
118
|
+
this.#logger?.warn(
|
|
119
|
+
`Waiting for a DevTools connection to app='%s' on device='%s'.
|
|
120
|
+
Try again when the main bundle for the app is built and connection is established.
|
|
121
|
+
If no connection occurs, try to:
|
|
122
|
+
- Restart the app. For Android, force stopping the app first might be required.
|
|
123
|
+
- Ensure a stable connection to the device.
|
|
124
|
+
- Ensure that the app is built in a mode that supports debugging.
|
|
125
|
+
- Take the app out of running in the background.`,
|
|
126
|
+
device.getApp(),
|
|
127
|
+
device.getName(),
|
|
128
|
+
);
|
|
129
|
+
this.#eventReporter?.logEvent({
|
|
130
|
+
type: "no_debug_pages_for_device",
|
|
131
|
+
appId: device.getApp(),
|
|
132
|
+
deviceName: device.getName(),
|
|
133
|
+
deviceId: deviceId,
|
|
134
|
+
pageId: null,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
result = result.concat(devicePages);
|
|
138
|
+
});
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
processRequest(request, response, next) {
|
|
142
|
+
const pathname = _url.default.parse(request.url).pathname;
|
|
143
|
+
if (
|
|
144
|
+
pathname === PAGES_LIST_JSON_URL ||
|
|
145
|
+
pathname === PAGES_LIST_JSON_URL_2
|
|
146
|
+
) {
|
|
147
|
+
this.#sendJsonResponse(
|
|
148
|
+
response,
|
|
149
|
+
this.getPageDescriptions({
|
|
150
|
+
requestorRelativeBaseUrl:
|
|
151
|
+
(0, _getBaseUrlFromRequest.default)(request) ?? this.#serverBaseUrl,
|
|
152
|
+
logNoPagesForConnectedDevice: true,
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
155
|
+
} else if (pathname === PAGES_LIST_JSON_VERSION_URL) {
|
|
156
|
+
this.#sendJsonResponse(response, {
|
|
157
|
+
Browser: "Mobile JavaScript",
|
|
158
|
+
"Protocol-Version": "1.1",
|
|
159
|
+
});
|
|
160
|
+
} else {
|
|
161
|
+
next();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
createWebSocketListeners() {
|
|
165
|
+
return {
|
|
166
|
+
[WS_DEVICE_URL]: this.#createDeviceConnectionWSServer(),
|
|
167
|
+
[WS_DEBUGGER_URL]: this.#createDebuggerConnectionWSServer(),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
#buildPageDescription(deviceId, device, page, requestorRelativeBaseUrl) {
|
|
171
|
+
const { host, protocol } = requestorRelativeBaseUrl;
|
|
172
|
+
const webSocketScheme = protocol === "https:" ? "wss" : "ws";
|
|
173
|
+
const webSocketUrlWithoutProtocol = `${host}${WS_DEBUGGER_URL}?device=${deviceId}&page=${page.id}`;
|
|
174
|
+
const webSocketDebuggerUrl = `${webSocketScheme}://${webSocketUrlWithoutProtocol}`;
|
|
175
|
+
const devtoolsFrontendUrl = (0, _getDevToolsFrontendUrl.default)(
|
|
176
|
+
this.#experiments,
|
|
177
|
+
webSocketDebuggerUrl,
|
|
178
|
+
this.#serverBaseUrl.origin,
|
|
179
|
+
{
|
|
180
|
+
relative: true,
|
|
181
|
+
useFuseboxEntryPoint: page.capabilities.prefersFuseboxFrontend,
|
|
182
|
+
},
|
|
183
|
+
);
|
|
184
|
+
return {
|
|
185
|
+
id: `${deviceId}-${page.id}`,
|
|
186
|
+
title: page.title,
|
|
187
|
+
description: page.description ?? page.app,
|
|
188
|
+
appId: page.app,
|
|
189
|
+
type: "node",
|
|
190
|
+
devtoolsFrontendUrl,
|
|
191
|
+
webSocketDebuggerUrl,
|
|
192
|
+
...(page.vm != null
|
|
193
|
+
? {
|
|
194
|
+
vm: page.vm,
|
|
195
|
+
}
|
|
196
|
+
: null),
|
|
197
|
+
deviceName: device.getName(),
|
|
198
|
+
reactNative: {
|
|
199
|
+
logicalDeviceId: deviceId,
|
|
200
|
+
capabilities: (0, _nullthrows.default)(page.capabilities),
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
#sendJsonResponse(response, object) {
|
|
205
|
+
const data = JSON.stringify(object, null, 2);
|
|
206
|
+
response.writeHead(200, {
|
|
207
|
+
"Content-Type": "application/json; charset=UTF-8",
|
|
208
|
+
"Cache-Control": "no-cache",
|
|
209
|
+
"Content-Length": Buffer.byteLength(data).toString(),
|
|
210
|
+
Connection: "close",
|
|
211
|
+
});
|
|
212
|
+
response.end(data);
|
|
213
|
+
}
|
|
214
|
+
#getTimeSinceLastCommunication() {
|
|
215
|
+
const timestamp = this.#lastMessageTimestamp;
|
|
216
|
+
return timestamp == null ? null : Date.now() - timestamp;
|
|
217
|
+
}
|
|
218
|
+
#onMessageFromDeviceOrDebugger(
|
|
219
|
+
message,
|
|
220
|
+
debuggerSessionIDs,
|
|
221
|
+
connectionUptime,
|
|
222
|
+
) {
|
|
223
|
+
if (message.includes('"event":"getPages"')) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
this.#lastMessageTimestamp = Date.now();
|
|
227
|
+
this.#eventLoopPerfTracker?.trackPerfThrottled(
|
|
228
|
+
debuggerSessionIDs,
|
|
229
|
+
connectionUptime,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
#createDeviceConnectionWSServer() {
|
|
233
|
+
const wss = new _ws.default.Server({
|
|
234
|
+
noServer: true,
|
|
235
|
+
perMessageDeflate: true,
|
|
236
|
+
maxPayload: 0,
|
|
237
|
+
});
|
|
238
|
+
wss.on("connection", async (socket, req) => {
|
|
239
|
+
const wssTimestamp = Date.now();
|
|
240
|
+
const fallbackDeviceId = String(this.#deviceCounter++);
|
|
241
|
+
const query = _url.default.parse(req.url || "", true).query || {};
|
|
242
|
+
const deviceId = query.device || fallbackDeviceId;
|
|
243
|
+
const deviceName = query.name || "Unknown";
|
|
244
|
+
const appName = query.app || "Unknown";
|
|
245
|
+
const isProfilingBuild = query.profiling === "true";
|
|
246
|
+
try {
|
|
247
|
+
const deviceRelativeBaseUrl =
|
|
248
|
+
(0, _getBaseUrlFromRequest.default)(req) ?? this.#serverBaseUrl;
|
|
249
|
+
const oldDevice = this.#devices.get(deviceId);
|
|
250
|
+
let newDevice;
|
|
251
|
+
const deviceOptions = {
|
|
252
|
+
id: deviceId,
|
|
253
|
+
name: deviceName,
|
|
254
|
+
app: appName,
|
|
255
|
+
socket,
|
|
256
|
+
eventReporter: this.#eventReporter,
|
|
257
|
+
createMessageMiddleware: this.#customMessageHandler,
|
|
258
|
+
deviceRelativeBaseUrl,
|
|
259
|
+
serverRelativeBaseUrl: this.#serverBaseUrl,
|
|
260
|
+
isProfilingBuild,
|
|
261
|
+
};
|
|
262
|
+
if (oldDevice) {
|
|
263
|
+
oldDevice.dangerouslyRecreateDevice(deviceOptions);
|
|
264
|
+
newDevice = oldDevice;
|
|
265
|
+
} else {
|
|
266
|
+
newDevice = new _Device.default(deviceOptions);
|
|
267
|
+
}
|
|
268
|
+
this.#devices.set(deviceId, newDevice);
|
|
269
|
+
debug(
|
|
270
|
+
"Got new device connection: name='%s', app=%s, device=%s, via=%s",
|
|
271
|
+
deviceName,
|
|
272
|
+
appName,
|
|
273
|
+
deviceId,
|
|
274
|
+
deviceRelativeBaseUrl.origin,
|
|
275
|
+
);
|
|
276
|
+
const debuggerSessionIDs = {
|
|
277
|
+
appId: newDevice?.getApp() || null,
|
|
278
|
+
deviceId,
|
|
279
|
+
deviceName: newDevice?.getName() || null,
|
|
280
|
+
pageId: null,
|
|
281
|
+
};
|
|
282
|
+
const heartbeat = new _InspectorProxyHeartbeat.default({
|
|
283
|
+
socket,
|
|
284
|
+
timeBetweenPings: HEARTBEAT_TIME_BETWEEN_PINGS_MS,
|
|
285
|
+
minHighPingToReport: MIN_PING_TO_REPORT,
|
|
286
|
+
timeoutMs: HEARTBEAT_TIMEOUT_MS,
|
|
287
|
+
onHighPing: (roundtripDuration) => {
|
|
288
|
+
debug(
|
|
289
|
+
"[high ping] [ Device ] %sms for app='%s' on device='%s'",
|
|
290
|
+
String(roundtripDuration).padStart(5),
|
|
291
|
+
debuggerSessionIDs.appId,
|
|
292
|
+
debuggerSessionIDs.deviceName,
|
|
293
|
+
);
|
|
294
|
+
this.#eventReporter?.logEvent({
|
|
295
|
+
type: "device_high_ping",
|
|
296
|
+
duration: roundtripDuration,
|
|
297
|
+
timeSinceLastCommunication: this.#getTimeSinceLastCommunication(),
|
|
298
|
+
connectionUptime: Date.now() - wssTimestamp,
|
|
299
|
+
...debuggerSessionIDs,
|
|
300
|
+
});
|
|
301
|
+
},
|
|
302
|
+
onTimeout: (roundtripDuration) => {
|
|
303
|
+
socket.terminate();
|
|
304
|
+
this.#logger?.error(
|
|
305
|
+
"[timeout] connection terminated with Device for app='%s' on device='%s' after not responding for %s seconds.",
|
|
306
|
+
debuggerSessionIDs.appId ?? "unknown",
|
|
307
|
+
debuggerSessionIDs.deviceName ?? "unknown",
|
|
308
|
+
String(roundtripDuration / 1000),
|
|
309
|
+
);
|
|
310
|
+
this.#eventReporter?.logEvent({
|
|
311
|
+
type: "device_timeout",
|
|
312
|
+
duration: roundtripDuration,
|
|
313
|
+
timeSinceLastCommunication: this.#getTimeSinceLastCommunication(),
|
|
314
|
+
connectionUptime: Date.now() - wssTimestamp,
|
|
315
|
+
...debuggerSessionIDs,
|
|
316
|
+
});
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
heartbeat.start();
|
|
320
|
+
socket.on("message", (message) =>
|
|
321
|
+
this.#onMessageFromDeviceOrDebugger(
|
|
322
|
+
message.toString(),
|
|
323
|
+
debuggerSessionIDs,
|
|
324
|
+
Date.now() - wssTimestamp,
|
|
325
|
+
),
|
|
326
|
+
);
|
|
327
|
+
socket.on("close", (code, reason) => {
|
|
328
|
+
debug(
|
|
329
|
+
"Connection closed to device='%s' for app='%s' with code='%s' and reason='%s'.",
|
|
330
|
+
deviceName,
|
|
331
|
+
appName,
|
|
332
|
+
String(code),
|
|
333
|
+
reason,
|
|
334
|
+
);
|
|
335
|
+
this.#eventReporter?.logEvent({
|
|
336
|
+
type: "device_connection_closed",
|
|
337
|
+
code,
|
|
338
|
+
reason,
|
|
339
|
+
timeSinceLastCommunication: this.#getTimeSinceLastCommunication(),
|
|
340
|
+
connectionUptime: Date.now() - wssTimestamp,
|
|
341
|
+
...debuggerSessionIDs,
|
|
342
|
+
});
|
|
343
|
+
if (this.#devices.get(deviceId)?.dangerouslyGetSocket() === socket) {
|
|
344
|
+
this.#devices.delete(deviceId);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
} catch (error) {
|
|
348
|
+
this.#logger?.error(
|
|
349
|
+
"Connection failed to be established with app='%s' on device='%s' with error:",
|
|
350
|
+
appName,
|
|
351
|
+
deviceName,
|
|
352
|
+
error,
|
|
353
|
+
);
|
|
354
|
+
socket.close(INTERNAL_ERROR_CODE, error?.toString() ?? "Unknown error");
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
return wss;
|
|
358
|
+
}
|
|
359
|
+
#createDebuggerConnectionWSServer() {
|
|
360
|
+
const wss = new _ws.default.Server({
|
|
361
|
+
noServer: true,
|
|
362
|
+
perMessageDeflate: false,
|
|
363
|
+
maxPayload: 0,
|
|
364
|
+
});
|
|
365
|
+
wss.on("connection", async (socket, req) => {
|
|
366
|
+
const wssTimestamp = Date.now();
|
|
367
|
+
const query = _url.default.parse(req.url || "", true).query || {};
|
|
368
|
+
const deviceId = query.device;
|
|
369
|
+
const pageId = query.page;
|
|
370
|
+
const debuggerRelativeBaseUrl =
|
|
371
|
+
(0, _getBaseUrlFromRequest.default)(req) ?? this.#serverBaseUrl;
|
|
372
|
+
const device = deviceId ? this.#devices.get(deviceId) : undefined;
|
|
373
|
+
const debuggerSessionIDs = {
|
|
374
|
+
appId: device?.getApp() || null,
|
|
375
|
+
deviceId,
|
|
376
|
+
deviceName: device?.getName() || null,
|
|
377
|
+
pageId,
|
|
378
|
+
};
|
|
379
|
+
try {
|
|
380
|
+
if (deviceId == null || pageId == null) {
|
|
381
|
+
throw new Error(INTERNAL_ERROR_MESSAGES.INCORRECT_URL);
|
|
382
|
+
}
|
|
383
|
+
if (device == null) {
|
|
384
|
+
throw new Error(INTERNAL_ERROR_MESSAGES.UNREGISTERED_DEVICE);
|
|
385
|
+
}
|
|
386
|
+
debug(
|
|
387
|
+
"Connection established to DevTools for app='%s' on device='%s'.",
|
|
388
|
+
device.getApp() || "unknown",
|
|
389
|
+
device.getName() || "unknown",
|
|
390
|
+
);
|
|
391
|
+
const heartbeat = new _InspectorProxyHeartbeat.default({
|
|
392
|
+
socket,
|
|
393
|
+
timeBetweenPings: HEARTBEAT_TIME_BETWEEN_PINGS_MS,
|
|
394
|
+
minHighPingToReport: MIN_PING_TO_REPORT,
|
|
395
|
+
timeoutMs: HEARTBEAT_TIMEOUT_MS,
|
|
396
|
+
onHighPing: (roundtripDuration) => {
|
|
397
|
+
debug(
|
|
398
|
+
"[high ping] [DevTools] %sms for app='%s' on device='%s'",
|
|
399
|
+
String(roundtripDuration).padStart(5),
|
|
400
|
+
debuggerSessionIDs.appId,
|
|
401
|
+
debuggerSessionIDs.deviceName,
|
|
402
|
+
);
|
|
403
|
+
this.#eventReporter?.logEvent({
|
|
404
|
+
type: "debugger_high_ping",
|
|
405
|
+
duration: roundtripDuration,
|
|
406
|
+
timeSinceLastCommunication: this.#getTimeSinceLastCommunication(),
|
|
407
|
+
connectionUptime: Date.now() - wssTimestamp,
|
|
408
|
+
...debuggerSessionIDs,
|
|
409
|
+
});
|
|
410
|
+
},
|
|
411
|
+
onTimeout: (roundtripDuration) => {
|
|
412
|
+
socket.terminate();
|
|
413
|
+
this.#logger?.error(
|
|
414
|
+
"[timeout] connection terminated with DevTools for app='%s' on device='%s' after not responding for %s seconds.",
|
|
415
|
+
debuggerSessionIDs.appId ?? "unknown",
|
|
416
|
+
debuggerSessionIDs.deviceName ?? "unknown",
|
|
417
|
+
String(roundtripDuration / 1000),
|
|
418
|
+
);
|
|
419
|
+
this.#eventReporter?.logEvent({
|
|
420
|
+
type: "debugger_timeout",
|
|
421
|
+
duration: roundtripDuration,
|
|
422
|
+
timeSinceLastCommunication: this.#getTimeSinceLastCommunication(),
|
|
423
|
+
connectionUptime: Date.now() - wssTimestamp,
|
|
424
|
+
...debuggerSessionIDs,
|
|
425
|
+
});
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
heartbeat.start();
|
|
429
|
+
socket.on("message", (message) =>
|
|
430
|
+
this.#onMessageFromDeviceOrDebugger(
|
|
431
|
+
message.toString(),
|
|
432
|
+
debuggerSessionIDs,
|
|
433
|
+
Date.now() - wssTimestamp,
|
|
434
|
+
),
|
|
435
|
+
);
|
|
436
|
+
device.handleDebuggerConnection(socket, pageId, {
|
|
437
|
+
debuggerRelativeBaseUrl,
|
|
438
|
+
userAgent: req.headers["user-agent"] ?? query.userAgent ?? null,
|
|
439
|
+
});
|
|
440
|
+
socket.on("close", (code, reason) => {
|
|
441
|
+
debug(
|
|
442
|
+
"Connection closed to DevTools for app='%s' on device='%s' with code='%s' and reason='%s'.",
|
|
443
|
+
device.getApp() || "unknown",
|
|
444
|
+
device.getName() || "unknown",
|
|
445
|
+
String(code),
|
|
446
|
+
reason,
|
|
447
|
+
);
|
|
448
|
+
this.#eventReporter?.logEvent({
|
|
449
|
+
type: "debugger_connection_closed",
|
|
450
|
+
code,
|
|
451
|
+
reason,
|
|
452
|
+
timeSinceLastCommunication: this.#getTimeSinceLastCommunication(),
|
|
453
|
+
connectionUptime: Date.now() - wssTimestamp,
|
|
454
|
+
...debuggerSessionIDs,
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
} catch (error) {
|
|
458
|
+
this.#logger?.error(
|
|
459
|
+
"Connection failed to be established with DevTools for app='%s' on device='%s' and device id='%s' with error:",
|
|
460
|
+
device?.getApp() || "unknown",
|
|
461
|
+
device?.getName() || "unknown",
|
|
462
|
+
deviceId,
|
|
463
|
+
error,
|
|
464
|
+
);
|
|
465
|
+
socket.close(INTERNAL_ERROR_CODE, error?.toString() ?? "Unknown error");
|
|
466
|
+
this.#eventReporter?.logEvent({
|
|
467
|
+
type: "connect_debugger_frontend",
|
|
468
|
+
status: "error",
|
|
469
|
+
error,
|
|
470
|
+
...debuggerSessionIDs,
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
return wss;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
exports.default = InspectorProxy;
|
|
@@ -0,0 +1,62 @@
|
|
|
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 type { Experiments } from "../types/Experiments";
|
|
13
|
+
import type { Logger } from "../types/Logger";
|
|
14
|
+
import type { CreateCustomMessageHandlerFn } from "./CustomMessageHandler";
|
|
15
|
+
import type { PageDescription } from "./types";
|
|
16
|
+
import type { IncomingMessage, ServerResponse } from "http";
|
|
17
|
+
|
|
18
|
+
import WS from "ws";
|
|
19
|
+
|
|
20
|
+
export type GetPageDescriptionsConfig = {
|
|
21
|
+
requestorRelativeBaseUrl: URL,
|
|
22
|
+
logNoPagesForConnectedDevice?: boolean,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export interface InspectorProxyQueries {
|
|
26
|
+
/**
|
|
27
|
+
* Returns list of page descriptions ordered by device connection order, then
|
|
28
|
+
* page addition order.
|
|
29
|
+
*/
|
|
30
|
+
getPageDescriptions(
|
|
31
|
+
config: GetPageDescriptionsConfig,
|
|
32
|
+
): Array<PageDescription>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Main Inspector Proxy class that connects JavaScript VM inside Android/iOS apps and JS debugger.
|
|
37
|
+
*/
|
|
38
|
+
declare export default class InspectorProxy implements InspectorProxyQueries {
|
|
39
|
+
constructor(
|
|
40
|
+
serverBaseUrl: string,
|
|
41
|
+
eventReporter: ?EventReporter,
|
|
42
|
+
experiments: Experiments,
|
|
43
|
+
logger?: Logger,
|
|
44
|
+
customMessageHandler: ?CreateCustomMessageHandlerFn,
|
|
45
|
+
trackEventLoopPerf?: boolean,
|
|
46
|
+
): void;
|
|
47
|
+
getPageDescriptions(
|
|
48
|
+
$$PARAM_0$$: GetPageDescriptionsConfig,
|
|
49
|
+
): Array<PageDescription>;
|
|
50
|
+
// Process HTTP request sent to server. We only respond to 2 HTTP requests:
|
|
51
|
+
// 1. /json/version returns Chrome debugger protocol version that we use
|
|
52
|
+
// 2. /json and /json/list returns list of page descriptions (list of inspectable apps).
|
|
53
|
+
// This list is combined from all the connected devices.
|
|
54
|
+
processRequest(
|
|
55
|
+
request: IncomingMessage,
|
|
56
|
+
response: ServerResponse,
|
|
57
|
+
next: (?Error) => unknown,
|
|
58
|
+
): void;
|
|
59
|
+
createWebSocketListeners(): {
|
|
60
|
+
[path: string]: WS.Server,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
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 WS from "ws";
|
|
12
|
+
export type HeartbeatTrackerArgs = {
|
|
13
|
+
socket: WS;
|
|
14
|
+
timeBetweenPings: number;
|
|
15
|
+
minHighPingToReport: number;
|
|
16
|
+
timeoutMs: number;
|
|
17
|
+
onTimeout: (roundtripDuration: number) => void;
|
|
18
|
+
onHighPing: (roundtripDuration: number) => void;
|
|
19
|
+
};
|
|
20
|
+
declare class InspectorProxyHeartbeat {
|
|
21
|
+
constructor(args: HeartbeatTrackerArgs);
|
|
22
|
+
start(): void;
|
|
23
|
+
}
|
|
24
|
+
export default InspectorProxyHeartbeat;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true,
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _timers = require("timers");
|
|
8
|
+
var _ws = _interopRequireDefault(require("ws"));
|
|
9
|
+
function _interopRequireDefault(e) {
|
|
10
|
+
return e && e.__esModule ? e : { default: e };
|
|
11
|
+
}
|
|
12
|
+
class InspectorProxyHeartbeat {
|
|
13
|
+
#socket;
|
|
14
|
+
#timeBetweenPings;
|
|
15
|
+
#minHighPingToReport;
|
|
16
|
+
#timeoutMs;
|
|
17
|
+
#onTimeout;
|
|
18
|
+
#onHighPing;
|
|
19
|
+
constructor(args) {
|
|
20
|
+
this.#socket = args.socket;
|
|
21
|
+
this.#timeBetweenPings = args.timeBetweenPings;
|
|
22
|
+
this.#minHighPingToReport = args.minHighPingToReport;
|
|
23
|
+
this.#timeoutMs = args.timeoutMs;
|
|
24
|
+
this.#onTimeout = args.onTimeout;
|
|
25
|
+
this.#onHighPing = args.onHighPing;
|
|
26
|
+
}
|
|
27
|
+
start() {
|
|
28
|
+
let latestPingMs = Date.now();
|
|
29
|
+
let terminateTimeout;
|
|
30
|
+
const pingTimeout = (0, _timers.setTimeout)(() => {
|
|
31
|
+
if (this.#socket.readyState !== _ws.default.OPEN) {
|
|
32
|
+
pingTimeout.refresh();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!terminateTimeout) {
|
|
36
|
+
terminateTimeout = (0, _timers.setTimeout)(() => {
|
|
37
|
+
if (this.#socket.readyState !== _ws.default.OPEN) {
|
|
38
|
+
terminateTimeout?.refresh();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
this.#onTimeout(this.#timeoutMs);
|
|
42
|
+
}, this.#timeoutMs).unref();
|
|
43
|
+
}
|
|
44
|
+
latestPingMs = Date.now();
|
|
45
|
+
this.#socket.ping();
|
|
46
|
+
}, this.#timeBetweenPings).unref();
|
|
47
|
+
this.#socket.on("pong", () => {
|
|
48
|
+
const roundtripDuration = Date.now() - latestPingMs;
|
|
49
|
+
if (roundtripDuration >= this.#minHighPingToReport) {
|
|
50
|
+
this.#onHighPing(roundtripDuration);
|
|
51
|
+
}
|
|
52
|
+
terminateTimeout?.refresh();
|
|
53
|
+
pingTimeout.refresh();
|
|
54
|
+
});
|
|
55
|
+
this.#socket.on("message", () => {
|
|
56
|
+
terminateTimeout?.refresh();
|
|
57
|
+
});
|
|
58
|
+
this.#socket.on("close", (code, reason) => {
|
|
59
|
+
terminateTimeout && (0, _timers.clearTimeout)(terminateTimeout);
|
|
60
|
+
(0, _timers.clearTimeout)(pingTimeout);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.default = InspectorProxyHeartbeat;
|
|
@@ -0,0 +1,25 @@
|
|
|
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 WS from "ws";
|
|
12
|
+
|
|
13
|
+
export type HeartbeatTrackerArgs = {
|
|
14
|
+
socket: WS,
|
|
15
|
+
timeBetweenPings: number,
|
|
16
|
+
minHighPingToReport: number,
|
|
17
|
+
timeoutMs: number,
|
|
18
|
+
onTimeout: (roundtripDuration: number) => void,
|
|
19
|
+
onHighPing: (roundtripDuration: number) => void,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
declare export default class InspectorProxyHeartbeat {
|
|
23
|
+
constructor(args: HeartbeatTrackerArgs): void;
|
|
24
|
+
start(): void;
|
|
25
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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 { JSONSerializable } from "../types";
|
|
12
|
+
import type { Commands, Events } from "./protocol";
|
|
13
|
+
export type CDPEvent<TEvent extends keyof Events = "unknown"> = {
|
|
14
|
+
method: TEvent;
|
|
15
|
+
params: Events[TEvent];
|
|
16
|
+
};
|
|
17
|
+
export type CDPRequest<TCommand extends keyof Commands = "unknown"> = {
|
|
18
|
+
method: TCommand;
|
|
19
|
+
params: Commands[TCommand]["paramsType"];
|
|
20
|
+
id: number;
|
|
21
|
+
};
|
|
22
|
+
export type CDPResponse<TCommand extends keyof Commands = "unknown"> =
|
|
23
|
+
| { result: Commands[TCommand]["resultType"]; id: number }
|
|
24
|
+
| { error: CDPRequestError; id: number };
|
|
25
|
+
export type CDPRequestError = {
|
|
26
|
+
code: number;
|
|
27
|
+
message: string;
|
|
28
|
+
data?: JSONSerializable;
|
|
29
|
+
};
|
|
30
|
+
export type CDPClientMessage =
|
|
31
|
+
| CDPRequest<"Debugger.getScriptSource">
|
|
32
|
+
| CDPRequest<"Debugger.scriptParsed">
|
|
33
|
+
| CDPRequest<"Debugger.setBreakpointByUrl">
|
|
34
|
+
| CDPRequest<"Network.loadNetworkResource">
|
|
35
|
+
| CDPRequest;
|
|
36
|
+
export type CDPServerMessage =
|
|
37
|
+
| CDPEvent<"Debugger.scriptParsed">
|
|
38
|
+
| CDPEvent<"Runtime.consoleAPICalled">
|
|
39
|
+
| CDPEvent
|
|
40
|
+
| CDPResponse<"Debugger.getScriptSource">
|
|
41
|
+
| CDPResponse;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|