1*fd055557SKudo Chienimport assert from 'assert'; 2*fd055557SKudo Chienimport { EventEmitter } from 'events'; 3*fd055557SKudo Chienimport WebSocket from 'ws'; 4*fd055557SKudo Chien 5*fd055557SKudo Chienlet serverInstance: WebSocket.WebSocketServer | null = null; 6*fd055557SKudo Chien 7*fd055557SKudo Chienconst eventEmitter = new EventEmitter(); 8*fd055557SKudo Chien 9*fd055557SKudo Chien/** 10*fd055557SKudo Chien * Private command to support DevTools frontend reload. 11*fd055557SKudo Chien * 12*fd055557SKudo Chien * The react-devtools maintains state between frontend(webpage) and backend(app). 13*fd055557SKudo Chien * If we reload the frontend without reloading the app, the react-devtools will stuck on incorrect state. 14*fd055557SKudo Chien * We introduce this special reload command. 15*fd055557SKudo Chien * As long as the frontend reload, we will close app's WebSocket connection and tell app to reconnect again. 16*fd055557SKudo Chien */ 17*fd055557SKudo Chienconst RELOAD_COMMAND = 'Expo::RELOAD'; 18*fd055557SKudo Chien 19*fd055557SKudo Chien/** 20*fd055557SKudo Chien * Start the react-devtools WebSocket proxy server 21*fd055557SKudo Chien */ 22*fd055557SKudo Chienexport async function startReactDevToolsProxyAsync(options?: { port: number }) { 23*fd055557SKudo Chien if (serverInstance != null) { 24*fd055557SKudo Chien return; 25*fd055557SKudo Chien } 26*fd055557SKudo Chien 27*fd055557SKudo Chien serverInstance = new WebSocket.WebSocketServer({ port: options?.port ?? 8097 }); 28*fd055557SKudo Chien 29*fd055557SKudo Chien serverInstance.on('connection', function connection(ws) { 30*fd055557SKudo Chien ws.on('message', function message(rawData, isBinary) { 31*fd055557SKudo Chien assert(!isBinary); 32*fd055557SKudo Chien const data = rawData.toString(); 33*fd055557SKudo Chien 34*fd055557SKudo Chien if (data === RELOAD_COMMAND) { 35*fd055557SKudo Chien closeAllOtherClients(ws); 36*fd055557SKudo Chien eventEmitter.emit(RELOAD_COMMAND); 37*fd055557SKudo Chien return; 38*fd055557SKudo Chien } 39*fd055557SKudo Chien 40*fd055557SKudo Chien serverInstance?.clients.forEach(function each(client) { 41*fd055557SKudo Chien if (client !== ws && client.readyState === WebSocket.OPEN) { 42*fd055557SKudo Chien client.send(data, { binary: isBinary }); 43*fd055557SKudo Chien } 44*fd055557SKudo Chien }); 45*fd055557SKudo Chien }); 46*fd055557SKudo Chien }); 47*fd055557SKudo Chien 48*fd055557SKudo Chien serverInstance.on('close', function () { 49*fd055557SKudo Chien serverInstance = null; 50*fd055557SKudo Chien }); 51*fd055557SKudo Chien} 52*fd055557SKudo Chien 53*fd055557SKudo Chien/** 54*fd055557SKudo Chien * Close the WebSocket server 55*fd055557SKudo Chien */ 56*fd055557SKudo Chienexport function closeReactDevToolsProxy() { 57*fd055557SKudo Chien serverInstance?.close(); 58*fd055557SKudo Chien serverInstance = null; 59*fd055557SKudo Chien} 60*fd055557SKudo Chien 61*fd055557SKudo Chien/** 62*fd055557SKudo Chien * add event listener from react-devtools frontend reload 63*fd055557SKudo Chien */ 64*fd055557SKudo Chienexport function addReactDevToolsReloadListener(listener: (...args: any[]) => void) { 65*fd055557SKudo Chien eventEmitter.addListener(RELOAD_COMMAND, listener); 66*fd055557SKudo Chien} 67*fd055557SKudo Chien 68*fd055557SKudo Chien/** 69*fd055557SKudo Chien * Close all other WebSocket clients other than the current `self` client 70*fd055557SKudo Chien */ 71*fd055557SKudo Chienfunction closeAllOtherClients(self: WebSocket.WebSocket) { 72*fd055557SKudo Chien serverInstance?.clients.forEach(function each(client) { 73*fd055557SKudo Chien if (client !== self && client.readyState === WebSocket.OPEN) { 74*fd055557SKudo Chien client.close(); 75*fd055557SKudo Chien } 76*fd055557SKudo Chien }); 77*fd055557SKudo Chien} 78