Fix Android Connection Swap Issue (Server Side)

Summary:
- Take device id retrieved by matching CSR to the given CSR destination
  - Otherwise, use the previous way

Note:
- Backward compatibility will be fixed next diff

Reviewed By: jknoxville

Differential Revision: D17346422

fbshipit-source-id: 59b2fb9849373db1ba930dde702194c5fb201678
This commit is contained in:
Chaiwat Ekkaewnumchai
2019-09-16 05:31:10 -07:00
committed by Facebook Github Bot
parent 88197a7076
commit 2239ca65a5
3 changed files with 84 additions and 49 deletions

View File

@@ -112,7 +112,6 @@ export default class Client extends EventEmitter {
lessPlugins: Plugins | undefined;
showAllPlugins: boolean;
connection: RSocketClientSocket<any, any> | null | undefined;
responder: Partial<Responder<string, any>>;
store: Store;
activePlugins: Set<string>;
device: Promise<BaseDevice>;
@@ -121,6 +120,7 @@ export default class Client extends EventEmitter {
logger: Logger;
lastSeenDeviceList: Array<BaseDevice>;
broadcastCallbacks: Map<string, Map<string, Set<Function>>>;
rIC: any;
requestCallbacks: Map<
number,
@@ -162,19 +162,12 @@ export default class Client extends EventEmitter {
const client = this;
// node.js doesn't support requestIdleCallback
const rIC =
this.rIC =
typeof window === 'undefined'
? (cb: Function, _: any) => {
cb();
}
: window.requestIdleCallback;
this.responder = {
fireAndForget: (payload: {data: string}) =>
rIC(() => client.onMessage(payload.data), {
timeout: 500,
}),
};
: window.requestIdleCallback.bind(window);
if (conn) {
conn.connectionStatus().subscribe({

View File

@@ -28,6 +28,11 @@ type ClientInfo = {
client: Client;
};
type ClientCsrQuery = {
csr?: string | undefined;
csr_path?: string | undefined;
};
declare interface Server {
on(event: 'new-client', callback: (client: Client) => void): this;
on(event: 'error', callback: (err: Error) => void): this;
@@ -124,16 +129,24 @@ class Server extends EventEmitter {
if (!payload.data) {
return {};
}
const clientData: ClientQuery = JSON.parse(payload.data);
const clientData: ClientQuery & ClientCsrQuery = JSON.parse(payload.data);
this.connectionTracker.logConnectionAttempt(clientData);
const client = this.addConnection(socket, clientData);
const {app, os, device, device_id, sdk_version, csr, csr_path} = clientData;
const client: Promise<Client> = this.addConnection(
socket,
{app, os, device, device_id, sdk_version},
{csr, csr_path},
);
socket.connectionStatus().subscribe({
onNext(payload) {
if (payload.kind == 'ERROR' || payload.kind == 'CLOSED') {
console.debug(`Device disconnected ${client.id}`, 'server');
server.removeConnection(client.id);
client.then(client => {
console.debug(`Device disconnected ${client.id}`, 'server');
server.removeConnection(client.id);
});
}
},
onSubscribe(subscription) {
@@ -141,7 +154,14 @@ class Server extends EventEmitter {
},
});
return client.responder;
return {
fireAndForget: (payload: {data: string}) =>
client.then(client => {
client.rIC(() => client.onMessage(payload.data), {
timeout: 500,
});
}),
};
};
_untrustedRequestHandler = (
@@ -272,49 +292,66 @@ class Server extends EventEmitter {
return null;
}
addConnection(
async addConnection(
conn: RSocketClientSocket<any, any>,
query: ClientQuery,
): Client {
csrQuery: ClientCsrQuery,
): Promise<Client> {
invariant(query, 'expected query');
const id = `${query.app}#${query.os}#${query.device}#${query.device_id}`;
console.debug(`Device connected: ${id}`, 'server');
// try to get id by comparing giving `csr` to file from `csr_path`
// otherwise, use given device_id
const {csr_path, csr} = csrQuery;
return (csr_path && csr
? this.certificateProvider.extractAppNameFromCSR(csr).then(appName => {
return this.certificateProvider.getTargetDeviceId(
query.os,
appName,
csr_path,
csr,
);
})
: Promise.resolve(query.device_id)
).then(csrId => {
query.device_id = csrId;
const id = `${query.app}#${query.os}#${query.device}#${csrId}`;
console.debug(`Device connected: ${id}`, 'server');
const client = new Client(id, query, conn, this.logger, this.store);
const client = new Client(id, query, conn, this.logger, this.store);
const info = {
client,
connection: conn,
};
const info = {
client,
connection: conn,
};
client.init().then(() => {
console.debug(
`Device client initialised: ${id}. Supported plugins: ${client.plugins.join(
', ',
)}`,
'server',
);
client.init().then(() => {
console.debug(
`Device client initialised: ${id}. Supported plugins: ${client.plugins.join(
', ',
)}`,
'server',
);
/* If a device gets disconnected without being cleaned up properly,
* Flipper won't be aware until it attempts to reconnect.
* When it does we need to terminate the zombie connection.
*/
if (this.connections.has(id)) {
const connectionInfo = this.connections.get(id);
connectionInfo &&
connectionInfo.connection &&
connectionInfo.connection.close();
this.removeConnection(id);
}
/* If a device gets disconnected without being cleaned up properly,
* Flipper won't be aware until it attempts to reconnect.
* When it does we need to terminate the zombie connection.
*/
if (this.connections.has(id)) {
const connectionInfo = this.connections.get(id);
connectionInfo &&
connectionInfo.connection &&
connectionInfo.connection.close();
this.removeConnection(id);
}
this.connections.set(id, info);
this.emit('new-client', client);
this.emit('clients-change');
client.emit('plugins-change');
this.connections.set(id, info);
this.emit('new-client', client);
this.emit('clients-change');
client.emit('plugins-change');
});
return client;
});
return client;
}
attachFakeClient(client: Client) {

View File

@@ -346,7 +346,12 @@ export default class CertificateProvider {
return androidUtil
.pull(deviceId, processName, directory + csrFileName)
.then(deviceCsr => {
return this.santitizeString(deviceCsr.toString()) === csr;
// Santitize both of the string before comparation
// The csr string extraction on client side return string in both way
return (
this.santitizeString(deviceCsr.toString()) ===
this.santitizeString(csr)
);
});
}