Skip to content

Commit

Permalink
Fix Web Debugging and Other Websocket Endpoints with Metro 0.67 (#1560)
Browse files Browse the repository at this point in the history
* Fix Web Debugging and Other Websocket Endpoints with Metro 0.67

Metro 0.67 included facebook/metro@38a200e which changed the way external applications should interface with it to add new WebSocket endpoints. This materializes as failures when web debugging RN 0.68 apps.

This change applies the reccomended fix, of passing a list of non-server endpoints to Metro when starting the server. I verified that web debugging works correctly after making the fix, for the same app where it previously did not work.

`ws` is updated as part of the change, along with removing tpe-checker suppressions, of which one was resulting in a runtime error.

* Remove session number

* chore: stop using stubs for isDebuggerConnected and broadcast

Co-authored-by: Michał Pierzchała <thymikee@gmail.com>
  • Loading branch information
NickGerleman and thymikee authored Feb 18, 2022
1 parent 6c1ccc0 commit e4b7805
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 131 deletions.
14 changes: 9 additions & 5 deletions packages/cli-plugin-metro/src/commands/start/runServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,12 @@ async function runServer(_argv: Array<string>, ctx: Config, args: Args) {
);
}

const {middleware, attachToServer} = createDevServerMiddleware({
const {
middleware,
websocketEndpoints,
messageSocketEndpoint,
eventsSocketEndpoint,
} = createDevServerMiddleware({
host: args.host,
port: metroConfig.server.port,
watchFolders: metroConfig.watchFolders,
Expand All @@ -94,14 +99,13 @@ async function runServer(_argv: Array<string>, ctx: Config, args: Args) {
secureCert: args.cert,
secureKey: args.key,
hmrEnabled: true,
websocketEndpoints,
});

const {messageSocket, eventsSocket} = attachToServer(serverInstance);

reportEvent = eventsSocket.reportEvent;
reportEvent = eventsSocketEndpoint.reportEvent;

if (args.interactive) {
enableWatchMode(messageSocket);
enableWatchMode(messageSocketEndpoint);
}

// In Node 8, the default keep-alive for an HTTP connection is 5 seconds. In
Expand Down
4 changes: 2 additions & 2 deletions packages/cli-server-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
"nocache": "^3.0.1",
"pretty-format": "^26.6.2",
"serve-static": "^1.13.1",
"ws": "^1.1.0"
"ws": "^7.5.1"
},
"devDependencies": {
"@types/compression": "^1.0.1",
"@types/connect": "^3.4.33",
"@types/errorhandler": "^0.0.32",
"@types/ws": "^6.0.3"
"@types/ws": "^7.4.7"
},
"files": [
"build",
Expand Down
52 changes: 19 additions & 33 deletions packages/cli-server-api/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import http, {Server as HttpServer} from 'http';
import {Server as HttpsServer} from 'https';
import http from 'http';

import compression from 'compression';
import connect from 'connect';
Expand All @@ -17,9 +16,9 @@ import securityHeadersMiddleware from './securityHeadersMiddleware';
import statusPageMiddleware from './statusPageMiddleware';
import systraceProfileMiddleware from './systraceProfileMiddleware';

import debuggerProxyServer from './websocket/debuggerProxyServer';
import eventsSocketServer from './websocket/eventsSocketServer';
import messageSocketServer from './websocket/messageSocketServer';
import createDebuggerProxyEndpoint from './websocket/createDebuggerProxyEndpoint';
import createMessageSocketEndpoint from './websocket/createMessageSocketEndpoint';
import createEventsSocketEndpoint from './websocket/createEventsSocketEndpoint';

export {devToolsMiddleware};
export {indexPageMiddleware};
Expand All @@ -30,19 +29,20 @@ export {securityHeadersMiddleware};
export {statusPageMiddleware};
export {systraceProfileMiddleware};

export {debuggerProxyServer};
export {eventsSocketServer};
export {messageSocketServer};

type MiddlewareOptions = {
host?: string;
watchFolders: ReadonlyArray<string>;
port: number;
};

export function createDevServerMiddleware(options: MiddlewareOptions) {
let isDebuggerConnected = () => false;
let broadcast = (_event: any) => {};
const debuggerProxyEndpoint = createDebuggerProxyEndpoint();
const isDebuggerConnected = debuggerProxyEndpoint.isDebuggerConnected;

const messageSocketEndpoint = createMessageSocketEndpoint();
const broadcast = messageSocketEndpoint.broadcast;

const eventsSocketEndpoint = createEventsSocketEndpoint(broadcast);

const middleware = connect()
.use(securityHeadersMiddleware)
Expand All @@ -52,7 +52,7 @@ export function createDevServerMiddleware(options: MiddlewareOptions) {
.use('/debugger-ui', debuggerUIMiddleware())
.use(
'/launch-js-devtools',
devToolsMiddleware(options, () => isDebuggerConnected()),
devToolsMiddleware(options, isDebuggerConnected),
)
.use('/open-stack-frame', openStackFrameInEditorMiddleware(options))
.use('/open-url', openURLMiddleware)
Expand All @@ -71,28 +71,14 @@ export function createDevServerMiddleware(options: MiddlewareOptions) {
});

return {
attachToServer(server: HttpServer | HttpsServer) {
const debuggerProxy = debuggerProxyServer.attachToServer(
server,
'/debugger-proxy',
);
const messageSocket = messageSocketServer.attachToServer(
server,
'/message',
);
broadcast = messageSocket.broadcast;
isDebuggerConnected = debuggerProxy.isDebuggerConnected;
const eventsSocket = eventsSocketServer.attachToServer(
server,
'/events',
messageSocket,
);
return {
debuggerProxy,
eventsSocket,
messageSocket,
};
websocketEndpoints: {
'/debugger-proxy': debuggerProxyEndpoint.server,
'/message': messageSocketEndpoint.server,
'/events': eventsSocketEndpoint.server,
},
debuggerProxyEndpoint,
messageSocketEndpoint,
eventsSocketEndpoint,
middleware,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@

import ws from 'ws';
import {logger} from '@react-native-community/cli-tools';
import {Server as HttpServer} from 'http';
import {Server as HttpsServer} from 'https';

type Server = HttpServer | HttpsServer;
function attachToServer(server: Server, path: string) {
export default function createDebuggerProxyEndpoint(): {
server: ws.Server;
isDebuggerConnected: () => boolean;
} {
const WebSocketServer = ws.Server;
const wss = new WebSocketServer({
server,
path,
noServer: true,
});

let debuggerSocket: ws | null;
Expand Down Expand Up @@ -48,37 +47,33 @@ function attachToServer(server: Server, path: string) {
send(debuggerSocket, JSON.stringify({method: '$disconnected'}));
};

wss.on('connection', (connection: ws) => {
// @ts-ignore current definition of ws does not have upgradeReq type
const {url} = connection.upgradeReq;
wss.on('connection', (socket, request) => {
const {url} = request;

if (url.indexOf('role=debugger') > -1) {
if (url && url.indexOf('role=debugger') > -1) {
if (debuggerSocket) {
connection.close(1011, 'Another debugger is already connected');
socket.close(1011, 'Another debugger is already connected');
return;
}
debuggerSocket = connection;
debuggerSocket = socket;
if (debuggerSocket) {
debuggerSocket.onerror = debuggerSocketCloseHandler;
debuggerSocket.onclose = debuggerSocketCloseHandler;
debuggerSocket.onmessage = ({data}) => send(clientSocket, data);
}
} else if (url.indexOf('role=client') > -1) {
} else if (url && url.indexOf('role=client') > -1) {
if (clientSocket) {
// @ts-ignore not nullable with current type definition of ws
clientSocket.onerror = null;
// @ts-ignore not nullable with current type definition of ws
clientSocket.onclose = null;
// @ts-ignore not nullable with current type definition of ws
clientSocket.onmessage = null;
clientSocket.onerror = () => {};
clientSocket.onclose = () => {};
clientSocket.onmessage = () => {};
clientSocket.close(1011, 'Another client connected');
}
clientSocket = connection;
clientSocket = socket;
clientSocket.onerror = clientSocketCloseHandler;
clientSocket.onclose = clientSocketCloseHandler;
clientSocket.onmessage = ({data}) => send(debuggerSocket, data);
} else {
connection.close(1011, 'Missing role param');
socket.close(1011, 'Missing role param');
}
});

Expand All @@ -89,7 +84,3 @@ function attachToServer(server: Server, path: string) {
},
};
}

export default {
attachToServer,
};
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import {Server as WebSocketServer} from 'ws';
import {logger} from '@react-native-community/cli-tools';
import prettyFormat from 'pretty-format';
import {Server as HttpServer} from 'http';
import {Server as HttpsServer} from 'https';
import messageSocketModule from './messageSocketServer';

/**
* The eventsSocket websocket listens at the 'events/` for websocket
Expand All @@ -21,8 +18,6 @@ import messageSocketModule from './messageSocketServer';
* Two useful commands are 'reload' and 'devmenu'.
*/

type Server = HttpServer | HttpsServer;

type Command = {
version: number;
type: 'command';
Expand Down Expand Up @@ -105,23 +100,18 @@ function serializeMessage(message: any) {
}
}

type MessageSocket = ReturnType<typeof messageSocketModule.attachToServer>;

/**
* Starts the eventsSocket at the given path
*
* @param server
* @param path typically: 'events/'
* @param messageSocket: webSocket to which all connected RN apps are listening
*/
function attachToServer(
server: Server,
path: string,
messageSocket: MessageSocket,
) {
export default function createEventsSocketEndpoint(
broadcast: (method: string, params?: Record<string, any>) => void,
): {
server: WebSocketServer;
reportEvent: (event: any) => void;
} {
const wss = new WebSocketServer({
server: server,
path: path,
noServer: true,
verifyClient({origin}: {origin: string}) {
// This exposes the full JS logs and enables issuing commands like reload
// so let's make sure only locally running stuff can connect to it
Expand Down Expand Up @@ -186,7 +176,7 @@ function attachToServer(
* messageSocket.broadcast (not to be confused with our own broadcast above)
* forwards a command to all connected React Native applications.
*/
messageSocket.broadcast(message.command, message.params);
broadcast(message.command, message.params);
} catch (e) {
logger.error('Failed to forward message to clients: ', e);
}
Expand All @@ -197,12 +187,9 @@ function attachToServer(
});

return {
server: wss,
reportEvent: (event: any) => {
broadCastEvent(event);
},
};
}

export default {
attachToServer,
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
import url from 'url';
import {Server as WebSocketServer} from 'ws';
import {logger} from '@react-native-community/cli-tools';
import {Server as HttpServer} from 'http';
import {Server as HttpsServer} from 'https';

const PROTOCOL_VERSION = 2;

Expand Down Expand Up @@ -70,11 +68,12 @@ function isResponse(message: Message) {
);
}

type Server = HttpServer | HttpsServer;
function attachToServer(server: Server, path: string) {
export default function createMessageSocketEndpoint(): {
server: WebSocketServer;
broadcast: (method: string, params?: Record<string, any>) => void;
} {
const wss = new WebSocketServer({
server,
path,
noServer: true,
});
const clients = new Map();
let nextClientId = 0;
Expand Down Expand Up @@ -211,8 +210,7 @@ function attachToServer(server: Server, path: string) {

clients.set(clientId, clientWs);
const onCloseHandler = () => {
// @ts-ignore
clientWs.onmessage = null;
clientWs.onmessage = () => {};
clients.delete(clientId);
};
clientWs.onclose = onCloseHandler;
Expand Down Expand Up @@ -245,10 +243,9 @@ function attachToServer(server: Server, path: string) {
});

return {
server: wss,
broadcast: (method: string, params?: Record<string, any>) => {
handleSendBroadcast(null, {method, params});
},
};
}

export default {attachToServer, parseMessage};
2 changes: 1 addition & 1 deletion packages/debugger-ui/src/ui/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const Page = (window.Page = {
const statusNode = document.getElementById('status');
switch (status.type) {
case 'connected':
statusNode.innerHTML = 'Debugger session #' + status.id + ' active.';
statusNode.innerHTML = 'Debugger session active.';
break;
case 'error':
statusNode.innerHTML =
Expand Down
Loading

0 comments on commit e4b7805

Please sign in to comment.