UNPKG

wise-eyes-core

Version:

Web server to monitor the status of owlcms

256 lines (255 loc) 10.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const json_1 = require("klona/json"); const cors_1 = __importDefault(require("cors")); const deep_equal_1 = __importDefault(require("deep-equal")); const express_1 = __importDefault(require("express")); const express_ws_1 = __importDefault(require("express-ws")); const platform_1 = __importDefault(require("./platform")); const ws_1 = __importDefault(require("ws")); function createApp({ debug = false, } = {}) { const app = (0, express_1.default)(); const appWs = (0, express_ws_1.default)(app); const socketMaps = { liftingOrder: new Map(), status: new Map(), }; function broadcastLiftingOrder(platformName, liftingOrder) { socketMaps.liftingOrder.get(platformName)?.forEach((client) => { if (client.readyState !== ws_1.default.OPEN) { return; } client.send(JSON.stringify(liftingOrder)); }); } function broadcastStatus(state) { socketMaps.status.get(state.name)?.forEach((client) => { if (client.readyState !== ws_1.default.OPEN) { return; } client.send(JSON.stringify(state)); }); } function withPlatformForClient(request, callback) { const platform = platform_1.default.getPlatform(request.params.platform, { noPersist: true, }); callback(platform); } function withPlatformForServer(platformName, callback) { const platform = platform_1.default.getPlatform(platformName); const prevState = (0, json_1.klona)(platform.getState()); const prevLiftingOrder = (0, json_1.klona)(platform.getLiftingOrder() .map((athlete) => athlete.getState())); callback(platform); const currentState = platform.getState(); if (!(0, deep_equal_1.default)(prevState, currentState)) { broadcastStatus(currentState); } const currentLiftingOrder = platform.getLiftingOrder() .map((athlete) => athlete.getState()); if (!(0, deep_equal_1.default)(prevLiftingOrder, currentLiftingOrder)) { broadcastLiftingOrder(currentState.name, currentLiftingOrder); } } app.use((0, cors_1.default)()); app.use(express_1.default.urlencoded({ extended: true, })); app.post('/decision', (request, response) => { response.end(); if (debug) { console.log('/decision'); console.log(request.body); } const { d1, d2, d3, decisionEventType: eventType, down, fop, fopState, juryDecision, juryReversal, mode, recordKind, waitForAnnouncer, } = request.body; withPlatformForServer(fop, (platform) => { let handled = true; switch (eventType) { case 'DOWN_SIGNAL': platform.setDownSignal(down === 'true'); break; case 'FULL_DECISION': platform.setDecisions({ centerReferee: !d2 ? null : d2 === 'true' ? 'good' : 'bad', leftReferee: !d1 ? null : d1 === 'true' ? 'good' : 'bad', rightReferee: !d3 ? null : d3 === 'true' ? 'good' : 'bad', }); break; case 'JURY_DECISION': if (waitForAnnouncer === 'true') { return; } platform.setJuryDecision({ decision: juryDecision === 'GOOD_LIFT' ? 'good' : 'bad', reversal: juryReversal === 'true', }); break; case 'RESET': platform.resetDecisions(); break; case 'START_DELIBERATION': // Do nothing. This will be handled in `/update` handled = false; break; default: handled = false; if (debug) { console.log(`!! UNHANDLED DECISION EVENT decisionEventType=${eventType}`); } } if (handled) { platform.setFopState(fopState); platform.setMode(mode); platform.setRecordKind(recordKind); } }); }); app.post('/timer', (request, response) => { response.end(); if (debug) { console.log('/timer'); console.log(request.body); } const { athleteMillisRemaining, athleteTimerEventType, breakTimerEventType, breakMillisRemaining, breakType, ceremonyType, fopName, fopState, indefiniteBreak, mode, } = request.body; withPlatformForServer(fopName, (platform) => { platform.setBreakType(breakType || null); platform.setCeremonyType(ceremonyType || null); platform.setFopState(fopState); platform.setMode(mode); if (breakTimerEventType) { // When a break ends, we will not receive `indefiniteBreak`, but we // can consider the end of a break to always considered indefinite since // we're waiting for the next part of the competition to start with no // running clock. const isIndefinite = !indefiniteBreak || indefiniteBreak === 'true'; platform.getBreakClock().update({ isStopped: breakTimerEventType !== 'BreakStarted', milliseconds: isIndefinite ? Number.POSITIVE_INFINITY : breakMillisRemaining, }); } if (athleteTimerEventType) { platform.getAthleteClock().update({ isStopped: athleteTimerEventType !== 'StartTime', milliseconds: athleteMillisRemaining, }); } }); }); app.post('/update', (request, response) => { response.end(); if (debug) { console.log('/update'); (({ /* eslint-disable @typescript-eslint/no-unused-vars */ groupAthletes, leaders, liftingOrderAthletes, translationMap, /* eslint-enable @typescript-eslint/no-unused-vars */ ...params }) => { console.log(Object.fromEntries(Object.entries(params) .sort((a, b) => a[0].localeCompare(b[0])))); })(request.body); } const { breakType, ceremonyType, fop, fopState, groupDescription, groupInfo, groupName, leaders, liftingOrderAthletes, liftType, liftTypeKey, mode, recordKind, records, startNumber, } = request.body; withPlatformForServer(fop, (platform) => { platform.setBreakType(breakType || null); platform.setCeremonyType(ceremonyType || null); platform.setFopState(fopState); platform.setLeaders(leaders ? JSON.parse(leaders) : []); platform.setLiftType({ key: liftTypeKey, name: liftType, }); platform.setMode(mode); platform.setRecordKind(recordKind); platform.setRecords(records ? JSON.parse(records) : null); platform.setSession({ description: groupDescription, info: groupInfo, name: groupName, }); // TODO: determine when this is undefined platform.updateAthletes(JSON.parse(liftingOrderAthletes)); platform.setCurrentAthlete(parseInt(startNumber)); }); }); app.get('/', (_request, response) => { response.json({ platforms: platform_1.default.getPlatforms(), }); }); app.get('/platform/:platform/athlete-clock', (request, response) => { withPlatformForClient(request, (platform) => { response.json([platform.getAthleteClock().getState()]); }); }); app.get('/platform/:platform/break-clock', (request, response) => { withPlatformForClient(request, (platform) => { response.json([platform.getBreakClock().getState()]); }); }); app.get('/platform/:platform/current-athlete', (request, response) => { withPlatformForClient(request, (platform) => { response.json([{ athlete: platform.getCurrentAthlete()?.getState(), clock: platform.getAthleteClock()?.getState(), }]); }); }); app.get('/platform/:platform/lifting-order', (request, response) => { withPlatformForClient(request, (platform) => { response.json(platform.getLiftingOrder() .map((athlete) => athlete.getState())); }); }); app.get('/platform/:platform/status', (request, response) => { withPlatformForClient(request, (platform) => { response.json([platform.getState()]); }); }); appWs.app.ws('/ws/platform/:platform/lifting-order', (client, request) => { const platformName = request.params.platform; let clients = socketMaps.liftingOrder.get(platformName); if (!clients) { clients = new Set(); socketMaps.liftingOrder.set(platformName, clients); } clients.add(client); withPlatformForClient(request, (platform) => { client.send(JSON.stringify(platform.getLiftingOrder() .map((athlete) => athlete.getState()))); }); }); appWs.app.ws('/ws/platform/:platform/status', (client, request) => { const platformName = request.params.platform; let clients = socketMaps.status.get(platformName); if (!clients) { clients = new Set(); socketMaps.status.set(platformName, clients); } clients.add(client); withPlatformForClient(request, (platform) => { client.send(JSON.stringify(platform.getState())); }); }); return app; } exports.default = createApp;