Refactor/socket controller (#212)

* refactor(socket): dictionary cleanup
* refactor(socket): detangle socket from EventTimer
* refactor(socket): poll object
* refactor(socket): wip, extract data responsibilities to provider
* fix: issue with onAir
* refactor: create data provider and validation utils
* refactor: remove deprecated endpoint
* refactor: detangle and validate ontime controller
* refactor: detangle and validate events controller
* refactor: validate routers
* refactor: handle post failure in modals
This commit is contained in:
Carlos Valente
2022-10-09 19:10:48 +02:00
committed by GitHub
parent cd0577bf99
commit c2ed9d7634
28 changed files with 1145 additions and 898 deletions
@@ -1,7 +1,168 @@
/**
* Class Event Provider adds functions specific for handling event data
* Class Event Provider is a mediator for handling the local db
* and adds logic specific to ontime data
*/
import { data, db } from '../../app.js';
export class DataProvider {
static getData() {
return data;
}
static async setEventData(newData) {
data.event = { ...data.event, ...newData };
await this.persist();
return data.event;
}
static getEventData() {
return data.event;
}
static async setEvents(newData) {
data.events = [...newData];
await this.persist();
}
static getEventById(eventId) {
return data.events.find((e) => e.id === eventId);
}
static async updateEventById(eventId, newData) {
const eventIndex = data.events.findIndex((e) => e.id === eventId);
const e = data.events[eventIndex];
data.events[eventIndex] = { ...e, ...newData };
data.events[eventIndex].revision++;
await this.persist();
return data.events[eventIndex];
}
static async deleteEvent(eventId) {
data.events = Array.from(data.events).filter((e) => e.id !== eventId);
await this.persist();
}
static getNumEvents() {
return data.events.length;
}
static async deleteAllEvents() {
data.events = [];
await db.write();
}
/**
* Insets an event after a given index
* @param entry
* @param index
* @return {Promise<void>}
* @private
*/
static async insertEventAt(entry, index) {
// get events
const events = DataProvider.getEvents();
const count = events.length;
const order = entry.order;
// Remove order field from object
delete entry.order;
// Insert at beginning
if (order === 0) {
events.unshift(entry);
}
// insert at end
else if (order >= count) {
events.push(entry);
}
// insert in the middle
else {
events.splice(index, 0, entry);
}
// save events
await DataProvider.setEvents(events);
}
/**
* @description Inserts an entry after an element with given ID
* @param entry
* @param id
* @return {Promise<void>}
* @private
*/
static async insertEventAfterId(entry, id) {
const index = [...data.events].findIndex((event) => event.id === id);
await DataProvider.insertEventAt(entry, index + 1);
}
static getSettings() {
return data.settings;
}
static async setSettings(newData) {
data.settings = { ...newData };
await this.persist();
}
static getOsc() {
return data.osc;
}
static getAliases() {
return data.aliases;
}
static async setAliases(newData) {
data.aliases = newData;
await this.persist();
}
static getUserFields() {
return { ...data.userFields };
}
static getViews() {
return { ...data.views };
}
static async setViews(newData) {
data.views = { ...newData };
await this.persist();
}
static async setUserFields(newData) {
data.userFields = { ...newData };
await this.persist();
}
static async setOsc(newData) {
data.osc = { ...newData };
await this.persist();
}
static getEvents() {
return [...data.events];
}
static async persist() {
await db.write();
}
static async mergeIntoData(newData) {
const mergedData = DataProvider.safeMerge(data, newData);
data.event = mergedData.event;
data.settings = mergedData.settings;
data.osc = mergedData.osc;
data.http = mergedData.http;
data.aliases = mergedData.aliases;
data.userFields = mergedData.userFields;
data.events = mergedData.events;
await this.persist();
}
/**
* Merges two data objects
* @param {object} existing
@@ -0,0 +1,400 @@
import { Server } from 'socket.io';
import getRandomName from '../../utils/getRandomName.js';
import { generateId } from '../../utils/generate_id.js';
import { stringFromMillis } from '../../utils/time.js';
import { Timer } from '../timer/Timer.js';
class SocketController {
constructor() {
this.numClients = 0;
this.messageStack = [];
this._MAX_MESSAGES = 100;
this._clientNames = {};
this.socket = null;
}
initServer(httpServer) {
this.socket = new Server(httpServer, {
cors: {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204,
},
});
}
startListener() {
this._socketMessageHandler();
}
shutdown() {
this.info('SERVER', 'Shutting down ontime');
if (this.socket) {
this.info('TX', '... Closing socket server');
this.socket.close();
}
}
/**
* Handle socket io connections
* @private
*/
_socketMessageHandler() {
this.socket.on('connection', (socket) => {
/*******************************/
/*** HANDLE NEW CONNECTION ***/
/*** --------------------- ***/
/*******************************/
// keep track of connections
this.numClients++;
this._clientNames[socket.id] = getRandomName();
const message = `${this.numClients} Clients with new connection: ${
this._clientNames[socket.id]
}`;
this.info('CLIENT', message);
// send state
socket.emit('timer', global.timer.getTimeObject());
socket.emit('playstate', global.timer.state);
socket.emit('selected-id', global.timer.selectedEventId);
socket.emit('next-id', global.timer.nextEventId);
socket.emit('publicselected-id', global.timer.selectedPublicEventId);
socket.emit('publicnext-id', global.timer.nextPublicEventId);
/**
* @description handle disconnecting a user
*/
socket.on('disconnect', () => {
this.numClients--;
const message = `${this.numClients} Clients with disconnection: ${
this._clientNames[socket.id]
}`;
delete this._clientNames[socket.id];
this.info('CLIENT', message);
});
/**
* @description utility for renaming a user
*/
socket.on('rename-client', (newName) => {
if (newName) {
const previousName = this._clientNames[socket.id];
this._clientNames[socket.id] = newName;
this.info('CLIENT', `Client ${previousName} renamed to ${newName}`);
}
});
/***************************************/
/*** TIMER STATE GETTERS / SETTERS ***/
/*** ------- WEBSOCKET API ------- ***/
/*** ----------------------------- ***/
/***************************************/
/*******************************************/
socket.on('ontime-test', () => {
socket.emit('hello', socket.id);
});
socket.on('set-start', () => {
global.timer.trigger('start');
socket.emit('playstate', global.timer.state);
});
socket.on('set-startid', (data) => {
global.timer.trigger('startById', data);
socket.emit('playstate', global.timer.state);
});
socket.on('set-startindex', (data) => {
const eventIndex = Number(data);
if (isNaN(eventIndex)) {
return;
}
global.timer.trigger('startByIndex', data);
socket.emit('playstate', global.timer.state);
});
socket.on('set-loadid', (data) => {
global.timer.trigger('loadById', data);
socket.emit('playstate', global.timer.state);
});
socket.on('set-loadindex', (data) => {
const eventIndex = Number(data);
if (isNaN(eventIndex)) {
return;
}
global.timer.trigger('loadByIndex', data);
socket.emit('playstate', global.timer.state);
});
socket.on('set-pause', () => {
global.timer.trigger('pause');
socket.emit('playstate', global.timer.state);
});
socket.on('set-stop', () => {
global.timer.trigger('stop');
socket.emit('playstate', global.timer.state);
});
socket.on('set-reload', () => {
global.timer.trigger('reload');
socket.emit('playstate', global.timer.state);
});
socket.on('set-previous', () => {
global.timer.trigger('previous');
socket.emit('playstate', global.timer.state);
});
socket.on('set-next', () => {
global.timer.trigger('next');
socket.emit('playstate', global.timer.state);
});
socket.on('set-roll', () => {
global.timer.trigger('roll');
socket.emit('playstate', global.timer.state);
});
socket.on('set-delay', (data) => {
const delayTime = Number(data);
if (isNaN(delayTime)) {
return;
}
global.timer.increment(delayTime * 1000 * 60);
});
socket.on('set-onAir', (data) => {
try {
const d = JSON.parse(data);
d ? global.timer.trigger('onAir') : global.timer.trigger('offAir');
} catch (error) {
this.error('RX', `Failed to parse message ${data}`);
}
this.send('onAir', global.timer.onAir);
global.timer._broadcastFeatureMessageControl();
});
/*******************************************/
// general playback state, useful for external sync
socket.on('ontime-poll', () => {
const timerPoll = global.timer.poll();
const isDelayed = false;
const colour = '';
socket.emit('ontime-poll', { isDelayed, colour, ...timerPoll });
});
/*******************************************/
// ** TO BE DEPRECATED ** //
socket.on('get-timer', () => {
socket.emit('timer', global.timer.getTimeObject());
});
// ** TO BE DEPRECATED IN FAVOR OF DELAY ** //
socket.on('increment-timer', (data) => {
if (isNaN(parseInt(data, 10))) return;
if (data < -5 || data > 5) return;
global.timer.increment(data * 1000 * 60);
});
/*******************************************/
// playstate
socket.on('set-playstate', (data) => {
global.timer.trigger(data);
global.timer._broadcastFeaturePlaybackControl();
global.timer._broadcastFeatureInfo();
});
socket.on('get-playstate', () => {
socket.emit('playstate', global.timer.state);
});
socket.on('get-onAir', () => {
socket.emit('onAir', global.timer.onAir);
});
/*******************************************/
// selection data
socket.on('get-selected', () => {
socket.emit('selected', {
id: global.timer.selectedEventId,
index: global.timer.selectedEventIndex,
total: global.timer._eventlist.length,
});
});
socket.on('get-selected-id', () => {
socket.emit('selected-id', global.timer.selectedEventId);
});
socket.on('get-next-id', () => {
socket.emit('next-id', global.timer.nextEventId);
});
// title data
socket.on('get-titles', () => {
socket.emit('titles', global.timer.titles);
});
socket.on('get-publictitles', () => {
socket.emit('publictitles', global.timer.titlesPublic);
});
/***********************************/
/*** MESSAGE GETTERS / SETTERS ***/
/*** ------------------------- ***/
/***********************************/
/*******************************************/
// Presenter message
socket.on('set-timer-message-text', (data) => {
global.timer._setTitles('set-timer-text', data);
global.timer._broadcastFeatureMessageControl();
});
socket.on('set-timer-message-visible', (data) => {
global.timer._setTitles('set-timer-visible', data);
global.timer._broadcastFeatureMessageControl();
});
/*******************************************/
// Public message
socket.on('set-public-message-text', (data) => {
global.timer._setTitles('set-public-text', data);
global.timer._broadcastFeatureMessageControl();
});
socket.on('set-public-message-visible', (data) => {
global.timer._setTitles('set-public-visible', data);
global.timer._broadcastFeatureMessageControl();
});
/*******************************************/
// Lower third message
socket.on('set-lower-message-text', (data) => {
global.timer._setTitles('set-lower-text', data);
global.timer._broadcastFeatureMessageControl();
});
socket.on('set-lower-message-visible', (data) => {
global.timer._setTitles('set-lower-visible', data);
global.timer._broadcastFeatureMessageControl();
});
/* MOLECULAR ENDPOINTS
* =====================
* 1. EVENT LIST
* 2. MESSAGE CONTROL
* 3. PLAYBACK CONTROL
* 4. INFO
* 5. CUE SHEET
* 6. TIMER OBJECT
* */
// 1. EVENT LIST
socket.on('get-ontime-feat-eventlist', () => {
global.timer._broadcastFeatureEventList();
});
// 2. MESSAGE CONTROL
socket.on('get-ontime-feat-messagecontrol', () => {
global.timer._broadcastFeatureMessageControl();
});
// 3. PLAYBACK CONTROL
socket.on('get-ontime-feat-playbackcontrol', () => {
global.timer._broadcastFeaturePlaybackControl();
});
// 4. INFO
socket.on('get-ontime-feat-info', () => {
global.timer._broadcastFeatureInfo();
});
// 5. CUE SHEET
socket.on('get-ontime-feat-cuesheet', () => {
global.timer._broadcastFeatureCuesheet();
});
// 6. TIMER
socket.on('get-ontime-timer', () => {
global.timer._broadcastFeatureTimer();
});
});
}
send(topic, payload) {
this.socket.emit(topic, payload);
}
/****************************************************************************/
/**
* Logger logic
* -------------
*
* This should be separate of event timer, left here for convenience
*
*/
/**
* Utility method, sends message and pushes into stack
* @param {string} level
* @param {string} origin
* @param {string} text
*/
_push(level, origin, text) {
const logMessage = {
id: generateId(),
level,
origin,
text,
time: stringFromMillis(Timer.getCurrentTime() || 0),
};
this.messageStack.unshift(logMessage);
this.socket.emit('logger', logMessage);
if (process.env.NODE_ENV !== 'production') {
console.log(`[${logMessage.level}] \t ${logMessage.origin} \t ${logMessage.text}`);
}
if (this.messageStack.length > this._MAX_MESSAGES) {
this.messageStack.pop();
}
}
/**
* Sends a message with level LOG
* @param {string} origin
* @param {string} text
*/
info(origin, text) {
this._push('INFO', origin, text);
}
/**
* Sends a message with level WARN
* @param {string} origin
* @param {string} text
*/
warning(origin, text) {
this._push('WARN', origin, text);
}
/**
* Sends a message with level ERROR
* @param {string} origin
* @param {string} text
*/
error(origin, text) {
this._push('ERROR', origin, text);
}
}
export const socketProvider = new SocketController();
+66 -443
View File
@@ -1,12 +1,8 @@
import { Timer } from './Timer.js';
import { Server } from 'socket.io';
import { DAY_TO_MS, getSelectionByRoll, replacePlaceholder, updateRoll } from './classUtils.js';
import { OSCIntegration } from './integrations/Osc.js';
import { HTTPIntegration } from './integrations/Http.js';
import { cleanURL } from '../../utils/url.js';
import getRandomName from '../../utils/getRandomName.js';
import { generateId } from '../../utils/generate_id.js';
import { stringFromMillis } from '../../utils/time.js';
/*
* Class EventTimer adds functions specific to APP
@@ -16,12 +12,12 @@ import { stringFromMillis } from '../../utils/time.js';
export class EventTimer extends Timer {
/**
* Instantiates an event timer object
* @param {object} httpServer
* @param {object} socket
* @param {object} timerConfig
* @param {object} [oscConfig]
* @param {object} [httpConfig]
*/
constructor(httpServer, timerConfig, oscConfig, httpConfig) {
constructor(socket, timerConfig, oscConfig, httpConfig) {
// call super constructor
super();
@@ -42,13 +38,15 @@ export class EventTimer extends Timer {
this.ontimeCycle = 'idle';
this.prevCycle = null;
// Socket Object
this.socket = socket;
// OSC Object
this.osc = null;
// HTTP Client Object
this.http = null;
this._numClients = 0;
this._interval = null;
this.presenter = {
@@ -69,26 +67,9 @@ export class EventTimer extends Timer {
this._eventlist = [];
this.onAir = false;
// initialise socketIO server
this.messageStack = [];
this.MAX_MESSAGES = 100;
this._clientNames = {};
this.io = new Server(httpServer, {
cors: {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204,
},
});
// set recurrent emits
this._interval = setInterval(() => this.runCycle(), timerConfig?.refresh || 1000);
// listen to new connections
this._listenToConnections();
if (oscConfig != null) {
this._initOscClient(oscConfig);
}
@@ -103,17 +84,12 @@ export class EventTimer extends Timer {
*/
shutdown() {
clearInterval(this._interval);
this.info('SERVER', 'Shutting down ontime');
if (this.io != null) {
this.info('TX', '... Closing socket server');
this.io.close();
}
if (this.osc != null) {
this.info('TX', '... Closing OSC Client');
this.socket.info('TX', '... Closing OSC Client');
this.osc.shutdown();
}
if (this.http != null) {
this.info('TX', '... Closing HTTP Client');
this.socket.info('TX', '... Closing HTTP Client');
this.http.shutdown();
}
}
@@ -126,7 +102,7 @@ export class EventTimer extends Timer {
_initOscClient(oscConfig) {
this.osc = new OSCIntegration();
const r = this.osc.init(oscConfig);
r.success ? this.info('TX', r.message) : this.error('TX', r.message);
r.success ? this.socket.info('TX', r.message) : this.socket.error('TX', r.message);
}
/**
@@ -135,7 +111,7 @@ export class EventTimer extends Timer {
* @private
*/
_initHTTPClient(httpConfig) {
this.info('TX', `Initialise HTTP Client on port`);
this.socket.info('TX', `Initialise HTTP Client on port`);
this.http = new HTTPIntegration();
this.http.init(httpConfig);
this.httpMessages = httpConfig.messages;
@@ -146,7 +122,7 @@ export class EventTimer extends Timer {
*/
broadcastTimer() {
// through websockets
this.io.emit('timer', this.getTimeObject());
this.socket.send('timer', this.getTimeObject());
}
/**
@@ -162,7 +138,7 @@ export class EventTimer extends Timer {
expectedFinish: this._getExpectedFinish(),
startedAt: this._startedAt,
};
this.io.emit('ontime-timer', featureData);
this.socket.send('ontime-timer', featureData);
}
/**
@@ -174,7 +150,7 @@ export class EventTimer extends Timer {
selectedEventId: this.selectedEventId,
nextEventId: this.nextEventId,
};
this.io.emit('ontime-feat-eventlist', featureData);
this.socket.send('ontime-feat-eventlist', featureData);
}
/**
@@ -188,7 +164,7 @@ export class EventTimer extends Timer {
lower: this.lower,
onAir: this.onAir,
};
this.io.emit('ontime-feat-messagecontrol', featureData);
this.socket.send('ontime-feat-messagecontrol', featureData);
}
/**
@@ -201,7 +177,7 @@ export class EventTimer extends Timer {
selectedEventId: this.selectedEventId,
numEvents: this._eventlist.length,
};
this.io.emit('ontime-feat-playbackcontrol', featureData);
this.socket.send('ontime-feat-playbackcontrol', featureData);
}
/**
@@ -216,7 +192,7 @@ export class EventTimer extends Timer {
selectedEventIndex: this.selectedEventIndex,
numEvents: this._eventlist.length,
};
this.io.emit('ontime-feat-info', featureData);
this.socket.send('ontime-feat-info', featureData);
}
_broadcastFeatureCuesheet() {
@@ -227,7 +203,7 @@ export class EventTimer extends Timer {
numEvents: this._eventlist.length,
titleNow: this.titles.titleNow,
};
this.io.emit('ontime-feat-cuesheet', featureData);
this.socket.send('ontime-feat-cuesheet', featureData);
}
/**
@@ -244,29 +220,20 @@ export class EventTimer extends Timer {
const numEvents = this._eventlist.length;
this.broadcastTimer();
this.io.emit('playstate', this.state);
this.io.emit('selected', {
this.socket.send('playstate', this.state);
this.socket.send('selected', {
id: this.selectedEventId,
index: this.selectedEventIndex,
total: numEvents,
});
this.io.emit('selected-id', this.selectedEventId);
this.io.emit('next-id', this.nextEventId);
this.io.emit('numevents', numEvents);
this.io.emit('publicselected-id', this.selectedPublicEventId);
this.io.emit('publicnext-id', this.nextPublicEventId);
this.io.emit('titles', this.titles);
this.io.emit('publictitles', this.titlesPublic);
this.io.emit('onAir', this.onAir);
}
/**
* Broadcast given message
* @param {string} address - socket io address
* @param {any} payload - message body
*/
broadcastThis(address, payload) {
this.io.emit(address, payload);
this.socket.send('selected-id', this.selectedEventId);
this.socket.send('next-id', this.nextEventId);
this.socket.send('numevents', numEvents);
this.socket.send('publicselected-id', this.selectedPublicEventId);
this.socket.send('publicnext-id', this.nextPublicEventId);
this.socket.send('titles', this.titles);
this.socket.send('publictitles', this.titlesPublic);
this.socket.send('onAir', this.onAir);
}
/**
@@ -282,7 +249,7 @@ export class EventTimer extends Timer {
case 'start': {
if (!numEvents) return false;
// Call action and force update
this.info('PLAYBACK', 'Play Mode Start');
this.socket.info('PLAYBACK', 'Play Mode Start');
this.start();
break;
}
@@ -290,8 +257,8 @@ export class EventTimer extends Timer {
if (!numEvents) return false;
const loaded = this.loadEventById(payload);
if (loaded) {
this.info('PLAYBACK', `Loaded event with ID ${payload}`);
this.info('PLAYBACK', 'Play Mode Start');
this.socket.info('PLAYBACK', `Loaded event with ID ${payload}`);
this.socket.info('PLAYBACK', 'Play Mode Start');
this.start();
} else {
return false;
@@ -302,8 +269,8 @@ export class EventTimer extends Timer {
if (!numEvents) return false;
const loaded = this.loadEventByIndex(payload);
if (loaded) {
this.info('PLAYBACK', `Loaded event with index ${payload}`);
this.info('PLAYBACK', 'Play Mode Start');
this.socket.info('PLAYBACK', `Loaded event with index ${payload}`);
this.socket.info('PLAYBACK', 'Play Mode Start');
this.start();
} else {
return false;
@@ -313,35 +280,35 @@ export class EventTimer extends Timer {
case 'pause': {
if (!numEvents) return false;
// Call action and force update
this.info('PLAYBACK', 'Play Mode Pause');
this.socket.info('PLAYBACK', 'Play Mode Pause');
this.pause();
break;
}
case 'stop': {
if (!numEvents) return false;
// Call action and force update
this.info('PLAYBACK', 'Play Mode Stop');
this.socket.info('PLAYBACK', 'Play Mode Stop');
this.stop();
break;
}
case 'roll': {
if (!numEvents) return false;
// Call action and force update
this.info('PLAYBACK', 'Play Mode Roll');
this.socket.info('PLAYBACK', 'Play Mode Roll');
this.roll();
break;
}
case 'previous': {
if (!numEvents) return false;
// Call action and force update
this.info('PLAYBACK', 'Play Mode Previous');
this.socket.info('PLAYBACK', 'Play Mode Previous');
this.previous();
break;
}
case 'next': {
if (!numEvents) return false;
// Call action and force update
this.info('PLAYBACK', 'Play Mode Next');
this.socket.info('PLAYBACK', 'Play Mode Next');
this.next();
break;
}
@@ -349,7 +316,7 @@ export class EventTimer extends Timer {
if (!numEvents) return false;
const loaded = this.loadEventById(payload);
if (loaded) {
this.info('PLAYBACK', `Loaded event with ID ${payload}`);
this.socket.info('PLAYBACK', `Loaded event with ID ${payload}`);
} else {
return false;
}
@@ -359,7 +326,7 @@ export class EventTimer extends Timer {
if (!numEvents) return false;
const loaded = this.loadEventByIndex(payload);
if (loaded) {
this.info('PLAYBACK', `Loaded event with index ${payload}`);
this.socket.info('PLAYBACK', `Loaded event with index ${payload}`);
} else {
return false;
}
@@ -368,32 +335,32 @@ export class EventTimer extends Timer {
case 'unload': {
if (!numEvents) return false;
// Call action and force update
this.info('PLAYBACK', 'Events unloaded');
this.socket.info('PLAYBACK', 'Events unloaded');
this.unload();
break;
}
case 'reload': {
if (!numEvents) return false;
// Call action and force update
this.info('PLAYBACK', 'Reloaded event');
this.socket.info('PLAYBACK', 'Reloaded event');
this.reload();
break;
}
case 'onAir': {
// Call action
this.info('PLAYBACK', 'Going On Air');
this.socket.info('PLAYBACK', 'Going On Air');
this.setonAir(true);
break;
}
case 'offAir': {
// Call action and force update
this.info('PLAYBACK', 'Going Off Air');
this.socket.info('PLAYBACK', 'Going Off Air');
this.setonAir(false);
break;
}
default: {
// Error, disable flag
this.error('RX', `Unhandled action triggered ${action}`);
this.socket.error('RX', `Unhandled action triggered ${action}`);
success = false;
break;
}
@@ -518,7 +485,7 @@ export class EventTimer extends Timer {
this.ontimeCycle = this.cycleState.onUpdate;
break;
default:
this.error('SERVER', `Unhandled cycle: ${this.ontimeCycle}`);
this.socket.error('SERVER', `Unhandled cycle: ${this.ontimeCycle}`);
}
// send http message if any
@@ -546,13 +513,13 @@ export class EventTimer extends Timer {
update() {
// if there is nothing selected, update clock
this.clock = this._getCurrentTime();
this.clock = Timer.getCurrentTime();
this._broadcastFeatureTimer();
this.broadcastTimer();
// if we are not updating, send the timers
if (this.ontimeCycle !== this.cycleState.onUpdate) {
this.io.emit('timer', this.getTimeObject());
this.socket.send('timer', this.getTimeObject());
}
// Have we skipped onStart?
@@ -616,33 +583,33 @@ export class EventTimer extends Timer {
// Presenter message
case 'set-timer-text':
this.presenter.text = payload;
this.broadcastThis('messages-timer', this.presenter);
this.socket.send('messages-timer', this.presenter);
break;
case 'set-timer-visible':
this.presenter.visible = payload;
this.broadcastThis('messages-timer', this.presenter);
this.socket.send('messages-timer', this.presenter);
break;
/*******************************************/
// Public message
case 'set-public-text':
this.public.text = payload;
this.broadcastThis('messages-public', this.public);
this.socket.send('messages-public', this.public);
break;
case 'set-public-visible':
this.public.visible = payload;
this.broadcastThis('messages-public', this.public);
this.socket.send('messages-public', this.public);
break;
/*******************************************/
// Lower third message
case 'set-lower-text':
this.lower.text = payload;
this.broadcastThis('messages-lower', this.lower);
this.socket.send('messages-lower', this.lower);
break;
case 'set-lower-visible':
this.lower.visible = payload;
this.broadcastThis('messages-lower', this.lower);
this.socket.send('messages-lower', this.lower);
break;
default:
@@ -650,286 +617,6 @@ export class EventTimer extends Timer {
}
}
/**
* Handle socket io connections
* @private
*/
_listenToConnections() {
this.io.on('connection', (socket) => {
/*******************************/
/*** HANDLE NEW CONNECTION ***/
/*** --------------------- ***/
/*******************************/
// keep track of connections
this._numClients++;
this._clientNames[socket.id] = getRandomName();
const m = `${this._numClients} Clients with new connection: ${this._clientNames[socket.id]}`;
this.info('CLIENT', m);
// send state
socket.emit('timer', this.getTimeObject());
socket.emit('playstate', this.state);
socket.emit('selected-id', this.selectedEventId);
socket.emit('next-id', this.nextEventId);
socket.emit('publicselected-id', this.selectedPublicEventId);
socket.emit('publicnext-id', this.nextPublicEventId);
/********************************/
/*** HANDLE DISCONNECT USER ***/
/*** ---------------------- ***/
/********************************/
socket.on('disconnect', () => {
this._numClients--;
const m = `${this._numClients} Clients with disconnection: ${this._clientNames[socket.id]}`;
delete this._clientNames[socket.id];
this.info('CLIENT', m);
});
/***************************************/
/*** TIMER STATE GETTERS / SETTERS ***/
/*** ------- WEBSOCKET API ------- ***/
/*** ----------------------------- ***/
/***************************************/
/*******************************************/
socket.on('ontime-test', () => {
socket.emit('hello');
});
socket.on('set-start', () => {
this.trigger('start');
socket.emit('playstate', this.state);
});
socket.on('set-startid', (data) => {
this.trigger('startById', data);
socket.emit('playstate', this.state);
});
socket.on('set-startindex', (data) => {
const eventIndex = Number(data);
if (isNaN(eventIndex)) {
return;
}
this.trigger('startByIndex', data);
socket.emit('playstate', this.state);
});
socket.on('set-loadid', (data) => {
this.trigger('loadById', data);
socket.emit('playstate', this.state);
});
socket.on('set-loadindex', (data) => {
const eventIndex = Number(data);
if (isNaN(eventIndex)) {
return;
}
this.trigger('loadByIndex', data);
socket.emit('playstate', this.state);
});
socket.on('set-pause', () => {
this.trigger('pause');
socket.emit('playstate', this.state);
});
socket.on('set-stop', () => {
this.trigger('stop');
socket.emit('playstate', this.state);
});
socket.on('set-reload', () => {
this.trigger('reload');
socket.emit('playstate', this.state);
});
socket.on('set-previous', () => {
this.trigger('previous');
socket.emit('playstate', this.state);
});
socket.on('set-next', () => {
this.trigger('next');
socket.emit('playstate', this.state);
});
socket.on('set-roll', () => {
this.trigger('roll');
socket.emit('playstate', this.state);
});
socket.on('set-delay', (data) => {
const delayTime = Number(data);
if (isNaN(delayTime)) {
return;
}
this.increment(delayTime * 1000 * 60);
});
socket.on('set-onAir', (data) => {
try {
const d = JSON.parse(data);
this.onAir = !!d;
} catch (error) {
this.error('RX', `Failed to parse message ${data}`);
}
this.broadcastThis('onAir', this.onAir);
this._broadcastFeatureMessageControl();
});
/*******************************************/
// general playback state
socket.on('get-state', () => {
socket.emit('timer', this.getTimeObject());
socket.emit('playstate', this.state);
socket.emit('selected-id', this.selectedEventId);
socket.emit('next-id', this.nextEventId);
socket.emit('publicselected-id', this.selectedPublicEventId);
socket.emit('publicnext-id', this.this.nextPublicEventId);
});
/*******************************************/
// ** TO BE DEPRECATED ** //
socket.on('get-timer', () => {
socket.emit('timer', this.getTimeObject());
});
// ** TO BE DEPRECATED IN FAVOR OF DELAY ** //
socket.on('increment-timer', (data) => {
if (isNaN(parseInt(data, 10))) return;
if (data < -5 || data > 5) return;
this.increment(data * 1000 * 60);
});
/*******************************************/
// playstate
socket.on('set-playstate', (data) => {
this.trigger(data);
this._broadcastFeaturePlaybackControl();
this._broadcastFeatureInfo();
});
socket.on('get-playstate', () => {
socket.emit('playstate', this.state);
});
socket.on('get-onAir', () => {
socket.emit('onAir', this.onAir);
});
/*******************************************/
// selection data
socket.on('get-selected', () => {
socket.emit('selected', {
id: this.selectedEventId,
index: this.selectedEventIndex,
total: this._eventlist.length,
});
});
socket.on('get-selected-id', () => {
socket.emit('selected-id', this.selectedEventId);
});
socket.on('get-next-id', () => {
socket.emit('next-id', this.nextEventId);
});
// title data
socket.on('get-titles', () => {
socket.emit('titles', this.titles);
});
socket.on('get-publictitles', () => {
socket.emit('publictitles', this.titlesPublic);
});
/***********************************/
/*** MESSAGE GETTERS / SETTERS ***/
/*** ------------------------- ***/
/***********************************/
/*******************************************/
// Presenter message
socket.on('set-timer-message-text', (data) => {
this._setTitles('set-timer-text', data);
this._broadcastFeatureMessageControl();
});
socket.on('set-timer-message-visible', (data) => {
this._setTitles('set-timer-visible', data);
this._broadcastFeatureMessageControl();
});
/*******************************************/
// Public message
socket.on('set-public-message-text', (data) => {
this._setTitles('set-public-text', data);
this._broadcastFeatureMessageControl();
});
socket.on('set-public-message-visible', (data) => {
this._setTitles('set-public-visible', data);
this._broadcastFeatureMessageControl();
});
/*******************************************/
// Lower third message
socket.on('set-lower-message-text', (data) => {
this._setTitles('set-lower-text', data);
this._broadcastFeatureMessageControl();
});
socket.on('set-lower-message-visible', (data) => {
this._setTitles('set-lower-visible', data);
this._broadcastFeatureMessageControl();
});
/* MOLECULAR ENDPOINTS
* =====================
* 1. EVENT LIST
* 2. MESSAGE CONTROL
* 3. PLAYBACK CONTROL
* 4. INFO
* 5. CUESHEET
* 6. TIMER OBJECT
* */
// 1. EVENT LIST
socket.on('get-ontime-feat-eventlist', () => {
this._broadcastFeatureEventList();
});
// 2. MESSAGE CONTROL
socket.on('get-ontime-feat-messagecontrol', () => {
this._broadcastFeatureMessageControl();
});
// 3. PLAYBACK CONTROL
socket.on('get-ontime-feat-playbackcontrol', () => {
this._broadcastFeaturePlaybackControl();
});
// 4. INFO
socket.on('get-ontime-feat-info', () => {
this._broadcastFeatureInfo();
});
// 5. CUE SHEET
socket.on('get-ontime-feat-cuesheet', () => {
this._broadcastFeatureCuesheet();
});
// 6. TIMER
socket.on('get-ontime-timer', () => {
this._broadcastFeatureTimer();
});
});
}
/**
* Deletes running event list from object
*/
@@ -944,7 +631,7 @@ export class EventTimer extends Timer {
this.ontimeCycle = this.cycleState.onStop;
// update clients
this.broadcastThis('numevents', this._eventlist.length);
this.socket.send('numevents', this._eventlist.length);
}
/**
@@ -1030,7 +717,7 @@ export class EventTimer extends Timer {
// find object in events
const eventIndex = this._eventlist.findIndex((e) => e.id === id);
if (eventIndex === -1) {
throw 'Event not found';
throw new Error('Event not found');
}
// check if event is set to be skipped
@@ -1068,7 +755,7 @@ export class EventTimer extends Timer {
this._loadTitlesNow();
}
} catch (error) {
this.error('SERVER', error);
this.socket.error('SERVER', error);
}
// update clients
@@ -1117,7 +804,7 @@ export class EventTimer extends Timer {
this._loadTitlesNow();
}
} catch (error) {
this.error('SERVER', error);
this.socket.error('SERVER', error);
}
// update clients
@@ -1228,7 +915,7 @@ export class EventTimer extends Timer {
this.selectedEventIndex = eventIndex;
this.selectedEventId = e.id;
} else if (type === 'reload') {
const now = this._getCurrentTime();
const now = Timer.getCurrentTime();
const elapsed = this.getElapsed();
this.duration = end - start;
@@ -1441,7 +1128,7 @@ export class EventTimer extends Timer {
setonAir(onAir) {
this.onAir = onAir;
// broadcast change
this.broadcastThis('onAir', onAir);
this.socket.send('onAir', onAir);
}
/**
@@ -1509,7 +1196,7 @@ export class EventTimer extends Timer {
* @description Look for current event considering local clock
*/
rollLoad() {
const now = this._getCurrentTime();
const now = Timer.getCurrentTime();
const prevLoaded = this.selectedEventId;
// maybe roll has already been loaded
@@ -1524,7 +1211,7 @@ export class EventTimer extends Timer {
// nothing to play, unload
if (nowIndex === null && nextIndex === null) {
this.unload();
this.warning('SERVER', 'Roll: no events found');
this.socket.warning('SERVER', 'Roll: no events found');
return;
}
@@ -1551,7 +1238,7 @@ export class EventTimer extends Timer {
if (nowIndex === null) {
// only warn the first time
if (this.secondaryTimer === null) {
this.info('SERVER', 'Roll: waiting for event start');
this.socket.info('SERVER', 'Roll: waiting for event start');
}
// reset running timer
@@ -1685,70 +1372,6 @@ export class EventTimer extends Timer {
/****************************************************************************/
/**
* Logger logic
* -------------
*
* This should be separate of event timer, left here for convenience
*
*/
/**
* Utility method, sends message and pushes into stack
* @param {string} level
* @param {string} origin
* @param {string} text
*/
_push(level, origin, text) {
const m = {
id: generateId(),
level,
origin,
text,
time: stringFromMillis(this._getCurrentTime()),
};
this.messageStack.unshift(m);
this.io.emit('logger', m);
if (process.env.NODE_ENV !== 'production') {
console.log(`[${m.level}] \t ${m.origin} \t ${m.text}`);
}
if (this.messageStack.length > this.MAX_MESSAGES) {
this.messageStack.pop();
}
}
/**
* Sends a message with level LOG
* @param {string} origin
* @param {string} text
*/
info(origin, text) {
this._push('INFO', origin, text);
}
/**
* Sends a message with level WARN
* @param {string} origin
* @param {string} text
*/
warning(origin, text) {
this._push('WARN', origin, text);
}
/**
* Sends a message with level ERROR
* @param {string} origin
* @param {string} text
*/
error(origin, text) {
this._push('ERROR', origin, text);
}
/****************************************************************************/
/**
* Integrations
* -------------
@@ -1765,20 +1388,20 @@ export class EventTimer extends Timer {
async sendOsc(message, payload) {
const reply = await this.osc.send(message, payload);
if (!reply.success) {
this.error('TX', reply.message);
this.socket.error('TX', reply.message);
}
}
/**
* Builds sync object
* @returns {{running: number, timer: (null|string|*), presenter: null, playback: string, clock: null, title: null}}
* @description Builds sync object
*/
poll() {
return {
clock: this.clock,
running: Timer.toSeconds(this.current),
currentId: this.selectedEventId,
timer: this.timeTag,
clock: this.clock,
playback: this.state,
currentColour: null,
title: this.titles.titleNow,
presenter: this.titles.presenterNow,
};
+5 -42
View File
@@ -11,39 +11,12 @@ export class Timer {
this.state = 'stop';
}
/**
* @description initiates a timer with given seconds
* @param seconds
* @param autoStart
*/
setupWithSeconds(seconds, autoStart = false) {
// aux
const now = this._getCurrentTime();
this.clock = now;
// populate targets
this.duration = seconds * 1000;
this._finishAt = now + seconds * 1000;
// start counting
this._startedAt = now;
if (autoStart) {
this.state = 'start';
} else {
this._pausedAt = now;
this._pausedInterval = 0;
}
this._pausedTotal = 0;
this.update();
}
/**
* @description updates the running timer
*/
update() {
// get current time
const now = this._getCurrentTime();
const now = Timer.getCurrentTime();
this.clock = now;
let checkFinish = false;
@@ -106,7 +79,7 @@ export class Timer {
* @return {number}
* @private
*/
_getCurrentTime() {
static getCurrentTime() {
const now = new Date();
// extract milliseconds since midnight
@@ -178,16 +151,6 @@ export class Timer {
};
}
/**
* @description get current time in seconds
* @return {number|number}
*/
getCurrentInSeconds() {
// update timeStamp
this.update();
return Timer.toSeconds(this.current);
}
// playback
/**
* @description start current time
@@ -197,7 +160,7 @@ export class Timer {
if (this.state === 'start') return;
else if (this._startedAt == null) {
// it hasn't started yet
const now = this._getCurrentTime();
const now = Timer.getCurrentTime();
// set start time as now
this._startedAt = now;
// calculate expected finish time
@@ -230,7 +193,7 @@ export class Timer {
}
// set pause time
this._pausedAt = this._getCurrentTime();
this._pausedAt = Timer.getCurrentTime();
// change state
this.state = 'pause';
@@ -259,7 +222,7 @@ export class Timer {
if (amount < 0 && Math.abs(amount) > this.current) {
// if we will make the clock negative
if (this._finishedAt == null) this._finishedAt = this._getCurrentTime();
if (this._finishedAt == null) this._finishedAt = Timer.getCurrentTime();
} else if (this.current < 0 && this.current + amount > 0) {
// clock will go from negative to positive
this._finishedAt = null;