Migrate from socket.io
Reviewed By: passy Differential Revision: D34787674 fbshipit-source-id: 63d7c166ea29d14c96f0646a045e3f6fa93472e2
This commit is contained in:
committed by
Facebook GitHub Bot
parent
6ec3771824
commit
f85def32fb
@@ -11,7 +11,8 @@
|
||||
"dependenciesComment": "mac-ca is required dynamically for darwin, node-fetch is treated special in electron-requires, not sure why",
|
||||
"dependencies": {
|
||||
"mac-ca": "^1.0.6",
|
||||
"node-fetch": "^2.6.7"
|
||||
"node-fetch": "^2.6.7",
|
||||
"ws": "^8.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.13",
|
||||
@@ -25,7 +26,6 @@
|
||||
"metro": "^0.69.0",
|
||||
"open": "^8.3.0",
|
||||
"p-filter": "^2.1.0",
|
||||
"socket.io": "^4.4.1",
|
||||
"yargs": "^17.0.1"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
|
||||
@@ -11,8 +11,9 @@ import express, {Express} from 'express';
|
||||
import http from 'http';
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import socketio from 'socket.io';
|
||||
import {VerifyClientCallbackSync, WebSocketServer} from 'ws';
|
||||
import {WEBSOCKET_MAX_MESSAGE_SIZE} from 'flipper-server-core';
|
||||
import {parse} from 'url';
|
||||
|
||||
type Config = {
|
||||
port: number;
|
||||
@@ -23,7 +24,7 @@ type Config = {
|
||||
export async function startBaseServer(config: Config): Promise<{
|
||||
app: Express;
|
||||
server: http.Server;
|
||||
socket: socketio.Server;
|
||||
socket: WebSocketServer;
|
||||
}> {
|
||||
const {app, server} = await startAssetServer(config);
|
||||
const socket = addWebsocket(server, config);
|
||||
@@ -67,36 +68,60 @@ function addWebsocket(server: http.Server, config: Config) {
|
||||
const localhostIPV6NoBrackets = `::1:${config.port}`;
|
||||
|
||||
const possibleHosts = [localhostIPV4, localhostIPV6, localhostIPV6NoBrackets];
|
||||
const possibleOrigins = possibleHosts.map((host) => `http://${host}`);
|
||||
|
||||
const io = new socketio.Server(server, {
|
||||
maxHttpBufferSize: WEBSOCKET_MAX_MESSAGE_SIZE,
|
||||
allowRequest(req, callback) {
|
||||
const noOriginHeader = req.headers.origin === undefined;
|
||||
if (
|
||||
noOriginHeader &&
|
||||
req.headers.host &&
|
||||
possibleHosts.includes(req.headers.host)
|
||||
) {
|
||||
// no origin header? Either the request is not cross-origin,
|
||||
// or the request is not originating from a browser, so should be OK to pass through
|
||||
callback(null, true);
|
||||
} else {
|
||||
// for now we don't allow cross origin request, so that an arbitrary website cannot try to
|
||||
// connect a socket to localhost:serverport, and try to use the all powerful Flipper APIs to read
|
||||
// for example files.
|
||||
// Potentially in the future we do want to allow this, e.g. if we want to connect to a local flipper-server
|
||||
// directly from intern. But before that, we should either authenticate the request somehow,
|
||||
// and discuss security impact and for example scope the files that can be read by Flipper.
|
||||
console.warn(
|
||||
`Refused sockect connection from cross domain request, origin: ${
|
||||
req.headers.origin
|
||||
}, host: ${req.headers.host}. Expected: ${possibleHosts.join(
|
||||
' or ',
|
||||
)}`,
|
||||
);
|
||||
callback(null, false);
|
||||
}
|
||||
},
|
||||
const verifyClient: VerifyClientCallbackSync = ({origin, req}) => {
|
||||
const noOriginHeader = origin === undefined;
|
||||
if (
|
||||
(noOriginHeader || possibleOrigins.includes(origin)) &&
|
||||
req.headers.host &&
|
||||
possibleHosts.includes(req.headers.host)
|
||||
) {
|
||||
// no origin header? The request is not originating from a browser, so should be OK to pass through
|
||||
// If origin matches our own address, it means we are serving the page
|
||||
return true;
|
||||
} else {
|
||||
// for now we don't allow cross origin request, so that an arbitrary website cannot try to
|
||||
// connect a socket to localhost:serverport, and try to use the all powerful Flipper APIs to read
|
||||
// for example files.
|
||||
// Potentially in the future we do want to allow this, e.g. if we want to connect to a local flipper-server
|
||||
// directly from intern. But before that, we should either authenticate the request somehow,
|
||||
// and discuss security impact and for example scope the files that can be read by Flipper.
|
||||
console.warn(
|
||||
`Refused socket connection from cross domain request, origin: ${origin}, host: ${
|
||||
req.headers.host
|
||||
}. Expected origins: ${possibleOrigins.join(
|
||||
' or ',
|
||||
)}. Expected hosts: ${possibleHosts.join(' or ')}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const wss = new WebSocketServer({
|
||||
noServer: true,
|
||||
maxPayload: WEBSOCKET_MAX_MESSAGE_SIZE,
|
||||
verifyClient,
|
||||
});
|
||||
return io;
|
||||
|
||||
server.on('upgrade', function upgrade(request, socket, head) {
|
||||
const {pathname} = parse(request.url);
|
||||
|
||||
// Handled by Metro
|
||||
if (pathname === '/hot') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathname === '/') {
|
||||
wss.handleUpgrade(request, socket, head, function done(ws) {
|
||||
wss.emit('connection', ws, request);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('addWebsocket.upgrade -> unknown pathname', pathname);
|
||||
socket.destroy();
|
||||
});
|
||||
|
||||
return wss;
|
||||
}
|
||||
|
||||
@@ -8,53 +8,91 @@
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import {
|
||||
ClientWebSocketMessage,
|
||||
ExecResponseWebSocketMessage,
|
||||
ExecResponseErrorWebSocketMessage,
|
||||
ServerEventWebSocketMessage,
|
||||
} from 'flipper-common';
|
||||
import {FlipperServerImpl} from 'flipper-server-core';
|
||||
import socketio from 'socket.io';
|
||||
import {WebSocketServer} from 'ws';
|
||||
|
||||
export function startSocketServer(
|
||||
flipperServer: FlipperServerImpl,
|
||||
socket: socketio.Server,
|
||||
socket: WebSocketServer,
|
||||
) {
|
||||
socket.on('connection', (client) => {
|
||||
console.log(chalk.green(`Client connected ${client.id}`));
|
||||
socket.on('connection', (client, req) => {
|
||||
const clientAddress = `${req.socket.remoteAddress}:${req.socket.remotePort}`;
|
||||
|
||||
console.log(chalk.green(`Client connected ${clientAddress}`));
|
||||
|
||||
let connected = true;
|
||||
|
||||
function onServerEvent(event: string, payoad: any) {
|
||||
client.emit('event', event, payoad);
|
||||
function onServerEvent(event: string, payload: any) {
|
||||
const message = {
|
||||
event: 'server-event',
|
||||
payload: {
|
||||
event,
|
||||
data: payload,
|
||||
},
|
||||
} as ServerEventWebSocketMessage;
|
||||
client.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
flipperServer.onAny(onServerEvent);
|
||||
|
||||
client.on('exec', (id, command, args) => {
|
||||
flipperServer
|
||||
.exec(command, ...args)
|
||||
.then((result: any) => {
|
||||
if (connected) {
|
||||
client.emit('exec-response', id, result);
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
if (connected) {
|
||||
// TODO: Serialize error
|
||||
// TODO: log if verbose console.warn('Failed to handle response', error);
|
||||
client.emit(
|
||||
'exec-response-error',
|
||||
id,
|
||||
error.toString() + (error.stack ? `\n${error.stack}` : ''),
|
||||
);
|
||||
}
|
||||
});
|
||||
client.on('message', (data) => {
|
||||
const {event, payload} = JSON.parse(
|
||||
data.toString(),
|
||||
) as ClientWebSocketMessage;
|
||||
|
||||
switch (event) {
|
||||
case 'exec': {
|
||||
const {id, command, args} = payload;
|
||||
|
||||
flipperServer
|
||||
.exec(command, ...args)
|
||||
.then((result: any) => {
|
||||
if (connected) {
|
||||
const response: ExecResponseWebSocketMessage = {
|
||||
event: 'exec-response',
|
||||
payload: {
|
||||
id,
|
||||
data: result,
|
||||
},
|
||||
};
|
||||
|
||||
client.send(JSON.stringify(response));
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
if (connected) {
|
||||
// TODO: Serialize error
|
||||
// TODO: log if verbose console.warn('Failed to handle response', error);
|
||||
const responseError: ExecResponseErrorWebSocketMessage = {
|
||||
event: 'exec-response-error',
|
||||
payload: {
|
||||
id,
|
||||
data:
|
||||
error.toString() +
|
||||
(error.stack ? `\n${error.stack}` : ''),
|
||||
},
|
||||
};
|
||||
client.send(JSON.stringify(responseError));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client.on('disconnect', () => {
|
||||
console.log(chalk.red(`Client disconnected ${client.id}`));
|
||||
client.on('close', () => {
|
||||
console.log(chalk.red(`Client disconnected ${clientAddress}`));
|
||||
connected = false;
|
||||
flipperServer.offAny(onServerEvent);
|
||||
});
|
||||
|
||||
client.on('error', (e) => {
|
||||
console.error(chalk.red(`Socket error ${client.id}`), e);
|
||||
console.error(chalk.red(`Socket error ${clientAddress}`), e);
|
||||
connected = false;
|
||||
flipperServer.offAny(onServerEvent);
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ import {Express} from 'express';
|
||||
import http from 'http';
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import socketio from 'socket.io';
|
||||
import {WebSocketServer} from 'ws';
|
||||
import pFilter from 'p-filter';
|
||||
import {homedir} from 'os';
|
||||
|
||||
@@ -51,7 +51,7 @@ export async function getPluginSourceFolders(): Promise<string[]> {
|
||||
export async function startWebServerDev(
|
||||
app: Express,
|
||||
server: http.Server,
|
||||
socket: socketio.Server,
|
||||
socket: WebSocketServer,
|
||||
rootDir: string,
|
||||
) {
|
||||
// prevent bundling!
|
||||
@@ -138,7 +138,11 @@ export async function startWebServerDev(
|
||||
connectMiddleware.attachHmrServer(server);
|
||||
app.use(function (err: any, _req: any, _res: any, next: any) {
|
||||
console.error(chalk.red('\n\nCompile error in client bundle\n'), err);
|
||||
socket.local.emit('hasErrors', err.toString());
|
||||
socket.clients.forEach((client) => {
|
||||
client.send(
|
||||
JSON.stringify({event: 'hasErrors', payload: err.toString()}),
|
||||
);
|
||||
});
|
||||
next();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user