2021-06-27 01:41:52 +03:00
|
|
|
|
2022-07-15 21:43:29 +03:00
|
|
|
import { RTMTWS } from "./rtmt/rtmt_websocket";
|
|
|
|
|
import { GameManager } from "./game/GameManager";
|
|
|
|
|
import { GameStoreImpl } from "./game/gamestore/GameStoreImpl";
|
|
|
|
|
import { MatchMakerImpl } from "./matchmaker/MatchMakerImpl";
|
|
|
|
|
import { ExpressApp } from "./server/ExpressApp";
|
|
|
|
|
import { WebServer } from "./server/WebServer";
|
2026-10-14 01:52:07 +03:00
|
|
|
import { KeycloakTokenVerifier, readAuthConfig } from "./auth";
|
2026-10-16 00:18:33 +03:00
|
|
|
import { createPool, runMigrations } from "./db/postgres";
|
|
|
|
|
import { PostgresGameHistoryRepository } from "./history/GameHistoryRepository";
|
|
|
|
|
import { createValkeyClient } from "./live/valkey";
|
|
|
|
|
import { ValkeyLiveGameStore } from "./live/LiveGameStore";
|
|
|
|
|
import { GameRecorder } from "./game/GameRecorder";
|
2021-06-27 19:28:09 +03:00
|
|
|
|
2026-10-16 00:18:33 +03:00
|
|
|
const DATABASE_URL = process.env.DATABASE_URL || "postgres://mancala:mancala@localhost:5433/mancala";
|
|
|
|
|
const VALKEY_URL = process.env.VALKEY_URL || "redis://localhost:6379";
|
|
|
|
|
const MIGRATION_RETRY_MS = 30_000;
|
|
|
|
|
const VALKEY_STARTUP_WAIT_MS = 5_000;
|
|
|
|
|
|
|
|
|
|
async function main() {
|
|
|
|
|
const authConfig = readAuthConfig();
|
|
|
|
|
const tokenVerifier = new KeycloakTokenVerifier({
|
|
|
|
|
issuer: authConfig.keycloakIssuer,
|
|
|
|
|
jwksUrl: authConfig.keycloakJwksUrl,
|
|
|
|
|
allowedClientIds: authConfig.keycloakClientIds,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const pool = createPool(DATABASE_URL);
|
|
|
|
|
migrateWhenReachable(pool);
|
|
|
|
|
const valkey = createValkeyClient(VALKEY_URL);
|
|
|
|
|
await waitForReady(valkey, VALKEY_STARTUP_WAIT_MS);
|
|
|
|
|
|
|
|
|
|
const liveGameStore = new ValkeyLiveGameStore(valkey);
|
|
|
|
|
const recorder = new GameRecorder(new PostgresGameHistoryRepository(pool), liveGameStore);
|
2026-10-14 01:52:07 +03:00
|
|
|
|
2026-10-16 00:18:33 +03:00
|
|
|
const rtmt = new RTMTWS({ tokenVerifier, legacyUserKeyAuth: authConfig.legacyUserKeyAuth });
|
|
|
|
|
const gameStore = new GameStoreImpl();
|
|
|
|
|
const matchMaker = new MatchMakerImpl({ rtmt });
|
|
|
|
|
const gameManager = new GameManager({ rtmt, gameStore, matchMaker, liveGameStore, recorder });
|
2021-06-27 19:28:09 +03:00
|
|
|
|
2026-10-16 00:18:33 +03:00
|
|
|
// Before accepting connections, so reconnecting players find their games.
|
|
|
|
|
await gameManager.restoreLiveGames();
|
|
|
|
|
recorder.start();
|
|
|
|
|
gameManager.startTimers();
|
2022-05-06 00:07:41 +03:00
|
|
|
|
2026-10-16 00:18:33 +03:00
|
|
|
const expressApp = new ExpressApp({ gameStore, legacyUserKeyAuth: authConfig.legacyUserKeyAuth });
|
|
|
|
|
const server = new WebServer({ expressApp: expressApp.app });
|
|
|
|
|
rtmt.initWebSocket(server.server);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The game runs without the database; saved games wait in the recorder until migrations have run.
|
|
|
|
|
function migrateWhenReachable(pool: ReturnType<typeof createPool>) {
|
|
|
|
|
runMigrations(pool).catch((err) => {
|
|
|
|
|
console.error(`(DB) Migrations failed, retrying in ${MIGRATION_RETRY_MS / 1000}s:`, err.message);
|
|
|
|
|
setTimeout(() => migrateWhenReachable(pool), MIGRATION_RETRY_MS);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function waitForReady(client: ReturnType<typeof createValkeyClient>, timeoutMs: number): Promise<void> {
|
|
|
|
|
if (client.status === "ready") return Promise.resolve();
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
const timer = setTimeout(() => {
|
|
|
|
|
console.error("(Valkey) Not reachable yet; starting without restoring live games");
|
|
|
|
|
resolve();
|
|
|
|
|
}, timeoutMs);
|
|
|
|
|
client.once("ready", () => {
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
resolve();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main().catch((err) => {
|
|
|
|
|
console.error(err);
|
|
|
|
|
process.exit(1);
|
|
|
|
|
});
|