wise-eyes-core
Version:
Web server to monitor the status of owlcms
256 lines (255 loc) • 10.7 kB
JavaScript
;
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;