@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.
Files changed (37) hide show
  1. package/dist/createDevMiddleware.d.ts +8 -1
  2. package/dist/createDevMiddleware.js +12 -9
  3. package/dist/createDevMiddleware.js.flow +9 -1
  4. package/dist/index.flow.d.ts +2 -0
  5. package/dist/index.flow.js +16 -0
  6. package/dist/index.flow.js.flow +3 -0
  7. package/dist/inspector-proxy/Device.d.ts +3 -55
  8. package/dist/inspector-proxy/Device.js +254 -166
  9. package/dist/inspector-proxy/Device.js.flow +3 -59
  10. package/dist/inspector-proxy/DeviceEventReporter.d.ts +2 -12
  11. package/dist/inspector-proxy/DeviceEventReporter.js +37 -35
  12. package/dist/inspector-proxy/DeviceEventReporter.js.flow +2 -17
  13. package/dist/inspector-proxy/InspectorProxy.d.ts +1 -24
  14. package/dist/inspector-proxy/InspectorProxy.js +41 -30
  15. package/dist/inspector-proxy/InspectorProxy.js.flow +1 -24
  16. package/dist/inspector-proxy/cdp-types/messages.d.ts +39 -0
  17. package/dist/inspector-proxy/cdp-types/messages.js +1 -0
  18. package/dist/inspector-proxy/cdp-types/messages.js.flow +52 -0
  19. package/dist/inspector-proxy/cdp-types/protocol.d.ts +87 -0
  20. package/dist/inspector-proxy/cdp-types/protocol.js +1 -0
  21. package/dist/inspector-proxy/cdp-types/protocol.js.flow +106 -0
  22. package/dist/inspector-proxy/types.d.ts +60 -45
  23. package/dist/inspector-proxy/types.js.flow +62 -70
  24. package/dist/middleware/deprecated_openFlipperMiddleware.d.ts +1 -1
  25. package/dist/middleware/deprecated_openFlipperMiddleware.js.flow +1 -2
  26. package/dist/middleware/openDebuggerMiddleware.d.ts +1 -1
  27. package/dist/middleware/openDebuggerMiddleware.js +23 -8
  28. package/dist/middleware/openDebuggerMiddleware.js.flow +1 -2
  29. package/dist/types/EventReporter.d.ts +3 -3
  30. package/dist/types/EventReporter.js.flow +1 -1
  31. package/dist/types/Experiments.d.ts +4 -0
  32. package/dist/types/Experiments.js.flow +5 -0
  33. package/dist/utils/DefaultBrowserLauncher.js +1 -1
  34. package/dist/utils/getDevToolsFrontendUrl.d.ts +2 -0
  35. package/dist/utils/getDevToolsFrontendUrl.js +20 -4
  36. package/dist/utils/getDevToolsFrontendUrl.js.flow +3 -0
  37. 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 _processMessageFromDevice method for
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 last list of device's pages.
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
- _debuggerConnection = null;
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
- _lastConnectedReactNativePage = null;
105
+ #lastConnectedLegacyReactNativePage = null;
101
106
 
102
107
  // Whether we are in the middle of a reload in the REACT_NATIVE_RELOADABLE_PAGE.
103
- _isReloading = false;
108
+ #isLegacyPageReloading = false;
104
109
 
105
110
  // The previous "GetPages" message, for deduplication in debug logs.
106
- _lastGetPagesMessage = "";
111
+ #lastGetPagesMessage = "";
107
112
 
108
113
  // Mapping built from scriptParsed events and used to fetch file content in `Debugger.getScriptSource`.
109
- _scriptIdToSourcePathMapping = new Map();
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._id = id;
115
- this._name = name;
116
- this._app = app;
117
- this._pages = [];
118
- this._deviceSocket = socket;
119
- this._projectRoot = projectRoot;
120
- this._deviceEventReporter = eventReporter
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._deviceSocket.on("message", (message) => {
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._lastGetPagesMessage) {
140
+ if (message !== this.#lastGetPagesMessage) {
134
141
  debug(
135
142
  "(Debugger) (Proxy) <- (Device), getPages ping has changed: " +
136
143
  message
137
144
  );
138
- this._lastGetPagesMessage = message;
145
+ this.#lastGetPagesMessage = message;
139
146
  }
140
147
  } else {
141
148
  debug("(Debugger) (Proxy) <- (Device): " + message);
142
149
  }
143
- this._handleMessageFromDevice(parsedMessage);
150
+ this.#handleMessageFromDevice(parsedMessage);
144
151
  });
145
- this._deviceSocket.on("close", () => {
146
- this._deviceEventReporter?.logDisconnection("device");
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._debuggerConnection) {
149
- this._debuggerConnection.socket.close();
150
- this._debuggerConnection = null;
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._name;
171
+ return this.#name;
157
172
  }
158
173
  getApp() {
159
- return this._app;
174
+ return this.#app;
160
175
  }
161
176
  getPagesList() {
162
- if (this._lastConnectedReactNativePage) {
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._app,
182
+ app: this.#app,
183
+ capabilities: {},
168
184
  };
169
- return this._pages.concat(reactNativeReloadablePage);
185
+ return [...this.#pages.values(), reactNativeReloadablePage];
170
186
  } else {
171
- return this._pages;
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._deviceEventReporter?.logDisconnection("debugger");
182
- this._deviceEventReporter?.logConnection("debugger", {
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._debuggerConnection) {
189
- this._debuggerConnection.socket.close();
190
- this._debuggerConnection = null;
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
- this._debuggerConnection = debuggerInfo;
199
- debug(`Got new debugger connection for page ${pageId} of ${this._name}`);
200
- this._sendMessageToDevice({
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._mapToDevicePageId(pageId),
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._deviceEventReporter?.logRequest(debuggerRequest, "debugger", {
212
- pageId: this._debuggerConnection?.pageId ?? null,
231
+ this.#deviceEventReporter?.logRequest(debuggerRequest, "debugger", {
232
+ pageId: this.#debuggerConnection?.pageId ?? null,
213
233
  frontendUserAgent: metadata.userAgent,
214
234
  });
215
- const handled = this._interceptMessageFromDebugger(
216
- debuggerRequest,
217
- debuggerInfo,
218
- socket
219
- );
220
- if (!handled) {
221
- this._sendMessageToDevice({
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._mapToDevicePageId(pageId),
225
- wrappedEvent: JSON.stringify(debuggerRequest),
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._name} disconnected.`);
232
- this._deviceEventReporter?.logDisconnection("debugger");
233
- this._sendMessageToDevice({
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._mapToDevicePageId(pageId),
259
+ pageId: this.#mapToDevicePageId(pageId),
237
260
  },
238
261
  });
239
- this._debuggerConnection = null;
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._app !== newDevice.getApp() ||
263
- this._name !== newDevice.getName()
285
+ this.#app !== newDevice.getApp() ||
286
+ this.#name !== newDevice.getName()
264
287
  ) {
265
- this._deviceSocket.close();
266
- this._debuggerConnection?.socket.close();
288
+ this.#deviceSocket.close();
289
+ this.#debuggerConnection?.socket.close();
267
290
  }
268
- const oldDebugger = this._debuggerConnection;
269
- this._debuggerConnection = null;
291
+ const oldDebugger = this.#debuggerConnection;
292
+ this.#debuggerConnection = null;
270
293
  if (oldDebugger) {
271
294
  oldDebugger.socket.removeAllListeners();
272
- this._deviceSocket.close();
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 _pages list.
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
- _handleMessageFromDevice(message) {
320
+ #handleMessageFromDevice(message) {
291
321
  if (message.event === "getPages") {
292
- this._pages = message.payload;
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 have new React Native page.
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 (let i = 0; i < this._pages.length; ++i) {
300
- if (this._pages[i].title.indexOf("React") >= 0) {
301
- if (this._pages[i].id !== this._lastConnectedReactNativePage?.id) {
302
- this._newReactNativePage(this._pages[i]);
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
- const debuggerSocket = this._debuggerConnection
312
- ? this._debuggerConnection.socket
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._debuggerConnection != null &&
317
- this._debuggerConnection.pageId !== REACT_NATIVE_RELOADABLE_PAGE_ID
379
+ this.#debuggerConnection != null &&
380
+ this.#debuggerConnection.pageId !== REACT_NATIVE_RELOADABLE_PAGE_ID
318
381
  ) {
319
- debug(`Page ${pageId} is reloading.`);
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._debuggerConnection == null) {
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._debuggerConnection.socket;
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._deviceEventReporter?.logResponse(parsedPayload, "device", {
346
- pageId: this._debuggerConnection?.pageId ?? null,
347
- frontendUserAgent: this._debuggerConnection?.userAgent ?? null,
410
+ this.#deviceEventReporter?.logResponse(parsedPayload, "device", {
411
+ pageId,
412
+ frontendUserAgent: this.#debuggerConnection?.userAgent ?? null,
348
413
  });
349
414
  }
350
- if (this._debuggerConnection) {
415
+ if (this.#debuggerConnection != null) {
351
416
  // Wrapping just to make flow happy :)
352
417
  // $FlowFixMe[unused-promise]
353
- this._processMessageFromDevice(
418
+ this.#processMessageFromDeviceLegacy(
354
419
  parsedPayload,
355
- this._debuggerConnection
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
- _sendMessageToDevice(message) {
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._deviceSocket.send(JSON.stringify(message));
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
- _newReactNativePage(page) {
443
+ #newLegacyReactNativePage(page) {
387
444
  debug(`React Native page updated to ${page.id}`);
388
445
  if (
389
- this._debuggerConnection == null ||
390
- this._debuggerConnection.pageId !== REACT_NATIVE_RELOADABLE_PAGE_ID
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._lastConnectedReactNativePage = page;
452
+ this.#lastConnectedLegacyReactNativePage = page;
396
453
  return;
397
454
  }
398
- const oldPageId = this._lastConnectedReactNativePage?.id;
399
- this._lastConnectedReactNativePage = page;
400
- this._isReloading = true;
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._sendMessageToDevice({
465
+ this.#sendMessageToDevice({
409
466
  event: "disconnect",
410
467
  payload: {
411
468
  pageId: oldPageId,
412
469
  },
413
470
  });
414
471
  }
415
- this._sendMessageToDevice({
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._deviceEventReporter?.logRequest(message, "proxy", {
433
- pageId: this._debuggerConnection?.pageId ?? null,
434
- frontendUserAgent: this._debuggerConnection?.userAgent ?? null,
489
+ this.#deviceEventReporter?.logRequest(message, "proxy", {
490
+ pageId: this.#debuggerConnection?.pageId ?? null,
491
+ frontendUserAgent: this.#debuggerConnection?.userAgent ?? null,
435
492
  });
436
- this._sendMessageToDevice({
493
+ this.#sendMessageToDevice({
437
494
  event: "wrappedEvent",
438
495
  payload: {
439
- pageId: this._mapToDevicePageId(page.id),
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 _processMessageFromDevice(payload, debuggerInfo) {
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 (payload.method === "Debugger.scriptParsed") {
450
- const params = payload.params || {};
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.indexOf(address) >= 0) {
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._tryParseHTTPURL(params.sourceMapURL);
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._fetchText(sourceMapURL);
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._sendErrorToDebugger(
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._scriptIdToSourcePathMapping.set(params.scriptId, params.url);
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._isReloading
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._deviceEventReporter?.logRequest(resumeMessage, "proxy", {
530
- pageId: this._debuggerConnection?.pageId ?? null,
531
- frontendUserAgent: this._debuggerConnection?.userAgent ?? null,
599
+ this.#deviceEventReporter?.logRequest(resumeMessage, "proxy", {
600
+ pageId: this.#debuggerConnection?.pageId ?? null,
601
+ frontendUserAgent: this.#debuggerConnection?.userAgent ?? null,
532
602
  });
533
- this._sendMessageToDevice({
603
+ this.#sendMessageToDevice({
534
604
  event: "wrappedEvent",
535
605
  payload: {
536
- pageId: this._mapToDevicePageId(debuggerInfo.pageId),
606
+ pageId: this.#mapToDevicePageId(debuggerInfo.pageId),
537
607
  wrappedEvent: JSON.stringify(resumeMessage),
538
608
  },
539
609
  });
540
- this._isReloading = false;
610
+ this.#isLegacyPageReloading = false;
541
611
  }
542
612
  }
543
613
 
544
- // Allows to make changes in incoming messages from debugger. Returns a boolean
545
- // indicating whether the message has been handled locally (i.e. does not need
546
- // to be forwarded to the target).
547
- _interceptMessageFromDebugger(req, debuggerInfo, socket) {
548
- if (req.method === "Debugger.setBreakpointByUrl") {
549
- this._processDebuggerSetBreakpointByUrl(req, debuggerInfo);
550
- } else if (req.method === "Debugger.getScriptSource") {
551
- this._processDebuggerGetScriptSource(req, socket);
552
- return true;
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
- _processDebuggerSetBreakpointByUrl(req, debuggerInfo) {
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
- if (req.params.url != null) {
560
- req.params.url = req.params.url.replace(
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
- req.params.url &&
566
- req.params.url.startsWith(FILE_PREFIX) &&
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 _processMessageFromDevice.
650
+ // Remove fake URL prefix if we modified URL in #processMessageFromDeviceLegacy.
570
651
  // $FlowFixMe[incompatible-use]
571
- req.params.url = req.params.url.slice(FILE_PREFIX.length);
652
+ processedReq.params.url = processedReq.params.url.slice(
653
+ FILE_PREFIX.length
654
+ );
572
655
  }
573
656
  }
574
- if (req.params.urlRegex != null) {
575
- req.params.urlRegex = req.params.urlRegex.replace(
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
- _processDebuggerGetScriptSource(req, socket) {
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._deviceEventReporter?.logResponse(response, "proxy", {
593
- pageId: this._debuggerConnection?.pageId ?? null,
594
- frontendUserAgent: this._debuggerConnection?.userAgent ?? null,
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._sendErrorToDebugger(error);
612
- this._deviceEventReporter?.logResponse(response, "proxy", {
613
- pageId: this._debuggerConnection?.pageId ?? null,
614
- frontendUserAgent: this._debuggerConnection?.userAgent ?? null,
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._scriptIdToSourcePathMapping.get(
702
+ const pathToSource = this.#scriptIdToSourcePathMapping.get(
618
703
  req.params.scriptId
619
704
  );
620
705
  if (pathToSource != null) {
621
- const httpURL = this._tryParseHTTPURL(pathToSource);
706
+ const httpURL = this.#tryParseHTTPURL(pathToSource);
622
707
  if (httpURL) {
623
- this._fetchText(httpURL).then(
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._projectRoot, pathToSource),
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
- _mapToDevicePageId(pageId) {
733
+ #mapToDevicePageId(pageId) {
649
734
  if (
650
735
  pageId === REACT_NATIVE_RELOADABLE_PAGE_ID &&
651
- this._lastConnectedReactNativePage != null
736
+ this.#lastConnectedLegacyReactNativePage != null
652
737
  ) {
653
- return this._lastConnectedReactNativePage.id;
738
+ return this.#lastConnectedLegacyReactNativePage.id;
654
739
  } else {
655
740
  return pageId;
656
741
  }
657
742
  }
658
- _tryParseHTTPURL(url) {
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 _fetchText(url) {
673
- if (url.hostname !== "localhost") {
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
- _sendErrorToDebugger(message) {
688
- const debuggerSocket = this._debuggerConnection?.socket;
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({