Skip to content

Commit 6695b6e

Browse files
motiz88facebook-github-bot
authored andcommitted
Reuse Device instances when handing off connections based on device ID (facebook#45027)
Summary: Pull Request resolved: facebook#45027 Changelog: [Internal] Changes the device ID collision handling logic to reuse `Device` instances instead of creating new ones. This enables further refactoring of `Device` to improve session state isolation. Reviewed By: hoxyq Differential Revision: D58724884 fbshipit-source-id: bc11ce45ce8c80c58c32dcd1b07b28f1d1753a62
1 parent 4ecc165 commit 6695b6e

File tree

2 files changed

+105
-54
lines changed

2 files changed

+105
-54
lines changed

packages/dev-middleware/src/inspector-proxy/Device.js

Lines changed: 83 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type {
2929

3030
import DeviceEventReporter from './DeviceEventReporter';
3131
import * as fs from 'fs';
32+
import invariant from 'invariant';
3233
import fetch from 'node-fetch';
3334
import * as path from 'path';
3435
import WS from 'ws';
@@ -98,7 +99,7 @@ export default class Device {
9899
#deviceSocket: WS;
99100

100101
// 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();
102103

103104
// Stores information about currently connected debugger (if any).
104105
#debuggerConnection: ?DebuggerConnection = null;
@@ -135,11 +136,30 @@ export default class Device {
135136
projectRoot: string,
136137
eventReporter: ?EventReporter,
137138
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,
138159
) {
139160
this.#id = id;
140161
this.#name = name;
141162
this.#app = app;
142-
this.#pages = new Map();
143163
this.#deviceSocket = socket;
144164
this.#projectRoot = projectRoot;
145165
this.#deviceEventReporter = eventReporter
@@ -192,16 +212,67 @@ export default class Device {
192212
PAGES_POLLING_INTERVAL,
193213
);
194214
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);
200223
}
201-
clearInterval(this.#pagesPollingIntervalId);
202224
});
203225
}
204226

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+
205276
getName(): string {
206277
return this.#name;
207278
}
@@ -373,40 +444,6 @@ export default class Device {
373444
};
374445
}
375446

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-
410447
/**
411448
* Returns `true` if a page supports the given target capability flag.
412449
*/
@@ -927,4 +964,8 @@ export default class Device {
927964

928965
return this.#pageHasCapability(page, 'prefersFuseboxFrontend');
929966
}
967+
968+
dangerouslyGetSocket(): WS {
969+
return this.#deviceSocket;
970+
}
930971
}

packages/dev-middleware/src/inspector-proxy/InspectorProxy.js

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -209,18 +209,28 @@ export default class InspectorProxy implements InspectorProxyQueries {
209209
const appName = query.app || 'Unknown';
210210

211211
const oldDevice = this.#devices.get(deviceId);
212-
const newDevice = new Device(
213-
deviceId,
214-
deviceName,
215-
appName,
216-
socket,
217-
this.#projectRoot,
218-
this.#eventReporter,
219-
this.#customMessageHandler,
220-
);
221-
212+
let newDevice;
222213
if (oldDevice) {
223-
oldDevice.handleDuplicateDeviceConnection(newDevice);
214+
oldDevice.dangerouslyRecreateDevice(
215+
deviceId,
216+
deviceName,
217+
appName,
218+
socket,
219+
this.#projectRoot,
220+
this.#eventReporter,
221+
this.#customMessageHandler,
222+
);
223+
newDevice = oldDevice;
224+
} else {
225+
newDevice = new Device(
226+
deviceId,
227+
deviceName,
228+
appName,
229+
socket,
230+
this.#projectRoot,
231+
this.#eventReporter,
232+
this.#customMessageHandler,
233+
);
224234
}
225235

226236
this.#devices.set(deviceId, newDevice);
@@ -230,7 +240,7 @@ export default class InspectorProxy implements InspectorProxyQueries {
230240
);
231241

232242
socket.on('close', () => {
233-
if (this.#devices.get(deviceId) === newDevice) {
243+
if (this.#devices.get(deviceId)?.dangerouslyGetSocket() === socket) {
234244
this.#devices.delete(deviceId);
235245
}
236246
debug(`Device ${deviceName} disconnected.`);

0 commit comments

Comments
 (0)