diff --git a/packages/metro-inspector-proxy/src/Device.js b/packages/metro-inspector-proxy/src/Device.js index cffa4f345d..a15856cdfe 100644 --- a/packages/metro-inspector-proxy/src/Device.js +++ b/packages/metro-inspector-proxy/src/Device.js @@ -55,7 +55,7 @@ const REACT_NATIVE_RELOADABLE_PAGE_ID = '-1'; */ class Device { // ID of the device. - _id: number; + _id: string; // Name of the device. _name: string; @@ -90,7 +90,7 @@ class Device { _projectRoot: string; constructor( - id: number, + id: string, name: string, app: string, socket: typeof WS, @@ -134,6 +134,10 @@ class Device { return this._name; } + getApp(): string { + return this._app; + } + getPagesList(): Array { if (this._lastConnectedReactNativePage) { const reactNativeReloadablePage = { @@ -212,6 +216,37 @@ class Device { }; } + /** + * Handles cleaning up a duplicate device connection, by client-side device ID. + * 1. Checks if the same device is attempting to reconnect for the same app. + * 2. If not, close both the device and debugger socket. + * 3. If the debugger connection can be reused, close the device socket only. + * + * This allows users to reload the app, either as result of a crash, or manually + * reloading, without having to restart the debugger. + */ + handleDuplicateDeviceConnection(newDevice: Device) { + if ( + this._app !== newDevice.getApp() || + this._name !== newDevice.getName() + ) { + this._deviceSocket.close(); + this._debuggerConnection?.socket.close(); + } + + const oldDebugger = this._debuggerConnection; + this._debuggerConnection = null; + + if (oldDebugger) { + oldDebugger.socket.removeAllListeners(); + this._deviceSocket.close(); + newDevice.handleDebuggerConnection( + oldDebugger.socket, + oldDebugger.pageId, + ); + } + } + // Handles messages received from device: // 1. For getPages responses updates local _pages list. // 2. All other messages are forwarded to debugger as wrappedEvent. diff --git a/packages/metro-inspector-proxy/src/InspectorProxy.js b/packages/metro-inspector-proxy/src/InspectorProxy.js index 96d4f0b19c..d9ee0ce18e 100644 --- a/packages/metro-inspector-proxy/src/InspectorProxy.js +++ b/packages/metro-inspector-proxy/src/InspectorProxy.js @@ -41,7 +41,7 @@ class InspectorProxy { _projectRoot: string; // Maps device ID to Device instance. - _devices: Map; + _devices: Map; // Internal counter for device IDs -- just gets incremented for each new device. _deviceCounter: number = 0; @@ -111,7 +111,7 @@ class InspectorProxy { // Converts page information received from device into PageDescription object // that is sent to debugger. _buildPageDescription( - deviceId: number, + deviceId: string, device: Device, page: Page, ): PageDescription { @@ -162,16 +162,31 @@ class InspectorProxy { // $FlowFixMe[value-as-type] wss.on('connection', async (socket: WS, req) => { try { + const fallbackDeviceId = String(this._deviceCounter++); + const query = url.parse(req.url || '', true).query || {}; + const deviceId = query.device || fallbackDeviceId; const deviceName = query.name || 'Unknown'; const appName = query.app || 'Unknown'; - const deviceId = this._deviceCounter++; - this._devices.set( + + const oldDevice = this._devices.get(deviceId); + const newDevice = new Device( deviceId, - new Device(deviceId, deviceName, appName, socket, this._projectRoot), + deviceName, + appName, + socket, + this._projectRoot, ); - debug(`Got new connection: device=${deviceName}, app=${appName}`); + if (oldDevice) { + oldDevice.handleDuplicateDeviceConnection(newDevice); + } + + this._devices.set(deviceId, newDevice); + + debug( + `Got new connection: name=${deviceName}, app=${appName}, device=${deviceId}`, + ); socket.on('close', () => { this._devices.delete(deviceId); @@ -206,7 +221,7 @@ class InspectorProxy { throw new Error('Incorrect URL - must provide device and page IDs'); } - const device = this._devices.get(parseInt(deviceId, 10)); + const device = this._devices.get(deviceId); if (device == null) { throw new Error('Unknown device with ID ' + deviceId); }