import { WebSocketServer, WebSocket } from "ws"; const APP_DOWN_ERROR_MESSAGE = "App is currently down"; export const APP_BACK_ALIVE_SIGNAL = "app-back-alive"; const sleep = (time: number) => new Promise((resolve) => { setTimeout(resolve, time); }); async function get_status( app_port: number ): Promise<{ started_at: number; status: string }> { const r = await fetch(`http://127.0.0.1:${app_port}/status.json`); return (await r.json()) as { started_at: number; status: string }; } async function wait_for_run_id_to_change(app_port: number) { let first_timestamp: number; try { const { started_at } = await get_status(app_port); first_timestamp = started_at; } catch (_e) { await wait_for_app_to_be_stable(app_port); return; } if (!first_timestamp) { throw new Error(APP_DOWN_ERROR_MESSAGE); } while (true) { // eslint-disable-next-line no-await-in-loop const { started_at } = await get_status(app_port).catch(() => ({ started_at: first_timestamp, })); if (started_at !== first_timestamp) { return; } // eslint-disable-next-line no-await-in-loop await sleep(100); } } async function wait_for_app_to_be_stable(app_port: number, n = 3) { // eslint-disable-next-line no-console console.log("Waiting for app to be stable...."); let counter = 0; while (true) { // eslint-disable-next-line no-await-in-loop const { status } = await get_status(app_port).catch(() => ({ status: "down", })); if (status == "running") { // eslint-disable-next-line no-console console.log(counter); counter++; } else { counter = 0; } if (counter == n) { return; } // eslint-disable-next-line no-await-in-loop await sleep(100); } } let restart_promise: Promise | null = null; async function wait_for_app_restart(app_port: number) { if (restart_promise) { return restart_promise; } try { restart_promise = wait_for_run_id_to_change(app_port); await restart_promise; } catch (e) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (e.message !== APP_DOWN_ERROR_MESSAGE) { throw e; } } restart_promise = null; await wait_for_app_to_be_stable(app_port); } export function make_notifier(port: number, app_port: number) { const server = new WebSocketServer({ port, }); let sockets: WebSocket[] = []; server.on("connection", function (socket) { sockets.push(socket); // When a socket closes, or disconnects, remove it from the array. socket.on("close", function () { sockets = sockets.filter((s) => s !== socket); }); }); console.info( "🔔 Build notifier listening on websocket at port " + port.toString() ); return function notify(message: string) { sockets.forEach((s) => s.send(message)); void wait_for_app_restart(app_port).then(() => { sockets.forEach((s) => s.send(APP_BACK_ALIVE_SIGNAL)); }); }; }