Files
Announcements/server/src/runtime.js
T

133 lines
3.1 KiB
JavaScript

const http = require("node:http");
const { Server: SocketServer } = require("socket.io");
const { createApp } = require("./app");
const { openDatabase } = require("./database");
const { runMigrations } = require("./database/migrate");
function listen(httpServer, port, host) {
return new Promise((resolve, reject) => {
function onError(error) {
httpServer.off("listening", onListening);
reject(error);
}
function onListening() {
httpServer.off("error", onError);
resolve(httpServer.address());
}
httpServer.once("error", onError);
httpServer.once("listening", onListening);
httpServer.listen(port, host);
});
}
function createRuntime({ config, logger = console }) {
const database = openDatabase(config.database);
let migrations;
try {
migrations = runMigrations(database);
} catch (error) {
database.close();
throw error;
}
const lifecycle = {
ready: false,
stopping: false,
};
const app = createApp({ config, database, lifecycle, migrations });
const httpServer = http.createServer(app);
const io = new SocketServer(httpServer, {
serveClient: false,
});
io.on("connection", (socket) => {
logger.info?.(`Socket.IO client connected: ${socket.id}`);
socket.on("disconnect", () => {
logger.info?.(`Socket.IO client disconnected: ${socket.id}`);
});
});
let address = null;
let startPromise = null;
let stopPromise = null;
async function start() {
if (stopPromise) {
throw new Error("Cannot start a runtime after shutdown has begun");
}
if (startPromise) {
return startPromise;
}
startPromise = listen(httpServer, config.port, config.host)
.then((listeningAddress) => {
address = listeningAddress;
lifecycle.ready = true;
return listeningAddress;
})
.catch((error) => {
database.close();
throw error;
});
return startPromise;
}
async function stop(reason = "shutdown") {
if (stopPromise) {
return stopPromise;
}
lifecycle.ready = false;
lifecycle.stopping = true;
stopPromise = new Promise((resolve) => {
let settled = false;
const finish = () => {
if (settled) return;
settled = true;
clearTimeout(timeout);
if (database.open) {
database.close();
}
resolve();
};
const timeout = setTimeout(() => {
logger.warn?.(
`Graceful shutdown timed out after ${config.shutdownTimeoutMs}ms`,
);
httpServer.closeAllConnections?.();
finish();
}, config.shutdownTimeoutMs);
timeout.unref?.();
if (!httpServer.listening) {
io.close();
finish();
return;
}
logger.info?.(`Stopping Announcements API (${reason})`);
io.close(finish);
});
return stopPromise;
}
return {
app,
config,
database,
httpServer,
io,
lifecycle,
migrations,
get address() {
return address;
},
start,
stop,
};
}
module.exports = {
createRuntime,
};