@@ -29,6 +29,7 @@ import type {
29
29
30
30
import DeviceEventReporter from './DeviceEventReporter' ;
31
31
import * as fs from 'fs' ;
32
+ import invariant from 'invariant' ;
32
33
import fetch from 'node-fetch' ;
33
34
import * as path from 'path' ;
34
35
import WS from 'ws' ;
@@ -98,7 +99,7 @@ export default class Device {
98
99
#deviceSocket: WS;
99
100
100
101
// Stores the most recent listing of device's pages, keyed by the `id` field.
101
- #pages: $ReadOnlyMap< string , Page > ;
102
+ #pages: $ReadOnlyMap< string , Page > = new Map() ;
102
103
103
104
// Stores information about currently connected debugger (if any).
104
105
#debuggerConnection: ?DebuggerConnection = null;
@@ -135,11 +136,30 @@ export default class Device {
135
136
projectRoot: string,
136
137
eventReporter: ?EventReporter,
137
138
createMessageMiddleware: ?CreateCustomMessageHandlerFn,
139
+ ) {
140
+ this . #dangerouslyConstruct(
141
+ id ,
142
+ name ,
143
+ app ,
144
+ socket ,
145
+ projectRoot ,
146
+ eventReporter ,
147
+ createMessageMiddleware ,
148
+ ) ;
149
+ }
150
+
151
+ #dangerouslyConstruct(
152
+ id: string,
153
+ name: string,
154
+ app: string,
155
+ socket: WS,
156
+ projectRoot: string,
157
+ eventReporter: ?EventReporter,
158
+ createMessageMiddleware: ?CreateCustomMessageHandlerFn,
138
159
) {
139
160
this . #id = id ;
140
161
this . #name = name ;
141
162
this . #app = app ;
142
- this . #pages = new Map ( ) ;
143
163
this . #deviceSocket = socket ;
144
164
this . #projectRoot = projectRoot ;
145
165
this . #deviceEventReporter = eventReporter
@@ -192,16 +212,67 @@ export default class Device {
192
212
PAGES_POLLING_INTERVAL ,
193
213
) ;
194
214
this . #deviceSocket. on ( 'close' , ( ) => {
195
- this . #deviceEventReporter?. logDisconnection ( 'device' ) ;
196
- // Device disconnected - close debugger connection.
197
- if ( this . #debuggerConnection) {
198
- this . #debuggerConnection. socket . close ( ) ;
199
- this . #debuggerConnection = null ;
215
+ if ( socket === this . #deviceSocket) {
216
+ this . #deviceEventReporter?. logDisconnection ( 'device' ) ;
217
+ // Device disconnected - close debugger connection.
218
+ if ( this . #debuggerConnection) {
219
+ this . #debuggerConnection. socket . close ( ) ;
220
+ this . #debuggerConnection = null ;
221
+ }
222
+ clearInterval ( this . #pagesPollingIntervalId) ;
200
223
}
201
- clearInterval ( this . #pagesPollingIntervalId) ;
202
224
} ) ;
203
225
}
204
226
227
+ /**
228
+ * Used to recreate the device connection if there is a device ID collision.
229
+ * 1. Checks if the same device is attempting to reconnect for the same app.
230
+ * 2. If not, close both the device and debugger socket.
231
+ * 3. If the debugger connection can be reused, close the device socket only.
232
+ *
233
+ * This hack attempts to allow users to reload the app, either as result of a
234
+ * crash, or manually reloading, without having to restart the debugger.
235
+ */
236
+ dangerouslyRecreateDevice(
237
+ id: string,
238
+ name: string,
239
+ app: string,
240
+ socket: WS,
241
+ projectRoot: string,
242
+ eventReporter: ?EventReporter,
243
+ createMessageMiddleware: ?CreateCustomMessageHandlerFn,
244
+ ) {
245
+ invariant (
246
+ id === this . #id,
247
+ 'dangerouslyRecreateDevice() can only be used for the same device ID' ,
248
+ ) ;
249
+ if ( this . #app !== app || this . #name !== name ) {
250
+ this . #deviceSocket. close ( ) ;
251
+ this . #debuggerConnection?. socket . close ( ) ;
252
+ }
253
+
254
+ const oldDebugger = this.#debuggerConnection;
255
+ this.#debuggerConnection = null;
256
+
257
+ if (oldDebugger) {
258
+ oldDebugger . socket . removeAllListeners ( ) ;
259
+ this . #deviceSocket. close ( ) ;
260
+ this . handleDebuggerConnection ( oldDebugger . socket , oldDebugger . pageId , {
261
+ userAgent : oldDebugger . userAgent ,
262
+ } ) ;
263
+ }
264
+
265
+ this.#dangerouslyConstruct(
266
+ id,
267
+ name,
268
+ app,
269
+ socket,
270
+ projectRoot,
271
+ eventReporter,
272
+ createMessageMiddleware,
273
+ );
274
+ }
275
+
205
276
getName ( ) : string {
206
277
return this . #name;
207
278
}
@@ -373,40 +444,6 @@ export default class Device {
373
444
} ;
374
445
}
375
446
376
- /**
377
- * Handles cleaning up a duplicate device connection, by client-side device ID.
378
- * 1. Checks if the same device is attempting to reconnect for the same app.
379
- * 2. If not, close both the device and debugger socket.
380
- * 3. If the debugger connection can be reused, close the device socket only.
381
- *
382
- * This allows users to reload the app, either as result of a crash, or manually
383
- * reloading, without having to restart the debugger.
384
- */
385
- handleDuplicateDeviceConnection(newDevice: Device) {
386
- if (
387
- this . #app !== newDevice . getApp ( ) ||
388
- this . #name !== newDevice . getName ( )
389
- ) {
390
- this . #deviceSocket. close ( ) ;
391
- this . #debuggerConnection?. socket . close ( ) ;
392
- }
393
-
394
- const oldDebugger = this.#debuggerConnection;
395
- this.#debuggerConnection = null;
396
-
397
- if (oldDebugger) {
398
- oldDebugger . socket . removeAllListeners ( ) ;
399
- this . #deviceSocket. close ( ) ;
400
- newDevice . handleDebuggerConnection (
401
- oldDebugger . socket ,
402
- oldDebugger . pageId ,
403
- {
404
- userAgent : oldDebugger . userAgent ,
405
- } ,
406
- ) ;
407
- }
408
- }
409
-
410
447
/**
411
448
* Returns `true` if a page supports the given target capability flag.
412
449
*/
@@ -927,4 +964,8 @@ export default class Device {
927
964
928
965
return this . #pageHasCapability( page , 'prefersFuseboxFrontend' ) ;
929
966
}
967
+
968
+ dangerouslyGetSocket ( ) : WS {
969
+ return this . #deviceSocket;
970
+ }
930
971
}
0 commit comments