@react-native/dev-middleware 0.75.0-rc.2 → 0.76.0-nightly-20240628-TEMP

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.
@@ -8,6 +8,7 @@ var _DeviceEventReporter = _interopRequireDefault(
8
8
  require("./DeviceEventReporter")
9
9
  );
10
10
  var fs = _interopRequireWildcard(require("fs"));
11
+ var _invariant = _interopRequireDefault(require("invariant"));
11
12
  var _nodeFetch = _interopRequireDefault(require("node-fetch"));
12
13
  var path = _interopRequireWildcard(require("path"));
13
14
  var _ws = _interopRequireDefault(require("ws"));
@@ -54,90 +55,28 @@ function _interopRequireWildcard(obj, nodeInterop) {
54
55
  function _interopRequireDefault(obj) {
55
56
  return obj && obj.__esModule ? obj : { default: obj };
56
57
  }
57
- /**
58
- * Copyright (c) Meta Platforms, Inc. and affiliates.
59
- *
60
- * This source code is licensed under the MIT license found in the
61
- * LICENSE file in the root directory of this source tree.
62
- *
63
- *
64
- * @format
65
- * @oncall react_native
66
- */
67
-
68
58
  const debug = require("debug")("Metro:InspectorProxy");
69
59
  const PAGES_POLLING_INTERVAL = 1000;
70
-
71
- // Replace hosts appearing in the `url` and `sourceMapURL` fields of
72
- // `Debugger.scriptParsed`, and back again in messages from the debugger,
73
- // to account for device/debugger/proxy running on different networks.
74
- const REWRITE_HOSTS_TO_LOCALHOST = [
75
- // A device may retrieve a bundle through 127.0.0.1 via a (SSH) tunnel, but
76
- // the (remote) Metro server may be on a host without an IPv4 loopback, so
77
- // 127.0.0.1 may not be addressible locally for (e.g., for source map
78
- // fetching). Replacing with the more general 'localhost' should always be
79
- // safe while also more compatible with IPv6-only setups.
80
- "127.0.0.1",
81
- // Android's stock emulator and other emulators such as genymotion use a
82
- // standard localhost alias.
83
- "10.0.2.2",
84
- "10.0.3.2",
85
- ];
86
-
87
- // Prefix for script URLs that are alphanumeric IDs. See comment in #processMessageFromDeviceLegacy method for
88
- // more details.
60
+ const REWRITE_HOSTS_TO_LOCALHOST = ["127.0.0.1", "10.0.2.2", "10.0.3.2"];
89
61
  const FILE_PREFIX = "file://";
90
62
  const REACT_NATIVE_RELOADABLE_PAGE_ID = "-1";
91
-
92
- /**
93
- * Device class represents single device connection to Inspector Proxy. Each device
94
- * can have multiple inspectable pages.
95
- */
96
63
  class Device {
97
- // ID of the device.
98
64
  #id;
99
-
100
- // Name of the device.
101
65
  #name;
102
-
103
- // Package name of the app.
104
66
  #app;
105
-
106
- // Sequences async processing of messages from device to preserve order. Only
107
- // necessary while we need to accommodate #processMessageFromDeviceLegacy's
108
- // async fetch.
109
67
  #messageFromDeviceQueue = Promise.resolve();
110
-
111
- // Stores socket connection between Inspector Proxy and device.
112
68
  #deviceSocket;
113
-
114
- // Stores the most recent listing of device's pages, keyed by the `id` field.
115
- #pages;
116
-
117
- // Stores information about currently connected debugger (if any).
69
+ #pages = new Map();
118
70
  #debuggerConnection = null;
119
-
120
- // Last known Page ID of the React Native page.
121
- // This is used by debugger connections that don't have PageID specified
122
- // (and will interact with the latest React Native page).
123
71
  #lastConnectedLegacyReactNativePage = null;
124
-
125
- // Whether we are in the middle of a reload in the REACT_NATIVE_RELOADABLE_PAGE.
126
72
  #isLegacyPageReloading = false;
127
-
128
- // The previous "GetPages" message, for deduplication in debug logs.
129
73
  #lastGetPagesMessage = "";
130
-
131
- // Mapping built from scriptParsed events and used to fetch file content in `Debugger.getScriptSource`.
132
74
  #scriptIdToSourcePathMapping = new Map();
133
-
134
- // Root of the project used for relative to absolute source path conversion.
135
75
  #projectRoot;
136
76
  #deviceEventReporter;
137
77
  #pagesPollingIntervalId;
138
-
139
- // The device message middleware factory function allowing implementers to handle unsupported CDP messages.
140
78
  #createCustomMessageHandler;
79
+ #connectedPageIds = new Set();
141
80
  constructor(
142
81
  id,
143
82
  name,
@@ -146,11 +85,29 @@ class Device {
146
85
  projectRoot,
147
86
  eventReporter,
148
87
  createMessageMiddleware
88
+ ) {
89
+ this.#dangerouslyConstruct(
90
+ id,
91
+ name,
92
+ app,
93
+ socket,
94
+ projectRoot,
95
+ eventReporter,
96
+ createMessageMiddleware
97
+ );
98
+ }
99
+ #dangerouslyConstruct(
100
+ id,
101
+ name,
102
+ app,
103
+ socket,
104
+ projectRoot,
105
+ eventReporter,
106
+ createMessageMiddleware
149
107
  ) {
150
108
  this.#id = id;
151
109
  this.#name = name;
152
110
  this.#app = app;
153
- this.#pages = new Map();
154
111
  this.#deviceSocket = socket;
155
112
  this.#projectRoot = projectRoot;
156
113
  this.#deviceEventReporter = eventReporter
@@ -161,14 +118,11 @@ class Device {
161
118
  })
162
119
  : null;
163
120
  this.#createCustomMessageHandler = createMessageMiddleware;
164
-
165
- // $FlowFixMe[incompatible-call]
166
121
  this.#deviceSocket.on("message", (message) => {
167
122
  this.#messageFromDeviceQueue = this.#messageFromDeviceQueue
168
123
  .then(async () => {
169
124
  const parsedMessage = JSON.parse(message);
170
125
  if (parsedMessage.event === "getPages") {
171
- // There's a 'getPages' message every second, so only show them if they change
172
126
  if (message !== this.#lastGetPagesMessage) {
173
127
  debug(
174
128
  "(Debugger) (Proxy) <- (Device), getPages ping has changed: " +
@@ -197,7 +151,6 @@ class Device {
197
151
  }
198
152
  });
199
153
  });
200
- // Sends 'getPages' request to device every PAGES_POLLING_INTERVAL milliseconds.
201
154
  this.#pagesPollingIntervalId = setInterval(
202
155
  () =>
203
156
  this.#sendMessageToDevice({
@@ -206,15 +159,59 @@ class Device {
206
159
  PAGES_POLLING_INTERVAL
207
160
  );
208
161
  this.#deviceSocket.on("close", () => {
209
- this.#deviceEventReporter?.logDisconnection("device");
210
- // Device disconnected - close debugger connection.
211
- if (this.#debuggerConnection) {
212
- this.#debuggerConnection.socket.close();
213
- this.#debuggerConnection = null;
162
+ if (socket === this.#deviceSocket) {
163
+ this.#deviceEventReporter?.logDisconnection("device");
164
+ this.#terminateDebuggerConnection();
165
+ clearInterval(this.#pagesPollingIntervalId);
214
166
  }
215
- clearInterval(this.#pagesPollingIntervalId);
216
167
  });
217
168
  }
169
+ #terminateDebuggerConnection() {
170
+ const debuggerConnection = this.#debuggerConnection;
171
+ if (debuggerConnection) {
172
+ this.#sendDisconnectEventToDevice(
173
+ this.#mapToDevicePageId(debuggerConnection.pageId)
174
+ );
175
+ debuggerConnection.socket.close();
176
+ this.#debuggerConnection = null;
177
+ }
178
+ }
179
+ dangerouslyRecreateDevice(
180
+ id,
181
+ name,
182
+ app,
183
+ socket,
184
+ projectRoot,
185
+ eventReporter,
186
+ createMessageMiddleware
187
+ ) {
188
+ (0, _invariant.default)(
189
+ id === this.#id,
190
+ "dangerouslyRecreateDevice() can only be used for the same device ID"
191
+ );
192
+ const oldDebugger = this.#debuggerConnection;
193
+ if (this.#app !== app || this.#name !== name) {
194
+ this.#deviceSocket.close();
195
+ this.#terminateDebuggerConnection();
196
+ }
197
+ this.#debuggerConnection = null;
198
+ if (oldDebugger) {
199
+ oldDebugger.socket.removeAllListeners();
200
+ this.#deviceSocket.close();
201
+ this.handleDebuggerConnection(oldDebugger.socket, oldDebugger.pageId, {
202
+ userAgent: oldDebugger.userAgent,
203
+ });
204
+ }
205
+ this.#dangerouslyConstruct(
206
+ id,
207
+ name,
208
+ app,
209
+ socket,
210
+ projectRoot,
211
+ eventReporter,
212
+ createMessageMiddleware
213
+ );
214
+ }
218
215
  getName() {
219
216
  return this.#name;
220
217
  }
@@ -228,24 +225,26 @@ class Device {
228
225
  return [...this.#pages.values()];
229
226
  }
230
227
  }
231
-
232
- // Handles new debugger connection to this device:
233
- // 1. Sends connect event to device
234
- // 2. Forwards all messages from the debugger to device as wrappedEvent
235
- // 3. Sends disconnect event to device when debugger connection socket closes.
236
228
  handleDebuggerConnection(socket, pageId, metadata) {
237
- // Clear any commands we were waiting on.
229
+ const page =
230
+ pageId === REACT_NATIVE_RELOADABLE_PAGE_ID
231
+ ? this.#createSyntheticPage()
232
+ : this.#pages.get(pageId);
233
+ if (!page) {
234
+ debug(
235
+ `Got new debugger connection for page ${pageId} of ${
236
+ this.#name
237
+ }, but no such page exists`
238
+ );
239
+ socket.close();
240
+ return;
241
+ }
238
242
  this.#deviceEventReporter?.logDisconnection("debugger");
243
+ this.#terminateDebuggerConnection();
239
244
  this.#deviceEventReporter?.logConnection("debugger", {
240
245
  pageId,
241
246
  frontendUserAgent: metadata.userAgent,
242
247
  });
243
-
244
- // Disconnect current debugger if we already have debugger connected.
245
- if (this.#debuggerConnection) {
246
- this.#debuggerConnection.socket.close();
247
- this.#debuggerConnection = null;
248
- }
249
248
  const debuggerInfo = {
250
249
  socket,
251
250
  prependedFilePrefix: false,
@@ -253,16 +252,9 @@ class Device {
253
252
  userAgent: metadata.userAgent,
254
253
  customHandler: null,
255
254
  };
256
-
257
- // TODO(moti): Handle null case explicitly, e.g. refuse to connect to
258
- // unknown pages.
259
- const page =
260
- pageId === REACT_NATIVE_RELOADABLE_PAGE_ID
261
- ? this.#createSyntheticPage()
262
- : this.#pages.get(pageId);
263
255
  this.#debuggerConnection = debuggerInfo;
264
256
  debug(`Got new debugger connection for page ${pageId} of ${this.#name}`);
265
- if (page && this.#debuggerConnection && this.#createCustomMessageHandler) {
257
+ if (this.#debuggerConnection && this.#createCustomMessageHandler) {
266
258
  this.#debuggerConnection.customHandler = this.#createCustomMessageHandler(
267
259
  {
268
260
  page,
@@ -304,14 +296,7 @@ class Device {
304
296
  );
305
297
  }
306
298
  }
307
- this.#sendMessageToDevice({
308
- event: "connect",
309
- payload: {
310
- pageId: this.#mapToDevicePageId(pageId),
311
- },
312
- });
313
-
314
- // $FlowFixMe[incompatible-call]
299
+ this.#sendConnectEventToDevice(this.#mapToDevicePageId(pageId));
315
300
  socket.on("message", (message) => {
316
301
  debug("(Debugger) -> (Proxy) (Device): " + message);
317
302
  const debuggerRequest = JSON.parse(message);
@@ -330,7 +315,7 @@ class Device {
330
315
  ) {
331
316
  return;
332
317
  }
333
- if (!page || !this.#pageHasCapability(page, "nativeSourceCodeFetching")) {
318
+ if (!this.#pageHasCapability(page, "nativeSourceCodeFetching")) {
334
319
  processedReq = this.#interceptClientMessageForSourceFetching(
335
320
  debuggerRequest,
336
321
  debuggerInfo,
@@ -350,66 +335,43 @@ class Device {
350
335
  socket.on("close", () => {
351
336
  debug(`Debugger for page ${pageId} and ${this.#name} disconnected.`);
352
337
  this.#deviceEventReporter?.logDisconnection("debugger");
353
- this.#sendMessageToDevice({
354
- event: "disconnect",
355
- payload: {
356
- pageId: this.#mapToDevicePageId(pageId),
357
- },
358
- });
359
- this.#debuggerConnection = null;
338
+ if (this.#debuggerConnection?.socket === socket) {
339
+ this.#terminateDebuggerConnection();
340
+ }
360
341
  });
361
-
362
- // $FlowFixMe[method-unbinding]
363
342
  const sendFunc = socket.send;
364
- // $FlowFixMe[cannot-write]
365
343
  socket.send = function (message) {
366
344
  debug("(Debugger) <- (Proxy) (Device): " + message);
367
345
  return sendFunc.call(socket, message);
368
346
  };
369
347
  }
370
-
371
- /**
372
- * Handles cleaning up a duplicate device connection, by client-side device ID.
373
- * 1. Checks if the same device is attempting to reconnect for the same app.
374
- * 2. If not, close both the device and debugger socket.
375
- * 3. If the debugger connection can be reused, close the device socket only.
376
- *
377
- * This allows users to reload the app, either as result of a crash, or manually
378
- * reloading, without having to restart the debugger.
379
- */
380
- handleDuplicateDeviceConnection(newDevice) {
381
- if (
382
- this.#app !== newDevice.getApp() ||
383
- this.#name !== newDevice.getName()
384
- ) {
385
- this.#deviceSocket.close();
386
- this.#debuggerConnection?.socket.close();
348
+ #sendConnectEventToDevice(devicePageId) {
349
+ if (this.#connectedPageIds.has(devicePageId)) {
350
+ return;
387
351
  }
388
- const oldDebugger = this.#debuggerConnection;
389
- this.#debuggerConnection = null;
390
- if (oldDebugger) {
391
- oldDebugger.socket.removeAllListeners();
392
- this.#deviceSocket.close();
393
- newDevice.handleDebuggerConnection(
394
- oldDebugger.socket,
395
- oldDebugger.pageId,
396
- {
397
- userAgent: oldDebugger.userAgent,
398
- }
399
- );
352
+ this.#connectedPageIds.add(devicePageId);
353
+ this.#sendMessageToDevice({
354
+ event: "connect",
355
+ payload: {
356
+ pageId: devicePageId,
357
+ },
358
+ });
359
+ }
360
+ #sendDisconnectEventToDevice(devicePageId) {
361
+ if (!this.#connectedPageIds.has(devicePageId)) {
362
+ return;
400
363
  }
364
+ this.#connectedPageIds.delete(devicePageId);
365
+ this.#sendMessageToDevice({
366
+ event: "disconnect",
367
+ payload: {
368
+ pageId: devicePageId,
369
+ },
370
+ });
401
371
  }
402
-
403
- /**
404
- * Returns `true` if a page supports the given target capability flag.
405
- */
406
372
  #pageHasCapability(page, flag) {
407
373
  return page.capabilities[flag] === true;
408
374
  }
409
-
410
- /**
411
- * Returns the synthetic "React Native Experimental (Improved Chrome Reloads)" page.
412
- */
413
375
  #createSyntheticPage() {
414
376
  return {
415
377
  id: REACT_NATIVE_RELOADABLE_PAGE_ID,
@@ -419,14 +381,6 @@ class Device {
419
381
  capabilities: {},
420
382
  };
421
383
  }
422
-
423
- // Handles messages received from device:
424
- // 1. For getPages responses updates local #pages list.
425
- // 2. All other messages are forwarded to debugger as wrappedEvent.
426
- //
427
- // In the future more logic will be added to this method for modifying
428
- // some of the messages (like updating messages with source maps and file
429
- // locations).
430
384
  async #handleMessageFromDevice(message) {
431
385
  if (message.event === "getPages") {
432
386
  this.#pages = new Map(
@@ -454,12 +408,6 @@ class Device {
454
408
  )}`
455
409
  );
456
410
  }
457
-
458
- // Check if device has a new legacy React Native page.
459
- // There is usually no more than 2-3 pages per device so this operation
460
- // is not expensive.
461
- // TODO(hypuk): It is better for VM to send update event when new page is
462
- // created instead of manually checking this on every getPages result.
463
411
  for (const page of this.#pages.values()) {
464
412
  if (this.#pageHasCapability(page, "nativePageReloads")) {
465
413
  continue;
@@ -472,11 +420,7 @@ class Device {
472
420
  }
473
421
  }
474
422
  } else if (message.event === "disconnect") {
475
- // Device sends disconnect events only when page is reloaded or
476
- // if debugger socket was disconnected.
477
423
  const pageId = message.payload.pageId;
478
- // TODO(moti): Handle null case explicitly, e.g. swallow disconnect events
479
- // for unknown pages.
480
424
  const page = this.#pages.get(pageId);
481
425
  if (page != null && this.#pageHasCapability(page, "nativePageReloads")) {
482
426
  return;
@@ -501,17 +445,11 @@ class Device {
501
445
  if (this.#debuggerConnection == null) {
502
446
  return;
503
447
  }
504
-
505
- // FIXME: Is it possible that we received message for pageID that does not
506
- // correspond to current debugger connection?
507
- // TODO(moti): yes, fix multi-debugger case
508
-
509
448
  const debuggerSocket = this.#debuggerConnection.socket;
510
449
  if (
511
450
  debuggerSocket == null ||
512
451
  debuggerSocket.readyState !== _ws.default.OPEN
513
452
  ) {
514
- // TODO(hypuk): Send error back to device?
515
453
  return;
516
454
  }
517
455
  const parsedPayload = JSON.parse(message.payload.wrappedEvent);
@@ -544,8 +482,6 @@ class Device {
544
482
  }
545
483
  }
546
484
  }
547
-
548
- // Sends single message to device.
549
485
  #sendMessageToDevice(message) {
550
486
  try {
551
487
  if (message.event !== "getPages") {
@@ -554,43 +490,22 @@ class Device {
554
490
  this.#deviceSocket.send(JSON.stringify(message));
555
491
  } catch (error) {}
556
492
  }
557
-
558
- // We received new React Native Page ID.
559
493
  #newLegacyReactNativePage(page) {
560
494
  debug(`React Native page updated to ${page.id}`);
561
495
  if (
562
496
  this.#debuggerConnection == null ||
563
497
  this.#debuggerConnection.pageId !== REACT_NATIVE_RELOADABLE_PAGE_ID
564
498
  ) {
565
- // We can just remember new page ID without any further actions if no
566
- // debugger is currently attached or attached debugger is not
567
- // "Reloadable React Native" connection.
568
499
  this.#lastConnectedLegacyReactNativePage = page;
569
500
  return;
570
501
  }
571
502
  const oldPageId = this.#lastConnectedLegacyReactNativePage?.id;
572
503
  this.#lastConnectedLegacyReactNativePage = page;
573
504
  this.#isLegacyPageReloading = true;
574
-
575
- // We already had a debugger connected to React Native page and a
576
- // new one appeared - in this case we need to emulate execution context
577
- // detroy and resend Debugger.enable and Runtime.enable commands to new
578
- // page.
579
-
580
505
  if (oldPageId != null) {
581
- this.#sendMessageToDevice({
582
- event: "disconnect",
583
- payload: {
584
- pageId: oldPageId,
585
- },
586
- });
506
+ this.#sendDisconnectEventToDevice(oldPageId);
587
507
  }
588
- this.#sendMessageToDevice({
589
- event: "connect",
590
- payload: {
591
- pageId: page.id,
592
- },
593
- });
508
+ this.#sendConnectEventToDevice(page.id);
594
509
  const toSend = [
595
510
  {
596
511
  method: "Runtime.enable",
@@ -617,15 +532,8 @@ class Device {
617
532
  });
618
533
  }
619
534
  }
620
-
621
- // Allows to make changes in incoming message from device.
622
535
  async #processMessageFromDeviceLegacy(payload, debuggerInfo, pageId) {
623
- // TODO(moti): Handle null case explicitly, or ideally associate a copy
624
- // of the page metadata object with the connection so this can never be
625
- // null.
626
536
  const page = pageId != null ? this.#pages.get(pageId) : null;
627
-
628
- // Replace Android addresses for scriptParsed event.
629
537
  if (
630
538
  (!page || !this.#pageHasCapability(page, "nativeSourceCodeFetching")) &&
631
539
  payload.method === "Debugger.scriptParsed" &&
@@ -635,7 +543,6 @@ class Device {
635
543
  if ("sourceMapURL" in params) {
636
544
  for (const hostToRewrite of REWRITE_HOSTS_TO_LOCALHOST) {
637
545
  if (params.sourceMapURL.includes(hostToRewrite)) {
638
- // $FlowFixMe[cannot-write]
639
546
  payload.params.sourceMapURL = params.sourceMapURL.replace(
640
547
  hostToRewrite,
641
548
  "localhost"
@@ -645,14 +552,8 @@ class Device {
645
552
  }
646
553
  const sourceMapURL = this.#tryParseHTTPURL(params.sourceMapURL);
647
554
  if (sourceMapURL) {
648
- // Some debug clients do not support fetching HTTP URLs. If the
649
- // message headed to the debug client identifies the source map with
650
- // an HTTP URL, fetch the content here and convert the content to a
651
- // Data URL (which is more widely supported) before passing the
652
- // message to the debug client.
653
555
  try {
654
556
  const sourceMap = await this.#fetchText(sourceMapURL);
655
- // $FlowFixMe[cannot-write]
656
557
  payload.params.sourceMapURL =
657
558
  "data:application/json;charset=utf-8;base64," +
658
559
  Buffer.from(sourceMap).toString("base64");
@@ -666,23 +567,14 @@ class Device {
666
567
  if ("url" in params) {
667
568
  for (const hostToRewrite of REWRITE_HOSTS_TO_LOCALHOST) {
668
569
  if (params.url.includes(hostToRewrite)) {
669
- // $FlowFixMe[cannot-write]
670
570
  payload.params.url = params.url.replace(hostToRewrite, "localhost");
671
571
  debuggerInfo.originalSourceURLAddress = hostToRewrite;
672
572
  }
673
573
  }
674
-
675
- // Chrome doesn't download source maps if URL param is not a valid
676
- // URL. Some frameworks pass alphanumeric script ID instead of URL which causes
677
- // Chrome to not download source maps. In this case we want to prepend script ID
678
- // with 'file://' prefix.
679
574
  if (payload.params.url.match(/^[0-9a-z]+$/)) {
680
- // $FlowFixMe[cannot-write]
681
575
  payload.params.url = FILE_PREFIX + payload.params.url;
682
576
  debuggerInfo.prependedFilePrefix = true;
683
577
  }
684
-
685
- // $FlowFixMe[prop-missing]
686
578
  if (params.scriptId != null) {
687
579
  this.#scriptIdToSourcePathMapping.set(params.scriptId, params.url);
688
580
  }
@@ -692,22 +584,11 @@ class Device {
692
584
  payload.method === "Runtime.executionContextCreated" &&
693
585
  this.#isLegacyPageReloading
694
586
  ) {
695
- // The new context is ready. First notify Chrome that we've reloaded so
696
- // it'll resend its breakpoints. If we do this earlier, we may not be
697
- // ready to receive them.
698
587
  debuggerInfo.socket.send(
699
588
  JSON.stringify({
700
589
  method: "Runtime.executionContextsCleared",
701
590
  })
702
591
  );
703
-
704
- // The VM starts in a paused mode. Ask it to resume.
705
- // Note that if setting breakpoints in early initialization functions,
706
- // there's a currently race condition between these functions executing
707
- // and Chrome re-applying the breakpoints due to the message above.
708
- //
709
- // This is not an issue in VSCode/Nuclide where the IDE knows to resume
710
- // at its convenience.
711
592
  const resumeMessage = {
712
593
  method: "Debugger.resume",
713
594
  id: 0,
@@ -729,18 +610,11 @@ class Device {
729
610
  this.#isLegacyPageReloading = false;
730
611
  }
731
612
  }
732
-
733
- /**
734
- * Intercept an incoming message from a connected debugger. Returns either an
735
- * original/replacement CDP message object, or `null` (will forward nothing
736
- * to the target).
737
- */
738
613
  #interceptClientMessageForSourceFetching(req, debuggerInfo, socket) {
739
614
  switch (req.method) {
740
615
  case "Debugger.setBreakpointByUrl":
741
616
  return this.#processDebuggerSetBreakpointByUrl(req, debuggerInfo);
742
617
  case "Debugger.getScriptSource":
743
- // Sends response to debugger via side-effect
744
618
  this.#processDebuggerGetScriptSource(req, socket);
745
619
  return null;
746
620
  default:
@@ -748,7 +622,6 @@ class Device {
748
622
  }
749
623
  }
750
624
  #processDebuggerSetBreakpointByUrl(req, debuggerInfo) {
751
- // If we replaced Android emulator's address to localhost we need to change it back.
752
625
  if (debuggerInfo.originalSourceURLAddress != null) {
753
626
  const processedReq = {
754
627
  ...req,
@@ -766,8 +639,6 @@ class Device {
766
639
  processedReq.params.url.startsWith(FILE_PREFIX) &&
767
640
  debuggerInfo.prependedFilePrefix
768
641
  ) {
769
- // Remove fake URL prefix if we modified URL in #processMessageFromDeviceLegacy.
770
- // $FlowFixMe[incompatible-use]
771
642
  processedReq.params.url = processedReq.params.url.slice(
772
643
  FILE_PREFIX.length
773
644
  );
@@ -776,7 +647,6 @@ class Device {
776
647
  if (processedReq.params.urlRegex != null) {
777
648
  processedReq.params.urlRegex = processedReq.params.urlRegex.replace(
778
649
  /localhost/g,
779
- // $FlowFixMe[incompatible-call]
780
650
  debuggerInfo.originalSourceURLAddress
781
651
  );
782
652
  }
@@ -801,8 +671,6 @@ class Device {
801
671
  });
802
672
  };
803
673
  const sendErrorResponse = (error) => {
804
- // Tell the client that the request failed
805
-
806
674
  const response = {
807
675
  id: req.id,
808
676
  result: {
@@ -812,8 +680,6 @@ class Device {
812
680
  },
813
681
  };
814
682
  socket.send(JSON.stringify(response));
815
-
816
- // Send to the console as well, so the user can see it
817
683
  this.#sendErrorToDebugger(error);
818
684
  const pageId = this.#debuggerConnection?.pageId ?? null;
819
685
  this.#deviceEventReporter?.logResponse(response, "proxy", {
@@ -874,18 +740,12 @@ class Device {
874
740
  }
875
741
  return parsedURL;
876
742
  }
877
-
878
- // Fetch text, raising an exception if the text could not be fetched,
879
- // or is too large.
880
743
  async #fetchText(url) {
881
- // $FlowFixMe[incompatible-call] Suppress arvr node-fetch flow error
882
744
  const response = await (0, _nodeFetch.default)(url);
883
745
  if (!response.ok) {
884
746
  throw new Error("HTTP " + response.status + " " + response.statusText);
885
747
  }
886
748
  const text = await response.text();
887
- // Restrict the length to well below the 500MB limit for nodejs (leaving
888
- // room some some later manipulation, e.g. base64 or wrapping in JSON)
889
749
  if (text.length > 350000000) {
890
750
  throw new Error("file too large to fetch via HTTP");
891
751
  }
@@ -918,5 +778,8 @@ class Device {
918
778
  }
919
779
  return this.#pageHasCapability(page, "prefersFuseboxFrontend");
920
780
  }
781
+ dangerouslyGetSocket() {
782
+ return this.#deviceSocket;
783
+ }
921
784
  }
922
785
  exports.default = Device;
@@ -29,6 +29,15 @@ declare export default class Device {
29
29
  eventReporter: ?EventReporter,
30
30
  createMessageMiddleware: ?CreateCustomMessageHandlerFn
31
31
  ): void;
32
+ dangerouslyRecreateDevice(
33
+ id: string,
34
+ name: string,
35
+ app: string,
36
+ socket: WS,
37
+ projectRoot: string,
38
+ eventReporter: ?EventReporter,
39
+ createMessageMiddleware: ?CreateCustomMessageHandlerFn
40
+ ): void;
32
41
  getName(): string;
33
42
  getApp(): string;
34
43
  getPagesList(): $ReadOnlyArray<Page>;
@@ -39,5 +48,5 @@ declare export default class Device {
39
48
  userAgent: string | null,
40
49
  }>
41
50
  ): void;
42
- handleDuplicateDeviceConnection(newDevice: Device): void;
51
+ dangerouslyGetSocket(): WS;
43
52
  }