Fix pending connections for websocket browser connections
Summary: Connections from VSCode and Kite would remain forever pending because they don't go through the secure connection handler. This diff fixes that. Also removed the separate event that existed for that, since registering a new client is already a 'success' signal, so it doesn't need a separate event. It turned out that the VSCode pending connection is actually correct, as it never handles the `getPlugins` event, so apparently the handling is broken. Added timeouts to guard against that as well. Applied several code simplications as well. Introduced an explicit cert exchange medium 'NONE' so that in code it is a bit clearer where CSR negotiation is supposed to happen. Changelog: Fixed an issue where Kite / Unity apps didn't connect anymore Reviewed By: timur-valiev Differential Revision: D30866301 fbshipit-source-id: 8bd214fd9eebcd9a7583f1b44ee283883002f62e
This commit is contained in:
committed by
Facebook GitHub Bot
parent
d8f77db632
commit
80f48b444c
@@ -26,7 +26,12 @@ import {processMessagesLater} from './utils/messageQueue';
|
|||||||
import {emitBytesReceived} from './dispatcher/tracking';
|
import {emitBytesReceived} from './dispatcher/tracking';
|
||||||
import {debounce} from 'lodash';
|
import {debounce} from 'lodash';
|
||||||
import {batch} from 'react-redux';
|
import {batch} from 'react-redux';
|
||||||
import {createState, _SandyPluginInstance, getFlipperLib} from 'flipper-plugin';
|
import {
|
||||||
|
createState,
|
||||||
|
_SandyPluginInstance,
|
||||||
|
getFlipperLib,
|
||||||
|
timeout,
|
||||||
|
} from 'flipper-plugin';
|
||||||
import {freeze} from 'immer';
|
import {freeze} from 'immer';
|
||||||
import GK from './fb-stubs/GK';
|
import GK from './fb-stubs/GK';
|
||||||
import {message} from 'antd';
|
import {message} from 'antd';
|
||||||
@@ -230,9 +235,10 @@ export default class Client extends EventEmitter {
|
|||||||
|
|
||||||
// get the supported plugins
|
// get the supported plugins
|
||||||
async loadPlugins(): Promise<Plugins> {
|
async loadPlugins(): Promise<Plugins> {
|
||||||
const {plugins} = await this.rawCall<{plugins: Plugins}>(
|
const {plugins} = await timeout(
|
||||||
'getPlugins',
|
30 * 1000,
|
||||||
false,
|
this.rawCall<{plugins: Plugins}>('getPlugins', false),
|
||||||
|
'Fetch plugin timeout for ' + this.id,
|
||||||
);
|
);
|
||||||
this.plugins = new Set(plugins);
|
this.plugins = new Set(plugins);
|
||||||
return plugins;
|
return plugins;
|
||||||
@@ -307,10 +313,12 @@ export default class Client extends EventEmitter {
|
|||||||
if (this.sdkVersion < 4) {
|
if (this.sdkVersion < 4) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return this.rawCall<{plugins: PluginsArr}>(
|
const data = await timeout(
|
||||||
'getBackgroundPlugins',
|
30 * 1000,
|
||||||
false,
|
this.rawCall<{plugins: PluginsArr}>('getBackgroundPlugins', false),
|
||||||
).then((data) => data.plugins);
|
'Fetch background plugins timeout for ' + this.id,
|
||||||
|
);
|
||||||
|
return data.plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the plugins, and update the UI
|
// get the plugins, and update the UI
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export default async (store: Store, logger: Logger) => {
|
|||||||
|
|
||||||
server.on('device-connected', (device) => {
|
server.on('device-connected', (device) => {
|
||||||
logger.track('usage', 'register-device', {
|
logger.track('usage', 'register-device', {
|
||||||
os: 'Android',
|
os: device.os,
|
||||||
name: device.title,
|
name: device.title,
|
||||||
serial: device.serial,
|
serial: device.serial,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import type BaseDevice from '../server/devices/BaseDevice';
|
|||||||
import MacDevice from '../server/devices/desktop/MacDevice';
|
import MacDevice from '../server/devices/desktop/MacDevice';
|
||||||
import type Client from '../Client';
|
import type Client from '../Client';
|
||||||
import type {UninitializedClient} from '../server/UninitializedClient';
|
import type {UninitializedClient} from '../server/UninitializedClient';
|
||||||
import {isEqual} from 'lodash';
|
|
||||||
import {performance} from 'perf_hooks';
|
import {performance} from 'perf_hooks';
|
||||||
import type {Actions, Store} from '.';
|
import type {Actions, Store} from '.';
|
||||||
import {WelcomeScreenStaticView} from '../sandy-chrome/WelcomeScreen';
|
import {WelcomeScreenStaticView} from '../sandy-chrome/WelcomeScreen';
|
||||||
@@ -73,11 +72,7 @@ type StateV2 = {
|
|||||||
enabledPlugins: {[client: string]: string[]};
|
enabledPlugins: {[client: string]: string[]};
|
||||||
enabledDevicePlugins: Set<string>;
|
enabledDevicePlugins: Set<string>;
|
||||||
clients: Array<Client>;
|
clients: Array<Client>;
|
||||||
uninitializedClients: Array<{
|
uninitializedClients: UninitializedClient[];
|
||||||
client: UninitializedClient;
|
|
||||||
deviceId?: string;
|
|
||||||
errorMessage?: string;
|
|
||||||
}>;
|
|
||||||
deepLinkPayload: unknown;
|
deepLinkPayload: unknown;
|
||||||
staticView: StaticView;
|
staticView: StaticView;
|
||||||
selectedAppPluginListRevision: number;
|
selectedAppPluginListRevision: number;
|
||||||
@@ -137,10 +132,6 @@ export type Action =
|
|||||||
type: 'START_CLIENT_SETUP';
|
type: 'START_CLIENT_SETUP';
|
||||||
payload: UninitializedClient;
|
payload: UninitializedClient;
|
||||||
}
|
}
|
||||||
| {
|
|
||||||
type: 'FINISH_CLIENT_SETUP';
|
|
||||||
payload: {client: UninitializedClient; deviceId: string};
|
|
||||||
}
|
|
||||||
| {
|
| {
|
||||||
type: 'SET_STATIC_VIEW';
|
type: 'SET_STATIC_VIEW';
|
||||||
payload: StaticView;
|
payload: StaticView;
|
||||||
@@ -366,8 +357,8 @@ export default (state: State = INITAL_STATE, action: Actions): State => {
|
|||||||
clients: newClients,
|
clients: newClients,
|
||||||
uninitializedClients: state.uninitializedClients.filter((c) => {
|
uninitializedClients: state.uninitializedClients.filter((c) => {
|
||||||
return (
|
return (
|
||||||
c.deviceId !== payload.query.device_id ||
|
c.deviceName !== payload.query.device ||
|
||||||
c.client.appName !== payload.query.app
|
c.appName !== payload.query.app
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -402,23 +393,7 @@ export default (state: State = INITAL_STATE, action: Actions): State => {
|
|||||||
const {payload} = action;
|
const {payload} = action;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
uninitializedClients: state.uninitializedClients
|
uninitializedClients: [...state.uninitializedClients, payload],
|
||||||
.filter((entry) => !isEqual(entry.client, payload))
|
|
||||||
.concat([{client: payload}])
|
|
||||||
.sort((a, b) => a.client.appName.localeCompare(b.client.appName)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case 'FINISH_CLIENT_SETUP': {
|
|
||||||
const {payload} = action;
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
uninitializedClients: state.uninitializedClients
|
|
||||||
.map((c) =>
|
|
||||||
isEqual(c.client, payload.client)
|
|
||||||
? {...c, deviceId: payload.deviceId}
|
|
||||||
: c,
|
|
||||||
)
|
|
||||||
.sort((a, b) => a.client.appName.localeCompare(b.client.appName)),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'REGISTER_PLUGINS': {
|
case 'REGISTER_PLUGINS': {
|
||||||
|
|||||||
@@ -223,8 +223,8 @@ function computeEntries(
|
|||||||
Currently connecting...
|
Currently connecting...
|
||||||
</Menu.Item>,
|
</Menu.Item>,
|
||||||
...uninitializedClients.map((client) => (
|
...uninitializedClients.map((client) => (
|
||||||
<Menu.Item key={'connecting' + client.client.appName}>
|
<Menu.Item key={'connecting' + client.appName}>
|
||||||
{client.client.appName}
|
{`${client.appName} (${client.deviceName})`}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
)),
|
)),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -113,16 +113,6 @@ export class FlipperServer {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
server.addListener(
|
|
||||||
'finish-client-setup',
|
|
||||||
(payload: {client: UninitializedClient; deviceId: string}) => {
|
|
||||||
this.store.dispatch({
|
|
||||||
type: 'FINISH_CLIENT_SETUP',
|
|
||||||
payload: payload,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
server.addListener(
|
server.addListener(
|
||||||
'client-setup-error',
|
'client-setup-error',
|
||||||
({client, error}: {client: UninitializedClient; error: Error}) => {
|
({client, error}: {client: UninitializedClient; error: Error}) => {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export type ClientCsrQuery = {
|
|||||||
* ClientCsrQuery. It also adds medium information.
|
* ClientCsrQuery. It also adds medium information.
|
||||||
*/
|
*/
|
||||||
export type SecureClientQuery = ClientQuery &
|
export type SecureClientQuery = ClientQuery &
|
||||||
ClientCsrQuery & {medium: number | undefined};
|
ClientCsrQuery & {medium: 1 /*FS*/ | 2 /*WWW*/ | 3 /*NONE*/ | undefined};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines an interface for events triggered by a running server interacting
|
* Defines an interface for events triggered by a running server interacting
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import ServerAdapter, {
|
|||||||
import {createBrowserServer, createServer} from './ServerFactory';
|
import {createBrowserServer, createServer} from './ServerFactory';
|
||||||
import {FlipperServer} from '../FlipperServer';
|
import {FlipperServer} from '../FlipperServer';
|
||||||
import {isTest} from '../../utils/isProduction';
|
import {isTest} from '../../utils/isProduction';
|
||||||
|
import {timeout} from 'flipper-plugin';
|
||||||
|
|
||||||
type ClientInfo = {
|
type ClientInfo = {
|
||||||
connection: ClientConnection | null | undefined;
|
connection: ClientConnection | null | undefined;
|
||||||
@@ -187,15 +188,15 @@ class ServerController extends EventEmitter implements ServerEventsListener {
|
|||||||
const transformedMedium = transformCertificateExchangeMediumToType(
|
const transformedMedium = transformCertificateExchangeMediumToType(
|
||||||
clientQuery.medium,
|
clientQuery.medium,
|
||||||
);
|
);
|
||||||
if (transformedMedium === 'WWW') {
|
if (transformedMedium === 'WWW' || transformedMedium === 'NONE') {
|
||||||
this.store.dispatch({
|
this.flipperServer.registerDevice(
|
||||||
type: 'REGISTER_DEVICE',
|
new DummyDevice(
|
||||||
payload: new DummyDevice(
|
|
||||||
clientQuery.device_id,
|
clientQuery.device_id,
|
||||||
clientQuery.app + ' Server Exchanged',
|
clientQuery.app +
|
||||||
|
(transformedMedium === 'WWW' ? ' Server Exchanged' : ''),
|
||||||
clientQuery.os,
|
clientQuery.os,
|
||||||
),
|
),
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,11 +248,6 @@ class ServerController extends EventEmitter implements ServerEventsListener {
|
|||||||
});
|
});
|
||||||
}, 30 * 1000);
|
}, 30 * 1000);
|
||||||
|
|
||||||
this.emit('finish-client-setup', {
|
|
||||||
client,
|
|
||||||
deviceId: response.deviceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
resolve(response);
|
resolve(response);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -285,8 +281,8 @@ class ServerController extends EventEmitter implements ServerEventsListener {
|
|||||||
// otherwise, use given device_id
|
// otherwise, use given device_id
|
||||||
const {csr_path, csr} = csrQuery;
|
const {csr_path, csr} = csrQuery;
|
||||||
|
|
||||||
// For iOS we do not need to confirm the device id, as it never changes unlike android.
|
// For Android, device id might change
|
||||||
if (csr_path && csr && query.os != 'iOS') {
|
if (csr_path && csr && query.os === 'Android') {
|
||||||
const app_name = await this.certificateProvider.extractAppNameFromCSR(
|
const app_name = await this.certificateProvider.extractAppNameFromCSR(
|
||||||
csr,
|
csr,
|
||||||
);
|
);
|
||||||
@@ -312,6 +308,7 @@ class ServerController extends EventEmitter implements ServerEventsListener {
|
|||||||
console.log(
|
console.log(
|
||||||
`[conn] Matching device for ${query.app} on ${query.device_id}...`,
|
`[conn] Matching device for ${query.app} on ${query.device_id}...`,
|
||||||
);
|
);
|
||||||
|
// TODO: grab device from flipperServer.devices instead of store
|
||||||
const device =
|
const device =
|
||||||
getDeviceBySerial(this.store.getState(), query.device_id) ??
|
getDeviceBySerial(this.store.getState(), query.device_id) ??
|
||||||
(await findDeviceForConnection(this.store, query.app, query.device_id));
|
(await findDeviceForConnection(this.store, query.app, query.device_id));
|
||||||
@@ -335,7 +332,11 @@ class ServerController extends EventEmitter implements ServerEventsListener {
|
|||||||
`[conn] Initializing client ${query.app} on ${query.device_id}...`,
|
`[conn] Initializing client ${query.app} on ${query.device_id}...`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await client.init();
|
await timeout(
|
||||||
|
30 * 1000,
|
||||||
|
client.init(),
|
||||||
|
`[conn] Failed to initialize client ${query.app} on ${query.device_id} in a timely manner`,
|
||||||
|
);
|
||||||
|
|
||||||
connection.subscribeToEvents((status: ConnectionStatus) => {
|
connection.subscribeToEvents((status: ConnectionStatus) => {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -281,8 +281,10 @@ class ServerWebSocket extends ServerWebSocketBase {
|
|||||||
if (typeof query.medium === 'string') {
|
if (typeof query.medium === 'string') {
|
||||||
medium = parseInt(query.medium, 10);
|
medium = parseInt(query.medium, 10);
|
||||||
}
|
}
|
||||||
|
if (medium !== undefined && (medium < 1 || medium > 3)) {
|
||||||
return {...clientQuery, csr, csr_path, medium};
|
throw new Error('Unsupported exchange medium: ' + medium);
|
||||||
|
}
|
||||||
|
return {...clientQuery, csr, csr_path, medium: medium as any};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class ServerWebSocketBrowser extends ServerWebSocketBase {
|
|||||||
plugins,
|
plugins,
|
||||||
);
|
);
|
||||||
|
|
||||||
const extendedClientQuery = {...clientQuery, medium: 1};
|
const extendedClientQuery = {...clientQuery, medium: 3 as const};
|
||||||
extendedClientQuery.sdk_version = plugins == null ? 4 : 1;
|
extendedClientQuery.sdk_version = plugins == null ? 4 : 1;
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
@@ -118,6 +118,7 @@ class ServerWebSocketBrowser extends ServerWebSocketBase {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let resolvedClient: Client | null = null;
|
let resolvedClient: Client | null = null;
|
||||||
|
this.listener.onSecureConnectionAttempt(extendedClientQuery);
|
||||||
const client: Promise<Client> = this.listener.onConnectionCreated(
|
const client: Promise<Client> = this.listener.onConnectionCreated(
|
||||||
extendedClientQuery,
|
extendedClientQuery,
|
||||||
clientConnection,
|
clientConnection,
|
||||||
|
|||||||
@@ -18,12 +18,16 @@ import {ClientQuery} from '../../Client';
|
|||||||
export function transformCertificateExchangeMediumToType(
|
export function transformCertificateExchangeMediumToType(
|
||||||
medium: number | undefined,
|
medium: number | undefined,
|
||||||
): CertificateExchangeMedium {
|
): CertificateExchangeMedium {
|
||||||
if (medium == 1) {
|
switch (medium) {
|
||||||
return 'FS_ACCESS';
|
case undefined:
|
||||||
} else if (medium == 2) {
|
case 1:
|
||||||
return 'WWW';
|
return 'FS_ACCESS';
|
||||||
} else {
|
case 2:
|
||||||
return 'FS_ACCESS';
|
return 'WWW';
|
||||||
|
case 3:
|
||||||
|
return 'NONE';
|
||||||
|
default:
|
||||||
|
throw new Error('Unknown Certificate exchange medium: ' + medium);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import {timeout} from 'flipper-plugin';
|
|||||||
import {v4 as uuid} from 'uuid';
|
import {v4 as uuid} from 'uuid';
|
||||||
import {isTest} from '../../utils/isProduction';
|
import {isTest} from '../../utils/isProduction';
|
||||||
|
|
||||||
export type CertificateExchangeMedium = 'FS_ACCESS' | 'WWW';
|
export type CertificateExchangeMedium = 'FS_ACCESS' | 'WWW' | 'NONE';
|
||||||
|
|
||||||
const tmpFile = promisify(tmp.file) as (
|
const tmpFile = promisify(tmp.file) as (
|
||||||
options?: FileOptions,
|
options?: FileOptions,
|
||||||
|
|||||||
Reference in New Issue
Block a user