Allow websocket connections from browser
Summary: Gets flipper to open up a websocket server for web browser connections. Reviewed By: passy, priteshrnandgaonkar Differential Revision: D19501123 fbshipit-source-id: e506f35d7ddce622128932494e8bb10802d3747b
This commit is contained in:
committed by
Facebook Github Bot
parent
9f899c7026
commit
a24b043df0
@@ -87,6 +87,7 @@
|
|||||||
"@types/uuid": "^3.4.5",
|
"@types/uuid": "^3.4.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.19.2",
|
"@typescript-eslint/eslint-plugin": "^2.19.2",
|
||||||
"@typescript-eslint/parser": "^2.19.2",
|
"@typescript-eslint/parser": "^2.19.2",
|
||||||
|
"@types/ws": "^7.2.0",
|
||||||
"babel-code-frame": "^6.26.0",
|
"babel-code-frame": "^6.26.0",
|
||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.1",
|
||||||
"electron": "7.1.2",
|
"electron": "7.1.2",
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {Store} from './reducers/index';
|
|||||||
import CertificateProvider from './utils/CertificateProvider';
|
import CertificateProvider from './utils/CertificateProvider';
|
||||||
import {RSocketServer} from 'rsocket-core';
|
import {RSocketServer} from 'rsocket-core';
|
||||||
import RSocketTCPServer from 'rsocket-tcp-server';
|
import RSocketTCPServer from 'rsocket-tcp-server';
|
||||||
import {Single} from 'rsocket-flowable';
|
|
||||||
import Client from './Client';
|
import Client from './Client';
|
||||||
import {FlipperClientConnection} from './Client';
|
import {FlipperClientConnection} from './Client';
|
||||||
import {UninitializedClient} from './UninitializedClient';
|
import {UninitializedClient} from './UninitializedClient';
|
||||||
@@ -27,6 +26,13 @@ import {Responder, Payload, ReactiveSocket} from 'rsocket-types';
|
|||||||
import GK from './fb-stubs/GK';
|
import GK from './fb-stubs/GK';
|
||||||
import {initJsEmulatorIPC} from './utils/js-client/serverUtils';
|
import {initJsEmulatorIPC} from './utils/js-client/serverUtils';
|
||||||
import {buildClientId} from './utils/clientUtils';
|
import {buildClientId} from './utils/clientUtils';
|
||||||
|
import {Single} from 'rsocket-flowable';
|
||||||
|
import WebSocket from 'ws';
|
||||||
|
import JSDevice from './devices/JSDevice';
|
||||||
|
import {WebsocketClientFlipperConnection} from './utils/js-client/websocketClientFlipperConnection';
|
||||||
|
import querystring from 'querystring';
|
||||||
|
import {IncomingMessage} from 'http';
|
||||||
|
const ws = window.require('ws'); // Electron tries to get you to use browser's ws instead, so can't use import.
|
||||||
|
|
||||||
type ClientInfo = {
|
type ClientInfo = {
|
||||||
connection: FlipperClientConnection<any, any> | null | undefined;
|
connection: FlipperClientConnection<any, any> | null | undefined;
|
||||||
@@ -86,6 +92,9 @@ class Server extends EventEmitter {
|
|||||||
this.insecureServer = this.startServer(insecure);
|
this.insecureServer = this.startServer(insecure);
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
if (GK.get('comet_enable_flipper_connection')) {
|
||||||
|
this.startWsServer(8333);
|
||||||
|
}
|
||||||
reportPlatformFailures(this.initialisePromise, 'initializeServer');
|
reportPlatformFailures(this.initialisePromise, 'initializeServer');
|
||||||
|
|
||||||
if (GK.get('flipper_js_client_emulator')) {
|
if (GK.get('flipper_js_client_emulator')) {
|
||||||
@@ -139,6 +148,90 @@ class Server extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startWsServer(port: number) {
|
||||||
|
const wss = new ws.Server({
|
||||||
|
host: 'localhost',
|
||||||
|
port,
|
||||||
|
verifyClient: (info: {
|
||||||
|
origin: string;
|
||||||
|
req: IncomingMessage;
|
||||||
|
secure: boolean;
|
||||||
|
}) => {
|
||||||
|
return info.origin.startsWith('chrome-extension://');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
wss.on('connection', (ws: WebSocket, message: any) => {
|
||||||
|
const clients: {[app: string]: Promise<Client>} = {};
|
||||||
|
const query = querystring.decode(message.url.split('?')[1]);
|
||||||
|
const deviceId: string =
|
||||||
|
typeof query.deviceId === 'string' ? query.deviceId : 'webbrowser';
|
||||||
|
this.store.dispatch({
|
||||||
|
type: 'REGISTER_DEVICE',
|
||||||
|
payload: new JSDevice(deviceId, 'Web Browser', 1),
|
||||||
|
});
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
Object.values(clients).map(p =>
|
||||||
|
p.then(c => this.removeConnection(c.id)),
|
||||||
|
);
|
||||||
|
this.store.dispatch({
|
||||||
|
type: 'UNREGISTER_DEVICES',
|
||||||
|
payload: new Set([deviceId]),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.on('message', (rawMessage: any) => {
|
||||||
|
const message = JSON.parse(rawMessage.toString());
|
||||||
|
switch (message.type) {
|
||||||
|
case 'connect': {
|
||||||
|
const app = message.app;
|
||||||
|
const plugins = message.plugins;
|
||||||
|
const client = this.addConnection(
|
||||||
|
new WebsocketClientFlipperConnection(ws, app, plugins),
|
||||||
|
{
|
||||||
|
app,
|
||||||
|
os: 'JSWebApp',
|
||||||
|
device: 'device',
|
||||||
|
device_id: deviceId,
|
||||||
|
sdk_version: 1,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
clients[app] = client;
|
||||||
|
client.then(c => {
|
||||||
|
ws.on('message', (m: any) => {
|
||||||
|
const parsed = JSON.parse(m.toString());
|
||||||
|
if (parsed.app === app) {
|
||||||
|
c.onMessage(JSON.stringify(parsed.payload));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'disconnect': {
|
||||||
|
const app = message.app;
|
||||||
|
(clients[app] || Promise.resolve()).then(c => {
|
||||||
|
this.removeConnection(c.id);
|
||||||
|
delete clients[app];
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('close', () => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('error', () => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
wss.on('error', (_ws: WebSocket) => {
|
||||||
|
console.error('error from wss');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_trustedRequestHandler = (
|
_trustedRequestHandler = (
|
||||||
socket: ReactiveSocket<string, any>,
|
socket: ReactiveSocket<string, any>,
|
||||||
payload: Payload<string, any>,
|
payload: Payload<string, any>,
|
||||||
|
|||||||
83
src/utils/js-client/websocketClientFlipperConnection.tsx
Normal file
83
src/utils/js-client/websocketClientFlipperConnection.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {FlipperClientConnection} from '../../Client';
|
||||||
|
import {Payload} from 'rsocket-types';
|
||||||
|
import {Flowable, Single} from 'rsocket-flowable';
|
||||||
|
import {ConnectionStatus, ISubscriber} from 'rsocket-types';
|
||||||
|
import WebSocket from 'ws';
|
||||||
|
|
||||||
|
export class WebsocketClientFlipperConnection<M>
|
||||||
|
implements FlipperClientConnection<string, M> {
|
||||||
|
websocket: WebSocket;
|
||||||
|
connStatusSubscribers: Set<ISubscriber<ConnectionStatus>> = new Set();
|
||||||
|
connStatus: ConnectionStatus;
|
||||||
|
app: string;
|
||||||
|
plugins: string[] = [];
|
||||||
|
|
||||||
|
constructor(ws: WebSocket, app: string, plugins: string[]) {
|
||||||
|
this.websocket = ws;
|
||||||
|
this.connStatus = {kind: 'CONNECTED'};
|
||||||
|
this.app = app;
|
||||||
|
this.plugins = plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionStatus(): Flowable<ConnectionStatus> {
|
||||||
|
return new Flowable<ConnectionStatus>(subscriber => {
|
||||||
|
subscriber.onSubscribe({
|
||||||
|
cancel: () => {
|
||||||
|
this.connStatusSubscribers.delete(subscriber);
|
||||||
|
},
|
||||||
|
request: _ => {
|
||||||
|
this.connStatusSubscribers.add(subscriber);
|
||||||
|
subscriber.onNext(this.connStatus);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
close(): void {
|
||||||
|
this.connStatus = {kind: 'CLOSED'};
|
||||||
|
this.connStatusSubscribers.forEach(subscriber => {
|
||||||
|
subscriber.onNext(this.connStatus);
|
||||||
|
});
|
||||||
|
this.websocket.send(JSON.stringify({type: 'disconnect', app: this.app}));
|
||||||
|
}
|
||||||
|
|
||||||
|
fireAndForget(payload: Payload<string, M>): void {
|
||||||
|
this.websocket.send(
|
||||||
|
JSON.stringify({
|
||||||
|
type: 'send',
|
||||||
|
app: this.app,
|
||||||
|
payload: payload.data != null ? payload.data : {},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: fully implement and return actual result
|
||||||
|
requestResponse(payload: Payload<string, M>): Single<Payload<string, M>> {
|
||||||
|
return new Single(subscriber => {
|
||||||
|
const method =
|
||||||
|
payload.data != null ? JSON.parse(payload.data).method : 'not-defined';
|
||||||
|
subscriber.onSubscribe(() => {});
|
||||||
|
if (method != 'getPlugins') {
|
||||||
|
this.fireAndForget(payload);
|
||||||
|
}
|
||||||
|
subscriber.onComplete(
|
||||||
|
method == 'getPlugins'
|
||||||
|
? {
|
||||||
|
data: JSON.stringify({
|
||||||
|
success: {plugins: this.plugins},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
: {data: JSON.stringify({success: null})},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1664,6 +1664,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/which/-/which-1.3.2.tgz#9c246fc0c93ded311c8512df2891fb41f6227fdf"
|
resolved "https://registry.yarnpkg.com/@types/which/-/which-1.3.2.tgz#9c246fc0c93ded311c8512df2891fb41f6227fdf"
|
||||||
integrity sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==
|
integrity sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==
|
||||||
|
|
||||||
|
"@types/ws@^7.2.0":
|
||||||
|
version "7.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.2.0.tgz#ed94695be01a77efd590244fc17c3b730e75d88a"
|
||||||
|
integrity sha512-HnqczxiZ828df9FUh9OyY7vSOelpQNaj+SLEnDvU74rYijp61ggV7dhmDlMky0oYXKLdVuIG4KvExk8DEqzJgQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/yargs-parser@*":
|
"@types/yargs-parser@*":
|
||||||
version "15.0.0"
|
version "15.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
|
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
|
||||||
|
|||||||
Reference in New Issue
Block a user