@react-native/dev-middleware 0.74.0 → 0.74.2
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/dist/createDevMiddleware.d.ts +8 -1
- package/dist/createDevMiddleware.js +12 -9
- package/dist/createDevMiddleware.js.flow +9 -1
- package/dist/index.flow.d.ts +2 -0
- package/dist/index.flow.js +16 -0
- package/dist/index.flow.js.flow +3 -0
- package/dist/inspector-proxy/Device.d.ts +3 -55
- package/dist/inspector-proxy/Device.js +254 -166
- package/dist/inspector-proxy/Device.js.flow +3 -59
- package/dist/inspector-proxy/DeviceEventReporter.d.ts +2 -12
- package/dist/inspector-proxy/DeviceEventReporter.js +37 -35
- package/dist/inspector-proxy/DeviceEventReporter.js.flow +2 -17
- package/dist/inspector-proxy/InspectorProxy.d.ts +1 -24
- package/dist/inspector-proxy/InspectorProxy.js +41 -30
- package/dist/inspector-proxy/InspectorProxy.js.flow +1 -24
- package/dist/inspector-proxy/cdp-types/messages.d.ts +39 -0
- package/dist/inspector-proxy/cdp-types/messages.js +1 -0
- package/dist/inspector-proxy/cdp-types/messages.js.flow +52 -0
- package/dist/inspector-proxy/cdp-types/protocol.d.ts +87 -0
- package/dist/inspector-proxy/cdp-types/protocol.js +1 -0
- package/dist/inspector-proxy/cdp-types/protocol.js.flow +106 -0
- package/dist/inspector-proxy/types.d.ts +60 -45
- package/dist/inspector-proxy/types.js.flow +62 -70
- package/dist/middleware/deprecated_openFlipperMiddleware.d.ts +1 -1
- package/dist/middleware/deprecated_openFlipperMiddleware.js.flow +1 -2
- package/dist/middleware/openDebuggerMiddleware.d.ts +1 -1
- package/dist/middleware/openDebuggerMiddleware.js +23 -8
- package/dist/middleware/openDebuggerMiddleware.js.flow +1 -2
- package/dist/types/EventReporter.d.ts +3 -3
- package/dist/types/EventReporter.js.flow +1 -1
- package/dist/types/Experiments.d.ts +4 -0
- package/dist/types/Experiments.js.flow +5 -0
- package/dist/utils/DefaultBrowserLauncher.js +1 -1
- package/dist/utils/getDevToolsFrontendUrl.d.ts +2 -0
- package/dist/utils/getDevToolsFrontendUrl.js +20 -4
- package/dist/utils/getDevToolsFrontendUrl.js.flow +3 -0
- package/package.json +13 -5
|
@@ -8,8 +8,8 @@ var _DeviceEventReporter = _interopRequireDefault(
|
|
|
8
8
|
require("./DeviceEventReporter")
|
|
9
9
|
);
|
|
10
10
|
var fs = _interopRequireWildcard(require("fs"));
|
|
11
|
-
var path = _interopRequireWildcard(require("path"));
|
|
12
11
|
var _nodeFetch = _interopRequireDefault(require("node-fetch"));
|
|
12
|
+
var path = _interopRequireWildcard(require("path"));
|
|
13
13
|
var _ws = _interopRequireDefault(require("ws"));
|
|
14
14
|
function _getRequireWildcardCache(nodeInterop) {
|
|
15
15
|
if (typeof WeakMap !== "function") return null;
|
|
@@ -71,7 +71,7 @@ const PAGES_POLLING_INTERVAL = 1000;
|
|
|
71
71
|
// Android's stock emulator and other emulators such as genymotion use a standard localhost alias.
|
|
72
72
|
const EMULATOR_LOCALHOST_ADDRESSES = ["10.0.2.2", "10.0.3.2"];
|
|
73
73
|
|
|
74
|
-
// Prefix for script URLs that are alphanumeric IDs. See comment in
|
|
74
|
+
// Prefix for script URLs that are alphanumeric IDs. See comment in #processMessageFromDeviceLegacy method for
|
|
75
75
|
// more details.
|
|
76
76
|
const FILE_PREFIX = "file://";
|
|
77
77
|
const REACT_NATIVE_RELOADABLE_PAGE_ID = "-1";
|
|
@@ -82,42 +82,49 @@ const REACT_NATIVE_RELOADABLE_PAGE_ID = "-1";
|
|
|
82
82
|
*/
|
|
83
83
|
class Device {
|
|
84
84
|
// ID of the device.
|
|
85
|
+
#id;
|
|
85
86
|
|
|
86
87
|
// Name of the device.
|
|
88
|
+
#name;
|
|
87
89
|
|
|
88
90
|
// Package name of the app.
|
|
91
|
+
#app;
|
|
89
92
|
|
|
90
93
|
// Stores socket connection between Inspector Proxy and device.
|
|
94
|
+
#deviceSocket;
|
|
91
95
|
|
|
92
|
-
// Stores
|
|
96
|
+
// Stores the most recent listing of device's pages, keyed by the `id` field.
|
|
97
|
+
#pages;
|
|
93
98
|
|
|
94
99
|
// Stores information about currently connected debugger (if any).
|
|
95
|
-
|
|
100
|
+
#debuggerConnection = null;
|
|
96
101
|
|
|
97
102
|
// Last known Page ID of the React Native page.
|
|
98
103
|
// This is used by debugger connections that don't have PageID specified
|
|
99
104
|
// (and will interact with the latest React Native page).
|
|
100
|
-
|
|
105
|
+
#lastConnectedLegacyReactNativePage = null;
|
|
101
106
|
|
|
102
107
|
// Whether we are in the middle of a reload in the REACT_NATIVE_RELOADABLE_PAGE.
|
|
103
|
-
|
|
108
|
+
#isLegacyPageReloading = false;
|
|
104
109
|
|
|
105
110
|
// The previous "GetPages" message, for deduplication in debug logs.
|
|
106
|
-
|
|
111
|
+
#lastGetPagesMessage = "";
|
|
107
112
|
|
|
108
113
|
// Mapping built from scriptParsed events and used to fetch file content in `Debugger.getScriptSource`.
|
|
109
|
-
|
|
114
|
+
#scriptIdToSourcePathMapping = new Map();
|
|
110
115
|
|
|
111
116
|
// Root of the project used for relative to absolute source path conversion.
|
|
112
|
-
|
|
117
|
+
#projectRoot;
|
|
118
|
+
#deviceEventReporter;
|
|
119
|
+
#pagesPollingIntervalId;
|
|
113
120
|
constructor(id, name, app, socket, projectRoot, eventReporter) {
|
|
114
|
-
this
|
|
115
|
-
this
|
|
116
|
-
this
|
|
117
|
-
this
|
|
118
|
-
this
|
|
119
|
-
this
|
|
120
|
-
this
|
|
121
|
+
this.#id = id;
|
|
122
|
+
this.#name = name;
|
|
123
|
+
this.#app = app;
|
|
124
|
+
this.#pages = new Map();
|
|
125
|
+
this.#deviceSocket = socket;
|
|
126
|
+
this.#projectRoot = projectRoot;
|
|
127
|
+
this.#deviceEventReporter = eventReporter
|
|
121
128
|
? new _DeviceEventReporter.default(eventReporter, {
|
|
122
129
|
deviceId: id,
|
|
123
130
|
deviceName: name,
|
|
@@ -126,49 +133,58 @@ class Device {
|
|
|
126
133
|
: null;
|
|
127
134
|
|
|
128
135
|
// $FlowFixMe[incompatible-call]
|
|
129
|
-
this.
|
|
136
|
+
this.#deviceSocket.on("message", (message) => {
|
|
130
137
|
const parsedMessage = JSON.parse(message);
|
|
131
138
|
if (parsedMessage.event === "getPages") {
|
|
132
139
|
// There's a 'getPages' message every second, so only show them if they change
|
|
133
|
-
if (message !== this
|
|
140
|
+
if (message !== this.#lastGetPagesMessage) {
|
|
134
141
|
debug(
|
|
135
142
|
"(Debugger) (Proxy) <- (Device), getPages ping has changed: " +
|
|
136
143
|
message
|
|
137
144
|
);
|
|
138
|
-
this
|
|
145
|
+
this.#lastGetPagesMessage = message;
|
|
139
146
|
}
|
|
140
147
|
} else {
|
|
141
148
|
debug("(Debugger) (Proxy) <- (Device): " + message);
|
|
142
149
|
}
|
|
143
|
-
this
|
|
150
|
+
this.#handleMessageFromDevice(parsedMessage);
|
|
144
151
|
});
|
|
145
|
-
|
|
146
|
-
|
|
152
|
+
// Sends 'getPages' request to device every PAGES_POLLING_INTERVAL milliseconds.
|
|
153
|
+
this.#pagesPollingIntervalId = setInterval(
|
|
154
|
+
() =>
|
|
155
|
+
this.#sendMessageToDevice({
|
|
156
|
+
event: "getPages",
|
|
157
|
+
}),
|
|
158
|
+
PAGES_POLLING_INTERVAL
|
|
159
|
+
);
|
|
160
|
+
this.#deviceSocket.on("close", () => {
|
|
161
|
+
this.#deviceEventReporter?.logDisconnection("device");
|
|
147
162
|
// Device disconnected - close debugger connection.
|
|
148
|
-
if (this
|
|
149
|
-
this.
|
|
150
|
-
this
|
|
163
|
+
if (this.#debuggerConnection) {
|
|
164
|
+
this.#debuggerConnection.socket.close();
|
|
165
|
+
this.#debuggerConnection = null;
|
|
151
166
|
}
|
|
167
|
+
clearInterval(this.#pagesPollingIntervalId);
|
|
152
168
|
});
|
|
153
|
-
this._setPagesPolling();
|
|
154
169
|
}
|
|
155
170
|
getName() {
|
|
156
|
-
return this
|
|
171
|
+
return this.#name;
|
|
157
172
|
}
|
|
158
173
|
getApp() {
|
|
159
|
-
return this
|
|
174
|
+
return this.#app;
|
|
160
175
|
}
|
|
161
176
|
getPagesList() {
|
|
162
|
-
if (this
|
|
177
|
+
if (this.#lastConnectedLegacyReactNativePage) {
|
|
163
178
|
const reactNativeReloadablePage = {
|
|
164
179
|
id: REACT_NATIVE_RELOADABLE_PAGE_ID,
|
|
165
180
|
title: "React Native Experimental (Improved Chrome Reloads)",
|
|
166
181
|
vm: "don't use",
|
|
167
|
-
app: this
|
|
182
|
+
app: this.#app,
|
|
183
|
+
capabilities: {},
|
|
168
184
|
};
|
|
169
|
-
return this.
|
|
185
|
+
return [...this.#pages.values(), reactNativeReloadablePage];
|
|
170
186
|
} else {
|
|
171
|
-
return this.
|
|
187
|
+
return [...this.#pages.values()];
|
|
172
188
|
}
|
|
173
189
|
}
|
|
174
190
|
|
|
@@ -178,16 +194,16 @@ class Device {
|
|
|
178
194
|
// 3. Sends disconnect event to device when debugger connection socket closes.
|
|
179
195
|
handleDebuggerConnection(socket, pageId, metadata) {
|
|
180
196
|
// Clear any commands we were waiting on.
|
|
181
|
-
this
|
|
182
|
-
this
|
|
197
|
+
this.#deviceEventReporter?.logDisconnection("debugger");
|
|
198
|
+
this.#deviceEventReporter?.logConnection("debugger", {
|
|
183
199
|
pageId,
|
|
184
200
|
frontendUserAgent: metadata.userAgent,
|
|
185
201
|
});
|
|
186
202
|
|
|
187
203
|
// Disconnect current debugger if we already have debugger connected.
|
|
188
|
-
if (this
|
|
189
|
-
this.
|
|
190
|
-
this
|
|
204
|
+
if (this.#debuggerConnection) {
|
|
205
|
+
this.#debuggerConnection.socket.close();
|
|
206
|
+
this.#debuggerConnection = null;
|
|
191
207
|
}
|
|
192
208
|
const debuggerInfo = {
|
|
193
209
|
socket,
|
|
@@ -195,12 +211,16 @@ class Device {
|
|
|
195
211
|
pageId,
|
|
196
212
|
userAgent: metadata.userAgent,
|
|
197
213
|
};
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
214
|
+
|
|
215
|
+
// TODO(moti): Handle null case explicitly, e.g. refuse to connect to
|
|
216
|
+
// unknown pages.
|
|
217
|
+
const page = this.#pages.get(pageId);
|
|
218
|
+
this.#debuggerConnection = debuggerInfo;
|
|
219
|
+
debug(`Got new debugger connection for page ${pageId} of ${this.#name}`);
|
|
220
|
+
this.#sendMessageToDevice({
|
|
201
221
|
event: "connect",
|
|
202
222
|
payload: {
|
|
203
|
-
pageId: this
|
|
223
|
+
pageId: this.#mapToDevicePageId(pageId),
|
|
204
224
|
},
|
|
205
225
|
});
|
|
206
226
|
|
|
@@ -208,35 +228,38 @@ class Device {
|
|
|
208
228
|
socket.on("message", (message) => {
|
|
209
229
|
debug("(Debugger) -> (Proxy) (Device): " + message);
|
|
210
230
|
const debuggerRequest = JSON.parse(message);
|
|
211
|
-
this
|
|
212
|
-
pageId: this
|
|
231
|
+
this.#deviceEventReporter?.logRequest(debuggerRequest, "debugger", {
|
|
232
|
+
pageId: this.#debuggerConnection?.pageId ?? null,
|
|
213
233
|
frontendUserAgent: metadata.userAgent,
|
|
214
234
|
});
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
235
|
+
let processedReq = debuggerRequest;
|
|
236
|
+
if (!page || !this.#pageHasCapability(page, "nativeSourceCodeFetching")) {
|
|
237
|
+
processedReq = this.#interceptClientMessageForSourceFetching(
|
|
238
|
+
debuggerRequest,
|
|
239
|
+
debuggerInfo,
|
|
240
|
+
socket
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
if (processedReq) {
|
|
244
|
+
this.#sendMessageToDevice({
|
|
222
245
|
event: "wrappedEvent",
|
|
223
246
|
payload: {
|
|
224
|
-
pageId: this
|
|
225
|
-
wrappedEvent: JSON.stringify(
|
|
247
|
+
pageId: this.#mapToDevicePageId(pageId),
|
|
248
|
+
wrappedEvent: JSON.stringify(processedReq),
|
|
226
249
|
},
|
|
227
250
|
});
|
|
228
251
|
}
|
|
229
252
|
});
|
|
230
253
|
socket.on("close", () => {
|
|
231
|
-
debug(`Debugger for page ${pageId} and ${this
|
|
232
|
-
this
|
|
233
|
-
this
|
|
254
|
+
debug(`Debugger for page ${pageId} and ${this.#name} disconnected.`);
|
|
255
|
+
this.#deviceEventReporter?.logDisconnection("debugger");
|
|
256
|
+
this.#sendMessageToDevice({
|
|
234
257
|
event: "disconnect",
|
|
235
258
|
payload: {
|
|
236
|
-
pageId: this
|
|
259
|
+
pageId: this.#mapToDevicePageId(pageId),
|
|
237
260
|
},
|
|
238
261
|
});
|
|
239
|
-
this
|
|
262
|
+
this.#debuggerConnection = null;
|
|
240
263
|
});
|
|
241
264
|
|
|
242
265
|
// $FlowFixMe[method-unbinding]
|
|
@@ -259,17 +282,17 @@ class Device {
|
|
|
259
282
|
*/
|
|
260
283
|
handleDuplicateDeviceConnection(newDevice) {
|
|
261
284
|
if (
|
|
262
|
-
this
|
|
263
|
-
this
|
|
285
|
+
this.#app !== newDevice.getApp() ||
|
|
286
|
+
this.#name !== newDevice.getName()
|
|
264
287
|
) {
|
|
265
|
-
this.
|
|
266
|
-
this
|
|
288
|
+
this.#deviceSocket.close();
|
|
289
|
+
this.#debuggerConnection?.socket.close();
|
|
267
290
|
}
|
|
268
|
-
const oldDebugger = this
|
|
269
|
-
this
|
|
291
|
+
const oldDebugger = this.#debuggerConnection;
|
|
292
|
+
this.#debuggerConnection = null;
|
|
270
293
|
if (oldDebugger) {
|
|
271
294
|
oldDebugger.socket.removeAllListeners();
|
|
272
|
-
this.
|
|
295
|
+
this.#deviceSocket.close();
|
|
273
296
|
newDevice.handleDebuggerConnection(
|
|
274
297
|
oldDebugger.socket,
|
|
275
298
|
oldDebugger.pageId,
|
|
@@ -280,26 +303,60 @@ class Device {
|
|
|
280
303
|
}
|
|
281
304
|
}
|
|
282
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Returns `true` if a page supports the given target capability flag.
|
|
308
|
+
*/
|
|
309
|
+
#pageHasCapability(page, flag) {
|
|
310
|
+
return page.capabilities[flag] === true;
|
|
311
|
+
}
|
|
312
|
+
|
|
283
313
|
// Handles messages received from device:
|
|
284
|
-
// 1. For getPages responses updates local
|
|
314
|
+
// 1. For getPages responses updates local #pages list.
|
|
285
315
|
// 2. All other messages are forwarded to debugger as wrappedEvent.
|
|
286
316
|
//
|
|
287
317
|
// In the future more logic will be added to this method for modifying
|
|
288
318
|
// some of the messages (like updating messages with source maps and file
|
|
289
319
|
// locations).
|
|
290
|
-
|
|
320
|
+
#handleMessageFromDevice(message) {
|
|
291
321
|
if (message.event === "getPages") {
|
|
292
|
-
this
|
|
322
|
+
this.#pages = new Map(
|
|
323
|
+
message.payload.map(({ capabilities, ...page }) => [
|
|
324
|
+
page.id,
|
|
325
|
+
{
|
|
326
|
+
...page,
|
|
327
|
+
capabilities: capabilities ?? {},
|
|
328
|
+
},
|
|
329
|
+
])
|
|
330
|
+
);
|
|
331
|
+
if (message.payload.length !== this.#pages.size) {
|
|
332
|
+
const duplicateIds = new Set();
|
|
333
|
+
const idsSeen = new Set();
|
|
334
|
+
for (const page of message.payload) {
|
|
335
|
+
if (!idsSeen.has(page.id)) {
|
|
336
|
+
idsSeen.add(page.id);
|
|
337
|
+
} else {
|
|
338
|
+
duplicateIds.add(page.id);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
debug(
|
|
342
|
+
`Received duplicate page IDs from device: ${[...duplicateIds].join(
|
|
343
|
+
", "
|
|
344
|
+
)}`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
293
347
|
|
|
294
|
-
// Check if device
|
|
348
|
+
// Check if device has a new legacy React Native page.
|
|
295
349
|
// There is usually no more than 2-3 pages per device so this operation
|
|
296
350
|
// is not expensive.
|
|
297
351
|
// TODO(hypuk): It is better for VM to send update event when new page is
|
|
298
352
|
// created instead of manually checking this on every getPages result.
|
|
299
|
-
for (
|
|
300
|
-
if (this
|
|
301
|
-
|
|
302
|
-
|
|
353
|
+
for (const page of this.#pages.values()) {
|
|
354
|
+
if (this.#pageHasCapability(page, "nativePageReloads")) {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
if (page.title.includes("React")) {
|
|
358
|
+
if (page.id !== this.#lastConnectedLegacyReactNativePage?.id) {
|
|
359
|
+
this.#newLegacyReactNativePage(page);
|
|
303
360
|
break;
|
|
304
361
|
}
|
|
305
362
|
}
|
|
@@ -308,15 +365,21 @@ class Device {
|
|
|
308
365
|
// Device sends disconnect events only when page is reloaded or
|
|
309
366
|
// if debugger socket was disconnected.
|
|
310
367
|
const pageId = message.payload.pageId;
|
|
311
|
-
|
|
312
|
-
|
|
368
|
+
// TODO(moti): Handle null case explicitly, e.g. swallow disconnect events
|
|
369
|
+
// for unknown pages.
|
|
370
|
+
const page = this.#pages.get(pageId);
|
|
371
|
+
if (page != null && this.#pageHasCapability(page, "nativePageReloads")) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const debuggerSocket = this.#debuggerConnection
|
|
375
|
+
? this.#debuggerConnection.socket
|
|
313
376
|
: null;
|
|
314
377
|
if (debuggerSocket && debuggerSocket.readyState === _ws.default.OPEN) {
|
|
315
378
|
if (
|
|
316
|
-
this
|
|
317
|
-
this.
|
|
379
|
+
this.#debuggerConnection != null &&
|
|
380
|
+
this.#debuggerConnection.pageId !== REACT_NATIVE_RELOADABLE_PAGE_ID
|
|
318
381
|
) {
|
|
319
|
-
debug(`
|
|
382
|
+
debug(`Legacy page ${pageId} is reloading.`);
|
|
320
383
|
debuggerSocket.send(
|
|
321
384
|
JSON.stringify({
|
|
322
385
|
method: "reload",
|
|
@@ -325,14 +388,15 @@ class Device {
|
|
|
325
388
|
}
|
|
326
389
|
}
|
|
327
390
|
} else if (message.event === "wrappedEvent") {
|
|
328
|
-
if (this
|
|
391
|
+
if (this.#debuggerConnection == null) {
|
|
329
392
|
return;
|
|
330
393
|
}
|
|
331
394
|
|
|
332
395
|
// FIXME: Is it possible that we received message for pageID that does not
|
|
333
396
|
// correspond to current debugger connection?
|
|
397
|
+
// TODO(moti): yes, fix multi-debugger case
|
|
334
398
|
|
|
335
|
-
const debuggerSocket = this.
|
|
399
|
+
const debuggerSocket = this.#debuggerConnection.socket;
|
|
336
400
|
if (
|
|
337
401
|
debuggerSocket == null ||
|
|
338
402
|
debuggerSocket.readyState !== _ws.default.OPEN
|
|
@@ -341,63 +405,56 @@ class Device {
|
|
|
341
405
|
return;
|
|
342
406
|
}
|
|
343
407
|
const parsedPayload = JSON.parse(message.payload.wrappedEvent);
|
|
408
|
+
const pageId = this.#debuggerConnection?.pageId ?? null;
|
|
344
409
|
if ("id" in parsedPayload) {
|
|
345
|
-
this
|
|
346
|
-
pageId
|
|
347
|
-
frontendUserAgent: this
|
|
410
|
+
this.#deviceEventReporter?.logResponse(parsedPayload, "device", {
|
|
411
|
+
pageId,
|
|
412
|
+
frontendUserAgent: this.#debuggerConnection?.userAgent ?? null,
|
|
348
413
|
});
|
|
349
414
|
}
|
|
350
|
-
if (this
|
|
415
|
+
if (this.#debuggerConnection != null) {
|
|
351
416
|
// Wrapping just to make flow happy :)
|
|
352
417
|
// $FlowFixMe[unused-promise]
|
|
353
|
-
this
|
|
418
|
+
this.#processMessageFromDeviceLegacy(
|
|
354
419
|
parsedPayload,
|
|
355
|
-
this
|
|
420
|
+
this.#debuggerConnection,
|
|
421
|
+
pageId
|
|
356
422
|
).then(() => {
|
|
357
423
|
const messageToSend = JSON.stringify(parsedPayload);
|
|
358
424
|
debuggerSocket.send(messageToSend);
|
|
359
425
|
});
|
|
426
|
+
} else {
|
|
427
|
+
debuggerSocket.send(message.payload.wrappedEvent);
|
|
360
428
|
}
|
|
361
429
|
}
|
|
362
430
|
}
|
|
363
431
|
|
|
364
432
|
// Sends single message to device.
|
|
365
|
-
|
|
433
|
+
#sendMessageToDevice(message) {
|
|
366
434
|
try {
|
|
367
435
|
if (message.event !== "getPages") {
|
|
368
436
|
debug("(Debugger) (Proxy) -> (Device): " + JSON.stringify(message));
|
|
369
437
|
}
|
|
370
|
-
this.
|
|
438
|
+
this.#deviceSocket.send(JSON.stringify(message));
|
|
371
439
|
} catch (error) {}
|
|
372
440
|
}
|
|
373
441
|
|
|
374
|
-
// Sends 'getPages' request to device every PAGES_POLLING_INTERVAL milliseconds.
|
|
375
|
-
_setPagesPolling() {
|
|
376
|
-
setInterval(
|
|
377
|
-
() =>
|
|
378
|
-
this._sendMessageToDevice({
|
|
379
|
-
event: "getPages",
|
|
380
|
-
}),
|
|
381
|
-
PAGES_POLLING_INTERVAL
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
442
|
// We received new React Native Page ID.
|
|
386
|
-
|
|
443
|
+
#newLegacyReactNativePage(page) {
|
|
387
444
|
debug(`React Native page updated to ${page.id}`);
|
|
388
445
|
if (
|
|
389
|
-
this
|
|
390
|
-
this.
|
|
446
|
+
this.#debuggerConnection == null ||
|
|
447
|
+
this.#debuggerConnection.pageId !== REACT_NATIVE_RELOADABLE_PAGE_ID
|
|
391
448
|
) {
|
|
392
449
|
// We can just remember new page ID without any further actions if no
|
|
393
450
|
// debugger is currently attached or attached debugger is not
|
|
394
451
|
// "Reloadable React Native" connection.
|
|
395
|
-
this
|
|
452
|
+
this.#lastConnectedLegacyReactNativePage = page;
|
|
396
453
|
return;
|
|
397
454
|
}
|
|
398
|
-
const oldPageId = this
|
|
399
|
-
this
|
|
400
|
-
this
|
|
455
|
+
const oldPageId = this.#lastConnectedLegacyReactNativePage?.id;
|
|
456
|
+
this.#lastConnectedLegacyReactNativePage = page;
|
|
457
|
+
this.#isLegacyPageReloading = true;
|
|
401
458
|
|
|
402
459
|
// We already had a debugger connected to React Native page and a
|
|
403
460
|
// new one appeared - in this case we need to emulate execution context
|
|
@@ -405,14 +462,14 @@ class Device {
|
|
|
405
462
|
// page.
|
|
406
463
|
|
|
407
464
|
if (oldPageId != null) {
|
|
408
|
-
this
|
|
465
|
+
this.#sendMessageToDevice({
|
|
409
466
|
event: "disconnect",
|
|
410
467
|
payload: {
|
|
411
468
|
pageId: oldPageId,
|
|
412
469
|
},
|
|
413
470
|
});
|
|
414
471
|
}
|
|
415
|
-
this
|
|
472
|
+
this.#sendMessageToDevice({
|
|
416
473
|
event: "connect",
|
|
417
474
|
payload: {
|
|
418
475
|
pageId: page.id,
|
|
@@ -429,14 +486,14 @@ class Device {
|
|
|
429
486
|
},
|
|
430
487
|
];
|
|
431
488
|
for (const message of toSend) {
|
|
432
|
-
this
|
|
433
|
-
pageId: this
|
|
434
|
-
frontendUserAgent: this
|
|
489
|
+
this.#deviceEventReporter?.logRequest(message, "proxy", {
|
|
490
|
+
pageId: this.#debuggerConnection?.pageId ?? null,
|
|
491
|
+
frontendUserAgent: this.#debuggerConnection?.userAgent ?? null,
|
|
435
492
|
});
|
|
436
|
-
this
|
|
493
|
+
this.#sendMessageToDevice({
|
|
437
494
|
event: "wrappedEvent",
|
|
438
495
|
payload: {
|
|
439
|
-
pageId: this
|
|
496
|
+
pageId: this.#mapToDevicePageId(page.id),
|
|
440
497
|
wrappedEvent: JSON.stringify(message),
|
|
441
498
|
},
|
|
442
499
|
});
|
|
@@ -444,14 +501,24 @@ class Device {
|
|
|
444
501
|
}
|
|
445
502
|
|
|
446
503
|
// Allows to make changes in incoming message from device.
|
|
447
|
-
async
|
|
504
|
+
async #processMessageFromDeviceLegacy(payload, debuggerInfo, pageId) {
|
|
505
|
+
// TODO(moti): Handle null case explicitly, or ideally associate a copy
|
|
506
|
+
// of the page metadata object with the connection so this can never be
|
|
507
|
+
// null.
|
|
508
|
+
const page = pageId != null ? this.#pages.get(pageId) : null;
|
|
509
|
+
|
|
448
510
|
// Replace Android addresses for scriptParsed event.
|
|
449
|
-
if (
|
|
450
|
-
|
|
511
|
+
if (
|
|
512
|
+
(!page || !this.#pageHasCapability(page, "nativeSourceCodeFetching")) &&
|
|
513
|
+
payload.method === "Debugger.scriptParsed" &&
|
|
514
|
+
payload.params != null
|
|
515
|
+
) {
|
|
516
|
+
const params = payload.params;
|
|
451
517
|
if ("sourceMapURL" in params) {
|
|
452
518
|
for (let i = 0; i < EMULATOR_LOCALHOST_ADDRESSES.length; ++i) {
|
|
453
519
|
const address = EMULATOR_LOCALHOST_ADDRESSES[i];
|
|
454
|
-
if (params.sourceMapURL.
|
|
520
|
+
if (params.sourceMapURL.includes(address)) {
|
|
521
|
+
// $FlowFixMe[cannot-write]
|
|
455
522
|
payload.params.sourceMapURL = params.sourceMapURL.replace(
|
|
456
523
|
address,
|
|
457
524
|
"localhost"
|
|
@@ -459,7 +526,7 @@ class Device {
|
|
|
459
526
|
debuggerInfo.originalSourceURLAddress = address;
|
|
460
527
|
}
|
|
461
528
|
}
|
|
462
|
-
const sourceMapURL = this
|
|
529
|
+
const sourceMapURL = this.#tryParseHTTPURL(params.sourceMapURL);
|
|
463
530
|
if (sourceMapURL) {
|
|
464
531
|
// Some debug clients do not support fetching HTTP URLs. If the
|
|
465
532
|
// message headed to the debug client identifies the source map with
|
|
@@ -467,12 +534,13 @@ class Device {
|
|
|
467
534
|
// Data URL (which is more widely supported) before passing the
|
|
468
535
|
// message to the debug client.
|
|
469
536
|
try {
|
|
470
|
-
const sourceMap = await this
|
|
537
|
+
const sourceMap = await this.#fetchText(sourceMapURL);
|
|
538
|
+
// $FlowFixMe[cannot-write]
|
|
471
539
|
payload.params.sourceMapURL =
|
|
472
540
|
"data:application/json;charset=utf-8;base64," +
|
|
473
541
|
new Buffer(sourceMap).toString("base64");
|
|
474
542
|
} catch (exception) {
|
|
475
|
-
this
|
|
543
|
+
this.#sendErrorToDebugger(
|
|
476
544
|
`Failed to fetch source map ${params.sourceMapURL}: ${exception.message}`
|
|
477
545
|
);
|
|
478
546
|
}
|
|
@@ -482,6 +550,7 @@ class Device {
|
|
|
482
550
|
for (let i = 0; i < EMULATOR_LOCALHOST_ADDRESSES.length; ++i) {
|
|
483
551
|
const address = EMULATOR_LOCALHOST_ADDRESSES[i];
|
|
484
552
|
if (params.url.indexOf(address) >= 0) {
|
|
553
|
+
// $FlowFixMe[cannot-write]
|
|
485
554
|
payload.params.url = params.url.replace(address, "localhost");
|
|
486
555
|
debuggerInfo.originalSourceURLAddress = address;
|
|
487
556
|
}
|
|
@@ -492,19 +561,20 @@ class Device {
|
|
|
492
561
|
// Chrome to not download source maps. In this case we want to prepend script ID
|
|
493
562
|
// with 'file://' prefix.
|
|
494
563
|
if (payload.params.url.match(/^[0-9a-z]+$/)) {
|
|
564
|
+
// $FlowFixMe[cannot-write]
|
|
495
565
|
payload.params.url = FILE_PREFIX + payload.params.url;
|
|
496
566
|
debuggerInfo.prependedFilePrefix = true;
|
|
497
567
|
}
|
|
498
568
|
|
|
499
569
|
// $FlowFixMe[prop-missing]
|
|
500
570
|
if (params.scriptId != null) {
|
|
501
|
-
this.
|
|
571
|
+
this.#scriptIdToSourcePathMapping.set(params.scriptId, params.url);
|
|
502
572
|
}
|
|
503
573
|
}
|
|
504
574
|
}
|
|
505
575
|
if (
|
|
506
576
|
payload.method === "Runtime.executionContextCreated" &&
|
|
507
|
-
this
|
|
577
|
+
this.#isLegacyPageReloading
|
|
508
578
|
) {
|
|
509
579
|
// The new context is ready. First notify Chrome that we've reloaded so
|
|
510
580
|
// it'll resend its breakpoints. If we do this earlier, we may not be
|
|
@@ -526,61 +596,76 @@ class Device {
|
|
|
526
596
|
method: "Debugger.resume",
|
|
527
597
|
id: 0,
|
|
528
598
|
};
|
|
529
|
-
this
|
|
530
|
-
pageId: this
|
|
531
|
-
frontendUserAgent: this
|
|
599
|
+
this.#deviceEventReporter?.logRequest(resumeMessage, "proxy", {
|
|
600
|
+
pageId: this.#debuggerConnection?.pageId ?? null,
|
|
601
|
+
frontendUserAgent: this.#debuggerConnection?.userAgent ?? null,
|
|
532
602
|
});
|
|
533
|
-
this
|
|
603
|
+
this.#sendMessageToDevice({
|
|
534
604
|
event: "wrappedEvent",
|
|
535
605
|
payload: {
|
|
536
|
-
pageId: this
|
|
606
|
+
pageId: this.#mapToDevicePageId(debuggerInfo.pageId),
|
|
537
607
|
wrappedEvent: JSON.stringify(resumeMessage),
|
|
538
608
|
},
|
|
539
609
|
});
|
|
540
|
-
this
|
|
610
|
+
this.#isLegacyPageReloading = false;
|
|
541
611
|
}
|
|
542
612
|
}
|
|
543
613
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
614
|
+
/**
|
|
615
|
+
* Intercept an incoming message from a connected debugger. Returns either an
|
|
616
|
+
* original/replacement CDP message object, or `null` (will forward nothing
|
|
617
|
+
* to the target).
|
|
618
|
+
*/
|
|
619
|
+
#interceptClientMessageForSourceFetching(req, debuggerInfo, socket) {
|
|
620
|
+
switch (req.method) {
|
|
621
|
+
case "Debugger.setBreakpointByUrl":
|
|
622
|
+
return this.#processDebuggerSetBreakpointByUrl(req, debuggerInfo);
|
|
623
|
+
case "Debugger.getScriptSource":
|
|
624
|
+
// Sends response to debugger via side-effect
|
|
625
|
+
this.#processDebuggerGetScriptSource(req, socket);
|
|
626
|
+
return null;
|
|
627
|
+
default:
|
|
628
|
+
return req;
|
|
553
629
|
}
|
|
554
|
-
return false;
|
|
555
630
|
}
|
|
556
|
-
|
|
631
|
+
#processDebuggerSetBreakpointByUrl(req, debuggerInfo) {
|
|
557
632
|
// If we replaced Android emulator's address to localhost we need to change it back.
|
|
558
633
|
if (debuggerInfo.originalSourceURLAddress != null) {
|
|
559
|
-
|
|
560
|
-
req
|
|
634
|
+
const processedReq = {
|
|
635
|
+
...req,
|
|
636
|
+
params: {
|
|
637
|
+
...req.params,
|
|
638
|
+
},
|
|
639
|
+
};
|
|
640
|
+
if (processedReq.params.url != null) {
|
|
641
|
+
processedReq.params.url = processedReq.params.url.replace(
|
|
561
642
|
"localhost",
|
|
562
643
|
debuggerInfo.originalSourceURLAddress
|
|
563
644
|
);
|
|
564
645
|
if (
|
|
565
|
-
|
|
566
|
-
|
|
646
|
+
processedReq.params.url &&
|
|
647
|
+
processedReq.params.url.startsWith(FILE_PREFIX) &&
|
|
567
648
|
debuggerInfo.prependedFilePrefix
|
|
568
649
|
) {
|
|
569
|
-
// Remove fake URL prefix if we modified URL in
|
|
650
|
+
// Remove fake URL prefix if we modified URL in #processMessageFromDeviceLegacy.
|
|
570
651
|
// $FlowFixMe[incompatible-use]
|
|
571
|
-
|
|
652
|
+
processedReq.params.url = processedReq.params.url.slice(
|
|
653
|
+
FILE_PREFIX.length
|
|
654
|
+
);
|
|
572
655
|
}
|
|
573
656
|
}
|
|
574
|
-
if (
|
|
575
|
-
|
|
657
|
+
if (processedReq.params.urlRegex != null) {
|
|
658
|
+
processedReq.params.urlRegex = processedReq.params.urlRegex.replace(
|
|
576
659
|
/localhost/g,
|
|
577
660
|
// $FlowFixMe[incompatible-call]
|
|
578
661
|
debuggerInfo.originalSourceURLAddress
|
|
579
662
|
);
|
|
580
663
|
}
|
|
664
|
+
return processedReq;
|
|
581
665
|
}
|
|
666
|
+
return req;
|
|
582
667
|
}
|
|
583
|
-
|
|
668
|
+
#processDebuggerGetScriptSource(req, socket) {
|
|
584
669
|
const sendSuccessResponse = (scriptSource) => {
|
|
585
670
|
const response = {
|
|
586
671
|
id: req.id,
|
|
@@ -589,9 +674,9 @@ class Device {
|
|
|
589
674
|
},
|
|
590
675
|
};
|
|
591
676
|
socket.send(JSON.stringify(response));
|
|
592
|
-
this
|
|
593
|
-
pageId: this
|
|
594
|
-
frontendUserAgent: this
|
|
677
|
+
this.#deviceEventReporter?.logResponse(response, "proxy", {
|
|
678
|
+
pageId: this.#debuggerConnection?.pageId ?? null,
|
|
679
|
+
frontendUserAgent: this.#debuggerConnection?.userAgent ?? null,
|
|
595
680
|
});
|
|
596
681
|
};
|
|
597
682
|
const sendErrorResponse = (error) => {
|
|
@@ -608,19 +693,19 @@ class Device {
|
|
|
608
693
|
socket.send(JSON.stringify(response));
|
|
609
694
|
|
|
610
695
|
// Send to the console as well, so the user can see it
|
|
611
|
-
this
|
|
612
|
-
this
|
|
613
|
-
pageId: this
|
|
614
|
-
frontendUserAgent: this
|
|
696
|
+
this.#sendErrorToDebugger(error);
|
|
697
|
+
this.#deviceEventReporter?.logResponse(response, "proxy", {
|
|
698
|
+
pageId: this.#debuggerConnection?.pageId ?? null,
|
|
699
|
+
frontendUserAgent: this.#debuggerConnection?.userAgent ?? null,
|
|
615
700
|
});
|
|
616
701
|
};
|
|
617
|
-
const pathToSource = this.
|
|
702
|
+
const pathToSource = this.#scriptIdToSourcePathMapping.get(
|
|
618
703
|
req.params.scriptId
|
|
619
704
|
);
|
|
620
705
|
if (pathToSource != null) {
|
|
621
|
-
const httpURL = this
|
|
706
|
+
const httpURL = this.#tryParseHTTPURL(pathToSource);
|
|
622
707
|
if (httpURL) {
|
|
623
|
-
this
|
|
708
|
+
this.#fetchText(httpURL).then(
|
|
624
709
|
(text) => sendSuccessResponse(text),
|
|
625
710
|
(err) =>
|
|
626
711
|
sendErrorResponse(
|
|
@@ -631,7 +716,7 @@ class Device {
|
|
|
631
716
|
let file;
|
|
632
717
|
try {
|
|
633
718
|
file = fs.readFileSync(
|
|
634
|
-
path.resolve(this
|
|
719
|
+
path.resolve(this.#projectRoot, pathToSource),
|
|
635
720
|
"utf8"
|
|
636
721
|
);
|
|
637
722
|
} catch (err) {
|
|
@@ -645,17 +730,17 @@ class Device {
|
|
|
645
730
|
}
|
|
646
731
|
}
|
|
647
732
|
}
|
|
648
|
-
|
|
733
|
+
#mapToDevicePageId(pageId) {
|
|
649
734
|
if (
|
|
650
735
|
pageId === REACT_NATIVE_RELOADABLE_PAGE_ID &&
|
|
651
|
-
this
|
|
736
|
+
this.#lastConnectedLegacyReactNativePage != null
|
|
652
737
|
) {
|
|
653
|
-
return this.
|
|
738
|
+
return this.#lastConnectedLegacyReactNativePage.id;
|
|
654
739
|
} else {
|
|
655
740
|
return pageId;
|
|
656
741
|
}
|
|
657
742
|
}
|
|
658
|
-
|
|
743
|
+
#tryParseHTTPURL(url) {
|
|
659
744
|
let parsedURL;
|
|
660
745
|
try {
|
|
661
746
|
parsedURL = new URL(url);
|
|
@@ -669,13 +754,16 @@ class Device {
|
|
|
669
754
|
|
|
670
755
|
// Fetch text, raising an exception if the text could not be fetched,
|
|
671
756
|
// or is too large.
|
|
672
|
-
async
|
|
673
|
-
if (url.hostname
|
|
757
|
+
async #fetchText(url) {
|
|
758
|
+
if (!["localhost", "127.0.0.1"].includes(url.hostname)) {
|
|
674
759
|
throw new Error("remote fetches not permitted");
|
|
675
760
|
}
|
|
676
761
|
|
|
677
762
|
// $FlowFixMe[incompatible-call] Suppress arvr node-fetch flow error
|
|
678
763
|
const response = await (0, _nodeFetch.default)(url);
|
|
764
|
+
if (!response.ok) {
|
|
765
|
+
throw new Error("HTTP " + response.status + " " + response.statusText);
|
|
766
|
+
}
|
|
679
767
|
const text = await response.text();
|
|
680
768
|
// Restrict the length to well below the 500MB limit for nodejs (leaving
|
|
681
769
|
// room some some later manipulation, e.g. base64 or wrapping in JSON)
|
|
@@ -684,8 +772,8 @@ class Device {
|
|
|
684
772
|
}
|
|
685
773
|
return text;
|
|
686
774
|
}
|
|
687
|
-
|
|
688
|
-
const debuggerSocket = this
|
|
775
|
+
#sendErrorToDebugger(message) {
|
|
776
|
+
const debuggerSocket = this.#debuggerConnection?.socket;
|
|
689
777
|
if (debuggerSocket && debuggerSocket.readyState === _ws.default.OPEN) {
|
|
690
778
|
debuggerSocket.send(
|
|
691
779
|
JSON.stringify({
|