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