Make client -> device association more reliable

Summary:
For every client, there should be a device, otherwise flipper isn't working properly. However, these two things are created independently and either can be created first.

So make the device a promise, that will get fulfilled with the device if any appears in a reasonable time frame.
Previously, if there wasn't a device at the time needed, we just used '' for the name, I've preserved this functionality, though with a potential small delay.

We should be able to get rid of the NEW_CLIENT_SANITY_CHECK in the connections reducer, because the success rate of this promise achieves the same result.

Reviewed By: passy

Differential Revision: D14242264

fbshipit-source-id: ad21a179160a766304ff90f8c81e0563b990ebac
This commit is contained in:
John Knox
2019-06-20 07:02:32 -07:00
committed by Facebook Github Bot
parent 9c99211221
commit a2663ea970

View File

@@ -16,6 +16,7 @@ import {setPluginState} from './reducers/pluginStates.js';
import {ReactiveSocket, PartialResponder} from 'rsocket-core'; import {ReactiveSocket, PartialResponder} from 'rsocket-core';
// $FlowFixMe perf_hooks is a new API in node // $FlowFixMe perf_hooks is a new API in node
import {performance} from 'perf_hooks'; import {performance} from 'perf_hooks';
import {reportPlatformFailures} from './utils/metrics';
import {reportPluginFailures} from './utils/metrics'; import {reportPluginFailures} from './utils/metrics';
import {default as isProduction} from './utils/isProduction.js'; import {default as isProduction} from './utils/isProduction.js';
import {registerPlugins} from './reducers/plugins'; import {registerPlugins} from './reducers/plugins';
@@ -108,6 +109,7 @@ export default class Client extends EventEmitter {
this.broadcastCallbacks = new Map(); this.broadcastCallbacks = new Map();
this.requestCallbacks = new Map(); this.requestCallbacks = new Map();
this.activePlugins = new Set(); this.activePlugins = new Set();
this.lastSeenDeviceList = [];
const client = this; const client = this;
// node.js doesn't support requestIdleCallback // node.js doesn't support requestIdleCallback
@@ -147,12 +149,49 @@ export default class Client extends EventEmitter {
} }
} }
getDevice = (): ?BaseDevice => /* All clients should have a corresponding Device in the store.
this.store However, clients can connect before a device is registered, so wait a
while for the device to be registered if it isn't already. */
setMatchingDevice(): void {
if (this.device) {
return;
}
this.device = reportPlatformFailures(
new Promise((resolve, reject) => {
const device: ?BaseDevice = this.store
.getState() .getState()
.connections.devices.find( .connections.devices.find(
(device: BaseDevice) => device.serial === this.query.device_id, (device: BaseDevice) => device.serial === this.query.device_id,
); );
if (device) {
resolve(device);
return;
}
const unsubscribe = this.store.subscribe(() => {
const newDeviceList = this.store.getState().connections.devices;
if (newDeviceList === this.lastSeenDeviceList) {
return;
}
this.lastSeenDeviceList = this.store.getState().connections.devices;
const matchingDevice = newDeviceList.find(
(device: BaseDevice) => device.serial === this.query.device_id,
);
if (matchingDevice) {
resolve(matchingDevice);
unsubscribe();
}
});
setTimeout(() => {
unsubscribe();
const error = `Timed out waiting for device for client ${this.id}`;
console.error(error);
reject(error);
}, 5000);
}),
'client-setMatchingDevice',
);
}
on: ((event: 'plugins-change', callback: () => void) => void) & on: ((event: 'plugins-change', callback: () => void) => void) &
((event: 'close', callback: () => void) => void); ((event: 'close', callback: () => void) => void);
@@ -168,6 +207,7 @@ export default class Client extends EventEmitter {
responder: PartialResponder; responder: PartialResponder;
store: Store; store: Store;
activePlugins: Set<string>; activePlugins: Set<string>;
device: Promise<BaseDevice>;
broadcastCallbacks: Map<?string, Map<string, Set<Function>>>; broadcastCallbacks: Map<?string, Map<string, Set<Function>>>;
@@ -185,6 +225,7 @@ export default class Client extends EventEmitter {
} }
async init() { async init() {
this.setMatchingDevice();
await this.getPlugins(); await this.getPlugins();
} }
@@ -218,12 +259,16 @@ export default class Client extends EventEmitter {
this.emit('plugins-change'); this.emit('plugins-change');
} }
getDevice = (): ?BaseDevice => deviceSerial(): Promise<string> {
this.store return this.device
.getState() .then(device => device.serial)
.connections.devices.find( .catch(e => {
(device: BaseDevice) => device.serial === this.query.device_id, console.error(
'Using "" for deviceId because client has no matching device',
); );
return '';
});
}
onMessage(msg: string) { onMessage(msg: string) {
if (typeof msg !== 'string') { if (typeof msg !== 'string') {
@@ -259,7 +304,9 @@ export default class Client extends EventEmitter {
}: ${error.message} + \nDevice Stack Trace: ${error.stacktrace}`, }: ${error.message} + \nDevice Stack Trace: ${error.stacktrace}`,
'deviceError', 'deviceError',
); );
handleError(this.store, this.getDevice()?.serial, error); this.deviceSerial().then(serial =>
handleError(this.store, serial, error),
);
} else if (method === 'refreshPlugins') { } else if (method === 'refreshPlugins') {
this.refreshPlugins(); this.refreshPlugins();
} else if (method === 'execute') { } else if (method === 'execute') {
@@ -274,7 +321,9 @@ export default class Client extends EventEmitter {
//$FlowFixMe //$FlowFixMe
if (persistingPlugin.prototype instanceof FlipperDevicePlugin) { if (persistingPlugin.prototype instanceof FlipperDevicePlugin) {
// For device plugins, we are just using the device id instead of client id as the prefix. // For device plugins, we are just using the device id instead of client id as the prefix.
pluginKey = `${this.getDevice()?.serial || ''}#${params.api}`; this.deviceSerial().then(
serial => (pluginKey = `${serial}#${params.api}`),
);
} }
const persistedState = { const persistedState = {
...persistingPlugin.defaultPersistedState, ...persistingPlugin.defaultPersistedState,
@@ -335,7 +384,9 @@ export default class Client extends EventEmitter {
reject(data.error); reject(data.error);
const {error} = data; const {error} = data;
if (error) { if (error) {
handleError(this.store, this.getDevice()?.serial, error); this.deviceSerial().then(serial =>
handleError(this.store, serial, error),
);
} }
} else { } else {
// ??? // ???