mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-04 23:18:01 +00:00
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:
@@ -165,12 +165,6 @@ export const getInfo = async () => {
|
||||
return res.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description HTTP request to mutate application info
|
||||
* @return {Promise}
|
||||
*/
|
||||
export const postInfo = async (data) => axios.post(`${ontimeURL}/info`, data);
|
||||
|
||||
/**
|
||||
* @description HTTP request to retrieve view settings
|
||||
* @return {Promise}
|
||||
|
||||
@@ -58,6 +58,7 @@ export default function AppSettingsModal() {
|
||||
setSubmitting(true);
|
||||
|
||||
// set context
|
||||
// TODO: add fast-equals here and check if event settings have changed
|
||||
saveEventSettings(formSettings);
|
||||
const validation = { isValid: false };
|
||||
|
||||
@@ -83,12 +84,17 @@ export default function AppSettingsModal() {
|
||||
if (!validation.isValid) {
|
||||
emitError(`Invalid Input: ${validation.message}`);
|
||||
} else {
|
||||
await postSettings(formData);
|
||||
await refetch();
|
||||
try {
|
||||
await postSettings(formData);
|
||||
} catch (error) {
|
||||
emitError(`Error saving settings: ${error}`)
|
||||
} finally {
|
||||
await refetch();
|
||||
setChanged(false);
|
||||
}
|
||||
validation?.message && emitWarning(validation.message);
|
||||
}
|
||||
setSubmitting(false);
|
||||
setChanged(false);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { ModalBody } from '@chakra-ui/modal';
|
||||
import { FormLabel, Input, Textarea } from '@chakra-ui/react';
|
||||
import { EVENT_TABLE } from 'common/api/apiConstants';
|
||||
@@ -6,6 +6,7 @@ import { fetchEvent, postEvent } from 'common/api/eventApi';
|
||||
import { useFetch } from 'common/hooks/useFetch';
|
||||
|
||||
import { eventPlaceholderSettings } from '../../common/api/ontimeApi';
|
||||
import { LoggingContext } from '../../common/context/LoggingContext';
|
||||
|
||||
import { inputProps } from './modalHelper';
|
||||
import SubmitContainer from './SubmitContainer';
|
||||
@@ -14,6 +15,7 @@ import style from './Modals.module.scss';
|
||||
|
||||
export default function SettingsModal() {
|
||||
const { data, status, refetch } = useFetch(EVENT_TABLE, fetchEvent);
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const [formData, setFormData] = useState(eventPlaceholderSettings);
|
||||
const [changed, setChanged] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
@@ -42,13 +44,18 @@ export default function SettingsModal() {
|
||||
event.preventDefault();
|
||||
setSubmitting(true);
|
||||
|
||||
await postEvent(formData);
|
||||
await refetch();
|
||||
try {
|
||||
await postEvent(formData);
|
||||
} catch (error) {
|
||||
emitError(`Error saving event settings: ${error}`)
|
||||
} finally {
|
||||
await refetch();
|
||||
setChanged(false);
|
||||
}
|
||||
|
||||
setChanged(false);
|
||||
setSubmitting(false);
|
||||
},
|
||||
[formData, refetch]
|
||||
[emitError, formData, refetch]
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ModalBody } from '@chakra-ui/modal';
|
||||
import { FormControl, FormLabel, Input, Switch } from '@chakra-ui/react';
|
||||
import { FiInfo } from '@react-icons/all-files/fi/FiInfo';
|
||||
import { APP_TABLE } from 'common/api/apiConstants';
|
||||
import { getInfo, httpPlaceholder, ontimeVars, postInfo } from 'common/api/ontimeApi';
|
||||
import { getInfo, httpPlaceholder, ontimeVars } from 'common/api/ontimeApi';
|
||||
import { useFetch } from 'common/hooks/useFetch';
|
||||
|
||||
import { LoggingContext } from '../../common/context/LoggingContext';
|
||||
@@ -50,7 +50,7 @@ export default function IntegrationSettingsModal() {
|
||||
if (e.status) {
|
||||
emitError(`Invalid Input: ${e.message}`);
|
||||
} else {
|
||||
await postInfo(f);
|
||||
// call API endpoint here with value of f
|
||||
setChanged(false);
|
||||
setSubmitting(false);
|
||||
}
|
||||
|
||||
@@ -122,10 +122,14 @@ export default function OscSettingsModal() {
|
||||
if (e.status) {
|
||||
emitError(`Invalid Input: ${e.message}`);
|
||||
} else {
|
||||
// Post here
|
||||
await postOSC(formData);
|
||||
await refetch();
|
||||
setChanged(false);
|
||||
try {
|
||||
await postOSC(formData);
|
||||
} catch (error){
|
||||
emitError(`Error setting OSC: ${error}`)
|
||||
} finally {
|
||||
await refetch();
|
||||
setChanged(false);
|
||||
}
|
||||
}
|
||||
setSubmitting(false);
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { ModalBody } from '@chakra-ui/modal';
|
||||
import { Input } from '@chakra-ui/react';
|
||||
import { IoInformationCircleOutline } from '@react-icons/all-files/io5/IoInformationCircleOutline';
|
||||
@@ -6,6 +6,7 @@ import { USERFIELDS } from 'common/api/apiConstants';
|
||||
import { useFetch } from 'common/hooks/useFetch';
|
||||
|
||||
import { getUserFields, postUserFields, userFieldsPlaceholder } from '../../common/api/ontimeApi';
|
||||
import { LoggingContext } from '../../common/context/LoggingContext';
|
||||
import { handleLinks, host } from '../../common/utils/linkUtils';
|
||||
|
||||
import SubmitContainer from './SubmitContainer';
|
||||
@@ -14,6 +15,7 @@ import style from './Modals.module.scss';
|
||||
|
||||
export default function TableOptionsModal() {
|
||||
const { data, status, refetch } = useFetch(USERFIELDS, getUserFields);
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const [changed, setChanged] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [userFields, setUserFields] = useState(userFieldsPlaceholder);
|
||||
@@ -43,13 +45,17 @@ export default function TableOptionsModal() {
|
||||
}
|
||||
|
||||
if (!errors) {
|
||||
await postUserFields(validatedFields);
|
||||
try {
|
||||
await postUserFields(validatedFields);
|
||||
} catch (error) {
|
||||
emitError(`Error saving table options: ${error}`)
|
||||
}
|
||||
await refetch();
|
||||
setChanged(false);
|
||||
}
|
||||
|
||||
setSubmitting(false);
|
||||
},[refetch, userFields]);
|
||||
},[emitError, refetch, userFields]);
|
||||
|
||||
/**
|
||||
* Reverts local state equals to server state
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { ModalBody } from '@chakra-ui/modal';
|
||||
import { FormControl, FormLabel } from '@chakra-ui/react';
|
||||
import { IoInformationCircleOutline } from '@react-icons/all-files/io5/IoInformationCircleOutline';
|
||||
@@ -6,6 +6,7 @@ import { IoInformationCircleOutline } from '@react-icons/all-files/io5/IoInforma
|
||||
import { VIEW_SETTINGS } from '../../common/api/apiConstants';
|
||||
import { getView, postView, viewsPlaceholder } from '../../common/api/ontimeApi';
|
||||
import EnableBtn from '../../common/components/buttons/EnableBtn';
|
||||
import { LoggingContext } from '../../common/context/LoggingContext';
|
||||
import { useFetch } from '../../common/hooks/useFetch';
|
||||
import { openLink } from '../../common/utils/linkUtils';
|
||||
|
||||
@@ -15,6 +16,7 @@ import style from './Modals.module.scss';
|
||||
|
||||
export default function ViewsSettingsModal() {
|
||||
const { data, status, refetch } = useFetch(VIEW_SETTINGS, getView);
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const [formData, setFormData] = useState(viewsPlaceholder);
|
||||
const [changed, setChanged] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
@@ -35,12 +37,17 @@ export default function ViewsSettingsModal() {
|
||||
async (event) => {
|
||||
event.preventDefault();
|
||||
setSubmitting(true);
|
||||
await postView(formData);
|
||||
await refetch();
|
||||
setChanged(false);
|
||||
try {
|
||||
await postView(formData);
|
||||
} catch (error) {
|
||||
emitError(`Error view settings: ${error}`)
|
||||
} finally{
|
||||
await refetch();
|
||||
setChanged(false);
|
||||
}
|
||||
setSubmitting(false);
|
||||
},
|
||||
[formData, refetch]
|
||||
[emitError, formData, refetch]
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
appIni: {
|
||||
mainWindowWait: 2000,
|
||||
},
|
||||
reactAppUrl: {
|
||||
development: 'http://localhost:3000/editor',
|
||||
production: 'http://localhost:4001/editor',
|
||||
},
|
||||
externalUrls: {
|
||||
help: 'https://cpvalente.gitbook.io/ontime/',
|
||||
},
|
||||
};
|
||||
+15
-14
@@ -10,19 +10,18 @@ const {
|
||||
Notification,
|
||||
} = require('electron');
|
||||
const path = require('path');
|
||||
|
||||
const electronConfig = require('./electron.config');
|
||||
if (process.env.NODE_ENV === undefined) {
|
||||
process.env.NODE_ENV = 'production';
|
||||
}
|
||||
const env = process.env.NODE_ENV;
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
let loaded = 'Nothing loaded';
|
||||
let isQuitting = false;
|
||||
|
||||
const nodePath =
|
||||
env !== 'production'
|
||||
? path.join('file://', __dirname, 'src/app.js')
|
||||
: path.join('file://', __dirname, '../', 'extraResources', 'src/app.js');
|
||||
const nodePath = isProduction
|
||||
? path.join('file://', __dirname, '../', 'extraResources', 'src/app.js')
|
||||
: path.join('file://', __dirname, 'src/app.js');
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
@@ -144,22 +143,25 @@ app.whenReady().then(() => {
|
||||
// give the nodejs server some time
|
||||
setTimeout(() => {
|
||||
// Load page served by node
|
||||
const reactApp =
|
||||
env === 'development' ? 'http://localhost:3000/editor' : 'http://localhost:4001/editor';
|
||||
const reactApp = isProduction
|
||||
? electronConfig.reactAppUrl.production
|
||||
: electronConfig.reactAppUrl.development;
|
||||
|
||||
win.loadURL(reactApp).then(() => {
|
||||
win.webContents.setBackgroundThrottling(false);
|
||||
|
||||
// window stuff
|
||||
win.show();
|
||||
win.focus();
|
||||
|
||||
splash.destroy();
|
||||
|
||||
// tray stuff
|
||||
tray.setToolTip(loaded);
|
||||
if (typeof loaded === 'string') {
|
||||
tray.setToolTip(loaded);
|
||||
} else {
|
||||
tray.setToolTip('Initialising error: please restart ontime');
|
||||
}
|
||||
});
|
||||
}, 2000);
|
||||
}, electronConfig.appIni.mainWindowWait);
|
||||
|
||||
// Hide on close
|
||||
win.on('close', function (event) {
|
||||
@@ -173,7 +175,6 @@ app.whenReady().then(() => {
|
||||
});
|
||||
|
||||
// create tray
|
||||
// TODO: Design better icon
|
||||
tray = new Tray(trayIcon);
|
||||
|
||||
// Define context menu
|
||||
@@ -269,7 +270,7 @@ ipcMain.on('send-to-link', (event, arg) => {
|
||||
|
||||
// send to help URL
|
||||
if (arg === 'help') {
|
||||
shell.openExternal('https://cpvalente.gitbook.io/ontime/');
|
||||
shell.openExternal(electronConfig.externalUrls.help);
|
||||
} else {
|
||||
shell.openExternal(arg);
|
||||
}
|
||||
|
||||
+24
-15
@@ -21,9 +21,11 @@ import { router as playbackRouter } from './routes/playbackRouter.js';
|
||||
|
||||
// Global Objects
|
||||
import { EventTimer } from './classes/timer/EventTimer.js';
|
||||
import { socketProvider } from './classes/socket/SocketController.js';
|
||||
// Start OSC server
|
||||
import { initiateOSC, shutdownOSCServer } from './controllers/OscController.js';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { DataProvider } from './classes/data-provider/DataProvider.js';
|
||||
|
||||
// get environment
|
||||
const env = process.env.NODE_ENV || 'production';
|
||||
@@ -31,9 +33,11 @@ const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
export const { db, data } = await loadDb(__dirname);
|
||||
|
||||
console.log(`Starting ontime version ${process.env.npm_package_version}`);
|
||||
|
||||
// import socket provider
|
||||
const socket = socketProvider;
|
||||
|
||||
// Create express APP
|
||||
const app = express();
|
||||
app.disable('x-powered-by');
|
||||
@@ -80,22 +84,22 @@ app.use((err, req, res, next) => {
|
||||
*
|
||||
*/
|
||||
|
||||
const osc = data.osc;
|
||||
const { osc, settings } = DataProvider.getData();
|
||||
const oscIP = osc?.targetIP || config.osc.targetIP;
|
||||
const oscOutPort = osc?.portOut || config.osc.portOut;
|
||||
const oscInPort = osc?.port || config.osc.port;
|
||||
const oscInEnabled = osc?.enabled !== undefined ? osc.enabled : config.osc.inputEnabled;
|
||||
|
||||
const serverPort = data.settings.serverPort || config.server.port;
|
||||
const serverPort = settings.serverPort || config.server.port;
|
||||
|
||||
/**
|
||||
* @description starts OSC server
|
||||
* @description starts OSC server
|
||||
* @param overrideConfig
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
export const startOSCServer = async (overrideConfig = null) => {
|
||||
if (!oscInEnabled) {
|
||||
global.timer.info('RX', 'OSC Input Disabled');
|
||||
socket.info('RX', 'OSC Input Disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -105,7 +109,7 @@ export const startOSCServer = async (overrideConfig = null) => {
|
||||
};
|
||||
|
||||
// Start OSC Server
|
||||
global.timer.info('RX', `Starting OSC Server on port: ${oscInPort}`);
|
||||
socket.info('RX', `Starting OSC Server on port: ${oscInPort}`);
|
||||
initiateOSC(oscSettings);
|
||||
};
|
||||
|
||||
@@ -119,11 +123,16 @@ const server = http.createServer(app);
|
||||
*/
|
||||
export const startServer = async (overrideConfig = null) => {
|
||||
const port = 4001; // port hardcoded
|
||||
const { events, http } = DataProvider.getData();
|
||||
|
||||
// Start server
|
||||
const returnMessage = `Ontime is listening on port ${port}`;
|
||||
server.listen(port, '0.0.0.0');
|
||||
|
||||
// init socket controller
|
||||
await socket.initServer(server);
|
||||
socket.info('SERVER', 'Socket initialised');
|
||||
|
||||
// OSC Config
|
||||
const oscConfig = {
|
||||
ip: oscIP,
|
||||
@@ -131,9 +140,11 @@ export const startServer = async (overrideConfig = null) => {
|
||||
};
|
||||
|
||||
// init timer
|
||||
global.timer = new EventTimer(server, config.timer, oscConfig, data.http);
|
||||
global.timer.setupWithEventList(data.events);
|
||||
global.timer.info('SERVER', returnMessage);
|
||||
global.timer = new EventTimer(socket, config.timer, oscConfig, http);
|
||||
global.timer.setupWithEventList(events);
|
||||
|
||||
socket.info('SERVER', returnMessage);
|
||||
socket.startListener();
|
||||
return returnMessage;
|
||||
};
|
||||
|
||||
@@ -146,16 +157,14 @@ export const shutdown = async () => {
|
||||
// shutdown express server
|
||||
server.close();
|
||||
|
||||
// shutdown OSC Server
|
||||
shutdownOSCServer();
|
||||
|
||||
// shutdown timer
|
||||
global.timer.shutdown();
|
||||
socket.shutdown();
|
||||
};
|
||||
|
||||
// register shutdown signals
|
||||
process.once('SIGHUP', shutdown)
|
||||
process.once('SIGINT', shutdown)
|
||||
process.once('SIGTERM', shutdown)
|
||||
process.once('SIGHUP', shutdown);
|
||||
process.once('SIGINT', shutdown);
|
||||
process.once('SIGTERM', shutdown);
|
||||
|
||||
export { server, app };
|
||||
|
||||
@@ -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();
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
// get database
|
||||
import { db, data } from '../app.js';
|
||||
import { removeUndefined } from '../utils/parserUtils.js';
|
||||
import { failEmptyObjects } from '../utils/routerUtils.js';
|
||||
import { DataProvider } from '../classes/data-provider/DataProvider.js';
|
||||
|
||||
// Create controller for GET request to 'event'
|
||||
// Returns ACK message
|
||||
export const getEvent = async (req, res) => {
|
||||
res.json(data.event);
|
||||
res.json(DataProvider.getEventData());
|
||||
};
|
||||
|
||||
// Create controller for POST request to 'event'
|
||||
// Returns ACK message
|
||||
export const postEvent = async (req, res) => {
|
||||
if (!req.body) {
|
||||
res.status(400).send('No object found in request');
|
||||
if (failEmptyObjects(req.body, res)) {
|
||||
return;
|
||||
}
|
||||
// TODO: validate data
|
||||
|
||||
try {
|
||||
data.event = { ...data.event, ...req.body };
|
||||
await db.write();
|
||||
res.sendStatus(200);
|
||||
const newEvent = removeUndefined({
|
||||
title: req.body?.title,
|
||||
url: req.body?.url,
|
||||
publicInfo: req.body?.publicInfo,
|
||||
backstageInfo: req.body?.backstageInfo,
|
||||
endMessage: req.body?.endMessage,
|
||||
});
|
||||
const newData = await DataProvider.setEventData(newEvent);
|
||||
res.status(200).send(newData);
|
||||
} catch (error) {
|
||||
res.status(400).send(error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { body, validationResult } from 'express-validator';
|
||||
|
||||
export const eventSanitizer = [
|
||||
body('title').optional().isString().trim().escape(),
|
||||
body('url').optional().isString().trim().escape(),
|
||||
body('publicInfo').optional().isString().trim().escape(),
|
||||
body('backstageInfo').optional().isString().trim().escape(),
|
||||
body('endMessage').optional().isString().trim().escape(),
|
||||
(req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||
next();
|
||||
},
|
||||
];
|
||||
@@ -1,7 +1,3 @@
|
||||
// get database
|
||||
import { data, db } from '../app.js';
|
||||
|
||||
// utils
|
||||
import {
|
||||
block as blockDef,
|
||||
delay as delayDef,
|
||||
@@ -10,87 +6,36 @@ import {
|
||||
import { generateId } from '../utils/generate_id.js';
|
||||
import { MAX_EVENTS } from '../settings.js';
|
||||
import { getPreviousPlayable } from '../utils/eventUtils.js';
|
||||
import { DataProvider } from '../classes/data-provider/DataProvider.js';
|
||||
import { failEmptyObjects } from '../utils/routerUtils.js';
|
||||
import { socketProvider } from '../classes/socket/SocketController.js';
|
||||
|
||||
// import socket provider
|
||||
const socket = socketProvider;
|
||||
|
||||
async function _insertAndSync(newEvent) {
|
||||
if (newEvent.order) {
|
||||
const events = data.events;
|
||||
await _insertAt(newEvent, newEvent.order);
|
||||
const events = DataProvider.getEvents();
|
||||
await DataProvider.insertEventAt(newEvent, newEvent.order);
|
||||
const previousId = events?.[newEvent.order - 1]?.id;
|
||||
_insertEventInTimerAfterId(newEvent, previousId);
|
||||
} else if (newEvent.after) {
|
||||
await _insertAfterId(newEvent, newEvent.after);
|
||||
await DataProvider.insertEventAfterId(newEvent, newEvent.after);
|
||||
_insertEventInTimerAfterId(newEvent, newEvent.after);
|
||||
} else {
|
||||
await _insertAt(newEvent, 0);
|
||||
await DataProvider.insertEventAt(newEvent, 0);
|
||||
_insertEventInTimerAfterId(newEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insets an event after a given index
|
||||
* @param entry
|
||||
* @param index
|
||||
* @return {Promise<void>}
|
||||
* @private
|
||||
*/
|
||||
async function _insertAt(entry, index) {
|
||||
// get events
|
||||
const events = data.events;
|
||||
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
|
||||
data.events = events;
|
||||
await db.write();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Inserts an entry after an element with given Id
|
||||
* @param entry
|
||||
* @param id
|
||||
* @return {Promise<void>}
|
||||
* @private
|
||||
*/
|
||||
async function _insertAfterId(entry, id) {
|
||||
const index = [...data.events].findIndex((event) => event.id === id);
|
||||
await _insertAt(entry, index + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description deletes an event from the db given its id
|
||||
* @param eventId
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async function _removeById(eventId) {
|
||||
data.events = Array.from(data.events).filter((e) => e.id !== eventId);
|
||||
await db.write();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description returns all events of type event
|
||||
* @return {unknown[]}
|
||||
*/
|
||||
function getEventEvents() {
|
||||
// return data.events.filter((e) => e.type === 'event');
|
||||
return Array.from(data.events).filter((e) => e.type === 'event');
|
||||
const events = DataProvider.getEvents();
|
||||
return Array.from(events).filter((e) => e.type === 'event');
|
||||
}
|
||||
|
||||
// Updates timer object
|
||||
@@ -112,7 +57,7 @@ function _insertEventInTimerAfterId(event, previousId) {
|
||||
try {
|
||||
global.timer.insertEventAfterId(event, previousId);
|
||||
} catch (error) {
|
||||
global.timer.error('SERVER', `Unable to update object: ${error}`);
|
||||
socket.error('SERVER', `Unable to update object: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +80,7 @@ function _deleteTimerId(entryId) {
|
||||
// Create controller for GET request to '/events'
|
||||
// Returns -
|
||||
export const eventsGetAll = async (req, res) => {
|
||||
res.json(data.events);
|
||||
res.json(DataProvider.getEvents());
|
||||
};
|
||||
|
||||
// Create controller for GET request to '/events/:eventId'
|
||||
@@ -146,7 +91,7 @@ export const eventsGetById = async (req, res) => {
|
||||
if (id == null) {
|
||||
res.status(400).send(`No eventId found in request`);
|
||||
} else {
|
||||
const event = data.events.find((e) => e.id === id);
|
||||
const event = DataProvider.getEventById(id);
|
||||
res.json(event);
|
||||
}
|
||||
};
|
||||
@@ -154,13 +99,12 @@ export const eventsGetById = async (req, res) => {
|
||||
// Create controller for POST request to '/events/'
|
||||
// Returns -
|
||||
export const eventsPost = async (req, res) => {
|
||||
// TODO: Validate event
|
||||
if (!req.body) {
|
||||
res.status(400).send(`No object found in request`);
|
||||
if (failEmptyObjects(req.body, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.events.length > MAX_EVENTS) {
|
||||
const numEvents = DataProvider.getNumEvents();
|
||||
if (numEvents > MAX_EVENTS) {
|
||||
const error = `ERROR: Reached limit number of ${MAX_EVENTS} events`;
|
||||
res.status(400).send(error);
|
||||
return;
|
||||
@@ -187,7 +131,7 @@ export const eventsPost = async (req, res) => {
|
||||
}
|
||||
|
||||
try {
|
||||
_insertAndSync(newEvent);
|
||||
await _insertAndSync(newEvent);
|
||||
res.sendStatus(201);
|
||||
} catch (error) {
|
||||
res.status(400).send(error);
|
||||
@@ -197,41 +141,33 @@ export const eventsPost = async (req, res) => {
|
||||
// Create controller for PUT request to '/events/'
|
||||
// Returns -
|
||||
export const eventsPut = async (req, res) => {
|
||||
// no valid params
|
||||
if (!req.body) {
|
||||
res.status(400).send(`No object found`);
|
||||
if (failEmptyObjects(req.body, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventId = req.body.id;
|
||||
if (!eventId) {
|
||||
res.status(400).send(`Object malformed: id missing`);
|
||||
return;
|
||||
}
|
||||
|
||||
const eventIndex = data.events.findIndex((e) => e.id === eventId);
|
||||
if (eventIndex === -1) {
|
||||
res.status(400).send(`No Id found found`);
|
||||
const eventDataFromRequest = req.body;
|
||||
const eventId = eventDataFromRequest.id;
|
||||
const event = DataProvider.getEventById(eventId);
|
||||
if (typeof event === 'undefined') {
|
||||
res.status(400).send(`No event with ID found`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const e = data.events[eventIndex];
|
||||
data.events[eventIndex] = { ...e, ...req.body };
|
||||
data.events[eventIndex].revision++;
|
||||
await db.write();
|
||||
const newData = DataProvider.updateEventById(eventId, eventDataFromRequest);
|
||||
|
||||
if (data.events[eventIndex].skip) {
|
||||
if (newData.skip) {
|
||||
_deleteTimerId(eventId);
|
||||
// if it is a skip, i make sure it is deleted from timer
|
||||
// if it is a skip, make sure it is deleted from timer
|
||||
// event id might already not exist
|
||||
} else {
|
||||
try {
|
||||
_updateTimersSingle(eventId, req.body);
|
||||
_updateTimersSingle(newData.id, eventDataFromRequest);
|
||||
} catch (error) {
|
||||
if (error === 'Event not found') {
|
||||
const { id: previousId } = getPreviousPlayable(data.events, e.id);
|
||||
_insertEventInTimerAfterId(data.events[eventIndex], previousId);
|
||||
const events = DataProvider.getEvents();
|
||||
const { id: previousId } = getPreviousPlayable(events, newData.id);
|
||||
_insertEventInTimerAfterId(newData, previousId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -250,16 +186,14 @@ export const eventsPatch = async (req, res) => {
|
||||
};
|
||||
|
||||
export const eventsReorder = async (req, res) => {
|
||||
// TODO: Validate event
|
||||
if (!req.body) {
|
||||
res.status(400).send(`No object found in request`);
|
||||
if (failEmptyObjects(req.body, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { index, from, to } = req.body;
|
||||
|
||||
// get events
|
||||
const events = data.events;
|
||||
const events = DataProvider.getEvents();
|
||||
const idx = events.findIndex((e) => e.id === index, from);
|
||||
|
||||
// Check if item is at given index
|
||||
@@ -276,8 +210,7 @@ export const eventsReorder = async (req, res) => {
|
||||
events.splice(to, 0, reorderedItem);
|
||||
|
||||
// save events
|
||||
data.events = events;
|
||||
await db.write();
|
||||
await DataProvider.setEventData(events);
|
||||
|
||||
// update timer
|
||||
_updateTimers();
|
||||
@@ -291,15 +224,9 @@ export const eventsReorder = async (req, res) => {
|
||||
// Create controller for PATCH request to '/events/applydelay/:eventId'
|
||||
// Returns -
|
||||
export const eventsApplyDelay = async (req, res) => {
|
||||
// no valid params
|
||||
if (!req.params.eventId) {
|
||||
res.status(400).send(`No id found in request`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// get events
|
||||
const events = data.events;
|
||||
const events = DataProvider.getEvents();
|
||||
|
||||
// AUX
|
||||
let delayIndex = null;
|
||||
@@ -340,8 +267,7 @@ export const eventsApplyDelay = async (req, res) => {
|
||||
if (blockIndex) events.splice(blockIndex - 1, 1);
|
||||
|
||||
// update events
|
||||
data.events = events;
|
||||
await db.write();
|
||||
await DataProvider.setEvents(events);
|
||||
|
||||
// update timer
|
||||
_updateTimers();
|
||||
@@ -355,20 +281,15 @@ export const eventsApplyDelay = async (req, res) => {
|
||||
// Create controller for DELETE request to '/events/:eventId'
|
||||
// Returns -
|
||||
export const eventsDelete = async (req, res) => {
|
||||
// no valid params
|
||||
if (!req.params.eventId) {
|
||||
res.status(400).send(`No id found in request`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const eventId = req.params.eventId;
|
||||
|
||||
// remove new event
|
||||
await _removeById(req.params.eventId);
|
||||
|
||||
await DataProvider.deleteEvent(eventId);
|
||||
// update timer
|
||||
_deleteTimerId(req.params.eventId);
|
||||
_deleteTimerId(eventId);
|
||||
|
||||
res.sendStatus(201);
|
||||
res.sendStatus(204);
|
||||
} catch (error) {
|
||||
res.status(400).send(error);
|
||||
}
|
||||
@@ -378,14 +299,9 @@ export const eventsDelete = async (req, res) => {
|
||||
// Returns -
|
||||
export const eventsDeleteAll = async (req, res) => {
|
||||
try {
|
||||
// set with nothing
|
||||
data.events = [];
|
||||
await db.write();
|
||||
|
||||
// update timer object
|
||||
await DataProvider.deleteAllEvents();
|
||||
global.timer.clearEventList();
|
||||
|
||||
res.sendStatus(201);
|
||||
res.sendStatus(204);
|
||||
} catch (error) {
|
||||
res.status(400).send(error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { body, param, validationResult } from 'express-validator';
|
||||
|
||||
export const eventsPutValidator = [
|
||||
body('id').isString().exists(),
|
||||
(req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||
next();
|
||||
},
|
||||
];
|
||||
|
||||
export const paramsMustHaveEventId = [
|
||||
param('eventId').exists(),
|
||||
(req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||
next();
|
||||
},
|
||||
];
|
||||
@@ -1,10 +1,11 @@
|
||||
import fs from 'fs';
|
||||
import { data, db } from '../app.js';
|
||||
import { networkInterfaces } from 'os';
|
||||
import { fileHandler } from '../utils/parser.js';
|
||||
import { generateId } from '../utils/generate_id.js';
|
||||
import { resolveDbPath } from '../modules/loadDb.js';
|
||||
import { DataProvider } from '../classes/data-provider/DataProvider.js';
|
||||
import { failEmptyObjects, failIsNotArray } from '../utils/routerUtils.js';
|
||||
import { mergeObject } from '../utils/parserUtils.js';
|
||||
|
||||
// Create controller for GET request to '/ontime/poll'
|
||||
// Returns data for current state
|
||||
@@ -22,7 +23,8 @@ export const poll = async (req, res) => {
|
||||
// Create controller for GET request to '/ontime/db'
|
||||
// Returns -
|
||||
export const dbDownload = async (req, res) => {
|
||||
const fileTitle = data?.event?.title || 'ontime events';
|
||||
const { title } = DataProvider.getEventData();
|
||||
const fileTitle = title || 'ontime events';
|
||||
const dbInDisk = resolveDbPath();
|
||||
|
||||
res.download(dbInDisk, `${fileTitle}.json`, (err) => {
|
||||
@@ -48,18 +50,13 @@ const uploadAndParse = async (file, req, res, options) => {
|
||||
} else if (result.message === 'success') {
|
||||
// explicitly write objects
|
||||
if (typeof result !== 'undefined') {
|
||||
if (!options.onlyEvents) {
|
||||
const mergedData = DataProvider.safeMerge(data, result.data);
|
||||
data.event = mergedData.event;
|
||||
data.settings = mergedData.settings;
|
||||
data.osc = mergedData.osc;
|
||||
data.http = mergedData.http;
|
||||
data.aliases = mergedData.aliases;
|
||||
data.userFields = mergedData.userFields;
|
||||
const newEvents = result.data.events || [];
|
||||
if (options.onlyEvents) {
|
||||
await DataProvider.setEvents(newEvents);
|
||||
} else {
|
||||
await DataProvider.mergeIntoData(result.data);
|
||||
}
|
||||
data.events = result.data.events || [];
|
||||
global.timer.setupWithEventList(result.data.events || []);
|
||||
await db.write();
|
||||
global.timer.setupWithEventList(newEvents);
|
||||
}
|
||||
res.sendStatus(200);
|
||||
} else {
|
||||
@@ -71,7 +68,7 @@ const uploadAndParse = async (file, req, res, options) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Gets information on IPV4 non internal interfaces
|
||||
* @description Gets information on IPV4 non-internal interfaces
|
||||
* @returns {array} - Array of objects {name: ip}
|
||||
*/
|
||||
const getNetworkInterfaces = () => {
|
||||
@@ -96,15 +93,8 @@ const getNetworkInterfaces = () => {
|
||||
// Create controller for POST request to '/ontime/info'
|
||||
// Returns -
|
||||
export const getInfo = async (req, res) => {
|
||||
const version = data.settings.version;
|
||||
const serverPort = data.settings.serverPort;
|
||||
|
||||
const osc = {
|
||||
port: data.osc.port,
|
||||
portOut: data.osc.portOut,
|
||||
targetIP: data.osc.targetIP,
|
||||
enabled: data.osc.enabled,
|
||||
};
|
||||
const { version, serverPort } = DataProvider.getSettings();
|
||||
const osc = DataProvider.getOsc();
|
||||
|
||||
// get nif and inject localhost
|
||||
const ni = getNetworkInterfaces();
|
||||
@@ -122,18 +112,16 @@ export const getInfo = async (req, res) => {
|
||||
// Create controller for POST request to '/ontime/aliases'
|
||||
// Returns -
|
||||
export const getAliases = async (req, res) => {
|
||||
// send aliases array
|
||||
res.status(200).send(data.aliases);
|
||||
const aliases = DataProvider.getAliases();
|
||||
res.status(200).send(aliases);
|
||||
};
|
||||
|
||||
// Create controller for POST request to '/ontime/aliases'
|
||||
// Returns ACK message
|
||||
export const postAliases = async (req, res) => {
|
||||
if (!req.body) {
|
||||
res.status(400).send('No object found in request');
|
||||
if (failIsNotArray()) {
|
||||
return;
|
||||
}
|
||||
// TODO: validate data
|
||||
try {
|
||||
const newAliases = [];
|
||||
req.body.forEach((a) => {
|
||||
@@ -144,9 +132,8 @@ export const postAliases = async (req, res) => {
|
||||
pathAndParams: a.pathAndParams,
|
||||
});
|
||||
});
|
||||
data.aliases = newAliases;
|
||||
await db.write();
|
||||
res.sendStatus(200);
|
||||
await DataProvider.setAliases(newAliases);
|
||||
res.status(200).send(newAliases);
|
||||
} catch (error) {
|
||||
res.status(400).send(error);
|
||||
}
|
||||
@@ -155,27 +142,21 @@ export const postAliases = async (req, res) => {
|
||||
// Create controller for GET request to '/ontime/userfields'
|
||||
// Returns -
|
||||
export const getUserFields = async (req, res) => {
|
||||
// send userFields array
|
||||
res.status(200).send(data.userFields);
|
||||
const userFields = DataProvider.getUserFields();
|
||||
res.status(200).send(userFields);
|
||||
};
|
||||
|
||||
// Create controller for POST request to '/ontime/userfields'
|
||||
// Returns ACK message
|
||||
export const postUserFields = async (req, res) => {
|
||||
if (!req.body) {
|
||||
res.status(400).send('No object found in request');
|
||||
if (failEmptyObjects(req.body, res)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const newUserFields = { ...data.userFields };
|
||||
for (const field in newUserFields) {
|
||||
if (typeof req.body[field] !== 'undefined') {
|
||||
newUserFields[field] = req.body[field];
|
||||
}
|
||||
}
|
||||
data.userFields = newUserFields;
|
||||
await db.write();
|
||||
res.sendStatus(200);
|
||||
const persistedData = DataProvider.getUserFields();
|
||||
const newData = mergeObject(persistedData, req.body);
|
||||
await DataProvider.setUserFields(newData);
|
||||
res.status(200).send(newData);
|
||||
} catch (error) {
|
||||
res.status(400).send(error);
|
||||
}
|
||||
@@ -184,12 +165,8 @@ export const postUserFields = async (req, res) => {
|
||||
// Create controller for POST request to '/ontime/settings'
|
||||
// Returns -
|
||||
export const getSettings = async (req, res) => {
|
||||
const version = data.settings.version;
|
||||
const serverPort = data.settings.serverPort;
|
||||
const pinCode = data.settings.pinCode;
|
||||
const timeFormat = data.settings.timeFormat;
|
||||
const { version, serverPort, pinCode, timeFormat } = DataProvider.getSettings();
|
||||
|
||||
// send object with network information
|
||||
res.status(200).send({
|
||||
version,
|
||||
serverPort,
|
||||
@@ -201,12 +178,12 @@ export const getSettings = async (req, res) => {
|
||||
// Create controller for POST request to '/ontime/settings'
|
||||
// Returns ACK message
|
||||
export const postSettings = async (req, res) => {
|
||||
if (!req.body) {
|
||||
res.status(400).send('No object found in request');
|
||||
if (failEmptyObjects(req.body, res)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let pin = data.settings.pinCode;
|
||||
const settings = DataProvider.getSettings();
|
||||
let pin = settings.pinCode;
|
||||
if (typeof req.body?.pinCode === 'string') {
|
||||
if (req.body?.pinCode.length === 0) {
|
||||
pin = null;
|
||||
@@ -215,20 +192,20 @@ export const postSettings = async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
let timeFormat = data.settings.timeFormat;
|
||||
let format = settings.timeFormat;
|
||||
if (typeof req.body?.timeFormat === 'string') {
|
||||
if (req.body?.timeFormat === '12' || req.body?.timeFormat === '24') {
|
||||
timeFormat = req.body.timeFormat;
|
||||
format = req.body.timeFormat;
|
||||
}
|
||||
}
|
||||
|
||||
data.settings = {
|
||||
...data.settings,
|
||||
const newData = {
|
||||
...settings,
|
||||
pinCode: pin,
|
||||
timeFormat: timeFormat,
|
||||
timeFormat: format,
|
||||
};
|
||||
await db.write();
|
||||
res.sendStatus(200);
|
||||
DataProvider.setSettings(newData);
|
||||
res.status(200).send(newData);
|
||||
} catch (error) {
|
||||
res.status(400).send(error);
|
||||
}
|
||||
@@ -239,7 +216,8 @@ export const postSettings = async (req, res) => {
|
||||
* @method GET
|
||||
*/
|
||||
export const getViewSettings = async (req, res) => {
|
||||
res.status(200).send({ ...data.views });
|
||||
const views = DataProvider.getViews();
|
||||
res.status(200).send(views);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -247,33 +225,14 @@ export const getViewSettings = async (req, res) => {
|
||||
* @method POST
|
||||
*/
|
||||
export const postViewSettings = async (req, res) => {
|
||||
if (!req.body) {
|
||||
res.status(400).send('No object found in request');
|
||||
if (failEmptyObjects(req.body, res)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
data.views = {
|
||||
overrideStyles: req.body?.overrideStyles ?? data.views.overrideStyles,
|
||||
};
|
||||
await db.write();
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
res.status(400).send(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Create controller for POST request to '/ontime/info'
|
||||
// Returns ACK message
|
||||
export const postInfo = async (req, res) => {
|
||||
if (!req.body) {
|
||||
res.status(400).send('No object found in request');
|
||||
return;
|
||||
}
|
||||
// TODO: validate data
|
||||
try {
|
||||
data.settings = { ...data.settings, ...req.body };
|
||||
await db.write();
|
||||
res.sendStatus(200);
|
||||
const newData = { overrideStyles: req.body.overrideStyles };
|
||||
DataProvider.setViews(newData);
|
||||
res.status(200).send(newData);
|
||||
} catch (error) {
|
||||
res.status(400).send(error);
|
||||
}
|
||||
@@ -282,25 +241,22 @@ export const postInfo = async (req, res) => {
|
||||
// Create controller for POST request to '/ontime/osc'
|
||||
// Returns -
|
||||
export const getOSC = async (req, res) => {
|
||||
// send object with network information
|
||||
res.status(200).send(data.osc);
|
||||
const osc = DataProvider.getOsc();
|
||||
res.status(200).send(osc);
|
||||
};
|
||||
|
||||
// Create controller for POST request to '/ontime/osc'
|
||||
// Returns ACK message
|
||||
export const postOSC = async (req, res) => {
|
||||
if (!req.body) {
|
||||
res.status(400).send('No object found in request');
|
||||
if (failEmptyObjects(req.body, res)) {
|
||||
return;
|
||||
}
|
||||
// TODO: validate data
|
||||
|
||||
try {
|
||||
data.osc = { ...data.osc, ...req.body };
|
||||
await db.write();
|
||||
res.sendStatus(200);
|
||||
await DataProvider.setOsc(req.body);
|
||||
res.send(req.body).status(200);
|
||||
} catch (error) {
|
||||
res.status(400).send(error);
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -313,7 +269,7 @@ export const dbUpload = async (req, res) => {
|
||||
}
|
||||
const options = req.query;
|
||||
const file = req.file.path;
|
||||
uploadAndParse(file, req, res, options);
|
||||
await uploadAndParse(file, req, res, options);
|
||||
};
|
||||
|
||||
// Create controller for POST request to '/ontime/dbpath'
|
||||
@@ -323,5 +279,5 @@ export const dbPathToUpload = async (req, res) => {
|
||||
res.status(400).send({ message: 'Path to file not found' });
|
||||
return;
|
||||
}
|
||||
uploadAndParse(req.body.path, req, res);
|
||||
await uploadAndParse(req.body.path, req, res);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { body, check, validationResult } from 'express-validator';
|
||||
|
||||
/**
|
||||
* @description Validates object for POST /ontime/views
|
||||
*/
|
||||
export const viewValidator = [
|
||||
check('overrideStyles').isBoolean().withMessage('overrideStyles value must be boolean'),
|
||||
(req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||
next();
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* @description Validates object for POST /ontime/aliases
|
||||
*/
|
||||
export const validateAliases = [
|
||||
body().isArray(),
|
||||
body('*.enabled').isBoolean(),
|
||||
body('*.alias').isString().trim(),
|
||||
body('*.pathAndParams').isString().trim(),
|
||||
(req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||
next();
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* @description Validates object for POST /ontime/userfields
|
||||
*/
|
||||
export const validateUserFields = [
|
||||
body('user0').exists().isString().trim(),
|
||||
body('user1').exists().isString().trim(),
|
||||
body('user2').exists().isString().trim(),
|
||||
body('user3').exists().isString().trim(),
|
||||
body('user4').exists().isString().trim(),
|
||||
body('user5').exists().isString().trim(),
|
||||
body('user6').exists().isString().trim(),
|
||||
body('user7').exists().isString().trim(),
|
||||
body('user8').exists().isString().trim(),
|
||||
body('user9').exists().isString().trim(),
|
||||
(req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||
next();
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* @description Validates object for POST /ontime/settings
|
||||
*/
|
||||
export const validateSettings = [
|
||||
body('pinCode').isString().isLength({ min: 0, max: 4 }).optional({ nullable: true }),
|
||||
body('timeFormat').isString().isIn(['12', '24']),
|
||||
(req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||
next();
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* @description Validates object for POST /ontime/osc
|
||||
*/
|
||||
export const validateOSC = [
|
||||
body('port').exists().isInt({ min: 0, max: 65353 }),
|
||||
body('portOut').exists().isInt({ min: 0, max: 65353 }),
|
||||
body('targetIP').exists().isIP(),
|
||||
body('enabled').exists().isBoolean(),
|
||||
(req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||
next();
|
||||
},
|
||||
];
|
||||
@@ -3,9 +3,10 @@ export const router = express.Router();
|
||||
|
||||
// import event controller
|
||||
import { getEvent, postEvent } from '../controllers/eventController.js';
|
||||
import { eventSanitizer } from '../controllers/eventController.validate.js';
|
||||
|
||||
// create route between controller and 'GET /event' endpoint
|
||||
router.get('/', getEvent);
|
||||
|
||||
// create route between controller and 'POST /event' endpoint
|
||||
router.post('/', postEvent);
|
||||
router.post('/', eventSanitizer, postEvent);
|
||||
|
||||
@@ -13,6 +13,10 @@ import {
|
||||
eventsDeleteAll,
|
||||
eventsDelete,
|
||||
} from '../controllers/eventsController.js';
|
||||
import {
|
||||
eventsPutValidator,
|
||||
paramsMustHaveEventId,
|
||||
} from '../controllers/eventsController.validate.js';
|
||||
|
||||
// create route between controller and '/events/' endpoint
|
||||
router.get('/', eventsGetAll);
|
||||
@@ -24,7 +28,7 @@ router.get('/:eventId', eventsGetById);
|
||||
router.post('/', eventsPost);
|
||||
|
||||
// create route between controller and '/events/' endpoint
|
||||
router.put('/', eventsPut);
|
||||
router.put('/', eventsPutValidator, eventsPut);
|
||||
|
||||
// create route between controller and '/events/' endpoint
|
||||
// DEPRECATED
|
||||
@@ -34,10 +38,10 @@ router.patch('/', eventsPatch);
|
||||
router.patch('/reorder/', eventsReorder);
|
||||
|
||||
// create route between controller and '/events/applydelay/:eventId' endpoint
|
||||
router.patch('/applydelay/:eventId', eventsApplyDelay);
|
||||
router.patch('/applydelay/:eventId', paramsMustHaveEventId, eventsApplyDelay);
|
||||
|
||||
// create route between controller and '/events/all' endpoint
|
||||
router.delete('/all', eventsDeleteAll);
|
||||
|
||||
// create route between controller and '/events/:eventId' endpoint
|
||||
router.delete('/:eventId', eventsDelete);
|
||||
router.delete('/:eventId', paramsMustHaveEventId, eventsDelete);
|
||||
|
||||
@@ -12,14 +12,19 @@ import {
|
||||
getViewSettings,
|
||||
poll,
|
||||
postAliases,
|
||||
postInfo,
|
||||
postOSC,
|
||||
postSettings,
|
||||
postUserFields,
|
||||
postViewSettings,
|
||||
} from '../controllers/ontimeController.js';
|
||||
|
||||
import { viewValidator } from '../validation/ontimeValidator.js';
|
||||
import {
|
||||
viewValidator,
|
||||
validateAliases,
|
||||
validateUserFields,
|
||||
validateSettings,
|
||||
validateOSC,
|
||||
} from '../controllers/ontimeController.validate.js';
|
||||
|
||||
export const router = express.Router();
|
||||
|
||||
@@ -36,7 +41,7 @@ router.post('/db', uploadFile, dbUpload);
|
||||
router.get('/settings', getSettings);
|
||||
|
||||
// create route between controller and '/ontime/settings' endpoint
|
||||
router.post('/settings', postSettings);
|
||||
router.post('/settings', validateSettings, postSettings);
|
||||
|
||||
// create route between controller and '/ontime/views' endpoint
|
||||
router.get('/views', getViewSettings);
|
||||
@@ -48,25 +53,22 @@ router.post('/views', viewValidator, postViewSettings);
|
||||
router.get('/aliases', getAliases);
|
||||
|
||||
// create route between controller and '/ontime/aliases' endpoint
|
||||
router.post('/aliases', postAliases);
|
||||
router.post('/aliases', validateAliases, postAliases);
|
||||
|
||||
// create route between controller and '/ontime/aliases' endpoint
|
||||
router.get('/userfields', getUserFields);
|
||||
|
||||
// create route between controller and '/ontime/aliases' endpoint
|
||||
router.post('/userfields', postUserFields);
|
||||
router.post('/userfields', validateUserFields, postUserFields);
|
||||
|
||||
// create route between controller and '/ontime/info' endpoint
|
||||
router.get('/info', getInfo);
|
||||
|
||||
// create route between controller and '/ontime/info' endpoint
|
||||
router.post('/info', postInfo);
|
||||
|
||||
// create route between controller and '/ontime/osc' endpoint
|
||||
router.get('/osc', getOSC);
|
||||
|
||||
// create route between controller and '/ontime/osc' endpoint
|
||||
router.post('/osc', postOSC);
|
||||
router.post('/osc', validateOSC, postOSC);
|
||||
|
||||
// create route between controller and '/ontime/dbpath' endpoint
|
||||
router.post('/dbpath', dbPathToUpload);
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { isEmptyObject, mergeObject, removeUndefined } from '../parserUtils.js';
|
||||
|
||||
describe('isEmptyObject()', () => {
|
||||
test('finds an empty object', () => {
|
||||
const isEmpty = isEmptyObject({});
|
||||
expect(isEmpty).toBe(true);
|
||||
});
|
||||
test('throws on other types', () => {
|
||||
expect(() => isEmptyObject(12)).toThrow();
|
||||
});
|
||||
test('resolves an object with methods', () => {
|
||||
const isEmpty = isEmptyObject({ test: 'yes' });
|
||||
expect(isEmpty).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergeObject()', () => {
|
||||
test('it suppresses undefined keys', () => {
|
||||
const a = {
|
||||
first: 'yes',
|
||||
second: 'yes',
|
||||
};
|
||||
const b = {
|
||||
first: undefined,
|
||||
second: 'no',
|
||||
};
|
||||
const merged = mergeObject(a, b);
|
||||
expect(merged).toStrictEqual({
|
||||
first: 'yes',
|
||||
second: 'no',
|
||||
});
|
||||
});
|
||||
test('it handles falsy values', () => {
|
||||
const a = {
|
||||
first: 'yes',
|
||||
second: 'yes',
|
||||
third: 'yes',
|
||||
};
|
||||
const b = {
|
||||
first: 0,
|
||||
second: null,
|
||||
third: '',
|
||||
};
|
||||
const merged = mergeObject(a, b);
|
||||
expect(merged).toStrictEqual({
|
||||
first: 0,
|
||||
second: null,
|
||||
third: '',
|
||||
});
|
||||
});
|
||||
test.skip('it only merges fields of the first object', () => {
|
||||
const a = {
|
||||
first: 'yes',
|
||||
second: 'yes',
|
||||
third: 'yes',
|
||||
};
|
||||
const b = {
|
||||
first: 0,
|
||||
second: null,
|
||||
third: '',
|
||||
forth: 'not-this',
|
||||
};
|
||||
const merged = mergeObject(a, b);
|
||||
expect(merged).toStrictEqual({
|
||||
first: 0,
|
||||
second: null,
|
||||
third: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeUndefined()', () => {
|
||||
test('it removes undefined keys from object', () => {
|
||||
const obj = {
|
||||
first: 'yes',
|
||||
second: undefined,
|
||||
third: 'yes',
|
||||
};
|
||||
expect(removeUndefined(obj)).toStrictEqual({
|
||||
first: 'yes',
|
||||
third: 'yes',
|
||||
});
|
||||
});
|
||||
test('it handles falsy values object', () => {
|
||||
const obj = {
|
||||
first: '',
|
||||
second: 0,
|
||||
third: 'null',
|
||||
};
|
||||
expect(removeUndefined(obj)).toStrictEqual(obj);
|
||||
});
|
||||
});
|
||||
@@ -1,19 +1,13 @@
|
||||
const adjective = [
|
||||
'abandoned',
|
||||
'able',
|
||||
'absolute',
|
||||
'adorable',
|
||||
'adventurous',
|
||||
'academic',
|
||||
'acceptable',
|
||||
'acclaimed',
|
||||
'accomplished',
|
||||
'accurate',
|
||||
'aching',
|
||||
'acidic',
|
||||
'acrobatic',
|
||||
'active',
|
||||
'actual',
|
||||
'adaptable',
|
||||
'adorable',
|
||||
'adventurous',
|
||||
'adept',
|
||||
'admirable',
|
||||
'admired',
|
||||
@@ -23,16 +17,11 @@ const adjective = [
|
||||
'advanced',
|
||||
'afraid',
|
||||
'affectionate',
|
||||
'aged',
|
||||
'aggravating',
|
||||
'aggressive',
|
||||
'agile',
|
||||
'agitated',
|
||||
'agonizing',
|
||||
'agreeable',
|
||||
'ajar',
|
||||
'alarmed',
|
||||
'alarming',
|
||||
'alert',
|
||||
'alienated',
|
||||
'alive',
|
||||
@@ -52,13 +41,11 @@ const adjective = [
|
||||
'annual',
|
||||
'another',
|
||||
'antique',
|
||||
'anxious',
|
||||
'any',
|
||||
'apprehensive',
|
||||
'appropriate',
|
||||
'apt',
|
||||
'arctic',
|
||||
'arid',
|
||||
'aromatic',
|
||||
'artistic',
|
||||
'ashamed',
|
||||
@@ -73,18 +60,12 @@ const adjective = [
|
||||
'authorized',
|
||||
'automatic',
|
||||
'avaricious',
|
||||
'average',
|
||||
'aware',
|
||||
'awesome',
|
||||
'awful',
|
||||
'awkward',
|
||||
'babyish',
|
||||
'bad',
|
||||
'back',
|
||||
'baggy',
|
||||
'bare',
|
||||
'barren',
|
||||
'basic',
|
||||
'beautiful',
|
||||
'belated',
|
||||
'beloved',
|
||||
@@ -112,7 +93,6 @@ const adjective = [
|
||||
'boiling',
|
||||
'bold',
|
||||
'bony',
|
||||
'boring',
|
||||
'bossy',
|
||||
'both',
|
||||
'bouncy',
|
||||
@@ -127,12 +107,10 @@ const adjective = [
|
||||
'broken',
|
||||
'bronze',
|
||||
'brown',
|
||||
'bruised',
|
||||
'bubbly',
|
||||
'bulky',
|
||||
'bumpy',
|
||||
'buoyant',
|
||||
'burdensome',
|
||||
'burly',
|
||||
'bustling',
|
||||
'busy',
|
||||
@@ -195,7 +173,6 @@ const adjective = [
|
||||
'cooperative',
|
||||
'coordinated',
|
||||
'corny',
|
||||
'corrupt',
|
||||
'costly',
|
||||
'courageous',
|
||||
'courteous',
|
||||
@@ -203,8 +180,6 @@ const adjective = [
|
||||
'crazy',
|
||||
'creamy',
|
||||
'creative',
|
||||
'creepy',
|
||||
'criminal',
|
||||
'crisp',
|
||||
'critical',
|
||||
'crooked',
|
||||
@@ -219,7 +194,6 @@ const adjective = [
|
||||
'curvy',
|
||||
'cute',
|
||||
'cylindrical',
|
||||
'damaged',
|
||||
'damp',
|
||||
'dangerous',
|
||||
'dapper',
|
||||
@@ -236,10 +210,8 @@ const adjective = [
|
||||
'decimal',
|
||||
'decisive',
|
||||
'deep',
|
||||
'defenseless',
|
||||
'defensive',
|
||||
'defiant',
|
||||
'deficient',
|
||||
'definite',
|
||||
'definitive',
|
||||
'delayed',
|
||||
@@ -249,7 +221,6 @@ const adjective = [
|
||||
'delirious',
|
||||
'demanding',
|
||||
'dense',
|
||||
'dental',
|
||||
'dependable',
|
||||
'dependent',
|
||||
'descriptive',
|
||||
@@ -258,29 +229,19 @@ const adjective = [
|
||||
'determined',
|
||||
'devoted',
|
||||
'different',
|
||||
'difficult',
|
||||
'digital',
|
||||
'diligent',
|
||||
'dim',
|
||||
'dimpled',
|
||||
'dimwitted',
|
||||
'direct',
|
||||
'disastrous',
|
||||
'discrete',
|
||||
'disfigured',
|
||||
'disgusting',
|
||||
'disloyal',
|
||||
'dismal',
|
||||
'distant',
|
||||
'downright',
|
||||
'dreary',
|
||||
'dirty',
|
||||
'disguised',
|
||||
'dishonest',
|
||||
'dismal',
|
||||
'distant',
|
||||
'distinct',
|
||||
'distorted',
|
||||
'dizzy',
|
||||
'dopey',
|
||||
'doting',
|
||||
@@ -293,7 +254,6 @@ const adjective = [
|
||||
'droopy',
|
||||
'dry',
|
||||
'dual',
|
||||
'dull',
|
||||
'dutiful',
|
||||
'each',
|
||||
'eager',
|
||||
@@ -474,20 +434,16 @@ const adjective = [
|
||||
'great',
|
||||
'greedy',
|
||||
'green',
|
||||
'gregarious',
|
||||
'grim',
|
||||
'grimy',
|
||||
'gripping',
|
||||
'grizzled',
|
||||
'gross',
|
||||
'grotesque',
|
||||
'grouchy',
|
||||
'grounded',
|
||||
'growing',
|
||||
'growling',
|
||||
'grown',
|
||||
'grubby',
|
||||
'gruesome',
|
||||
'grumpy',
|
||||
'guilty',
|
||||
'gullible',
|
||||
@@ -506,7 +462,6 @@ const adjective = [
|
||||
'harmonious',
|
||||
'harsh',
|
||||
'hasty',
|
||||
'hateful',
|
||||
'haunting',
|
||||
'healthy',
|
||||
'heartfelt',
|
||||
@@ -528,16 +483,13 @@ const adjective = [
|
||||
'honorable',
|
||||
'honored',
|
||||
'hopeful',
|
||||
'horrible',
|
||||
'hospitable',
|
||||
'hot',
|
||||
'huge',
|
||||
'humble',
|
||||
'humiliating',
|
||||
'humming',
|
||||
'humongous',
|
||||
'hungry',
|
||||
'hurtful',
|
||||
'husky',
|
||||
'icky',
|
||||
'icy',
|
||||
@@ -548,10 +500,7 @@ const adjective = [
|
||||
'idiotic',
|
||||
'idolized',
|
||||
'ignorant',
|
||||
'ill',
|
||||
'illegal',
|
||||
'ill-fated',
|
||||
'ill-informed',
|
||||
'illiterate',
|
||||
'illustrious',
|
||||
'imaginary',
|
||||
@@ -566,15 +515,11 @@ const adjective = [
|
||||
'imperfect',
|
||||
'imperturbable',
|
||||
'impish',
|
||||
'impolite',
|
||||
'important',
|
||||
'impossible',
|
||||
'impractical',
|
||||
'impressionable',
|
||||
'impressive',
|
||||
'improbable',
|
||||
'impure',
|
||||
'inborn',
|
||||
'incomparable',
|
||||
'incompatible',
|
||||
'incomplete',
|
||||
@@ -592,7 +537,6 @@ const adjective = [
|
||||
'innocent',
|
||||
'insecure',
|
||||
'insidious',
|
||||
'insignificant',
|
||||
'insistent',
|
||||
'instructive',
|
||||
'insubstantial',
|
||||
@@ -639,7 +583,6 @@ const adjective = [
|
||||
'known',
|
||||
'kooky',
|
||||
'kosher',
|
||||
'lame',
|
||||
'lanky',
|
||||
'large',
|
||||
'last',
|
||||
@@ -722,9 +665,7 @@ const adjective = [
|
||||
'miniature',
|
||||
'minor',
|
||||
'minty',
|
||||
'miserable',
|
||||
'miserly',
|
||||
'misguided',
|
||||
'misty',
|
||||
'mixed',
|
||||
'modern',
|
||||
@@ -777,18 +718,15 @@ const adjective = [
|
||||
'noteworthy',
|
||||
'novel',
|
||||
'noxious',
|
||||
'numb',
|
||||
'nutritious',
|
||||
'nutty',
|
||||
'obedient',
|
||||
'obese',
|
||||
'oblong',
|
||||
'oily',
|
||||
'oblong',
|
||||
'obvious',
|
||||
'occasional',
|
||||
'odd',
|
||||
'oddball',
|
||||
'offbeat',
|
||||
'offensive',
|
||||
'official',
|
||||
@@ -836,7 +774,6 @@ const adjective = [
|
||||
'personal',
|
||||
'pertinent',
|
||||
'pesky',
|
||||
'pessimistic',
|
||||
'petty',
|
||||
'phony',
|
||||
'physical',
|
||||
@@ -858,7 +795,6 @@ const adjective = [
|
||||
'pointed',
|
||||
'pointless',
|
||||
'poised',
|
||||
'poor',
|
||||
'popular',
|
||||
'portly',
|
||||
'posh',
|
||||
@@ -903,7 +839,6 @@ const adjective = [
|
||||
'quarterly',
|
||||
'queasy',
|
||||
'querulous',
|
||||
'questionable',
|
||||
'quick',
|
||||
'quick-witted',
|
||||
'quiet',
|
||||
@@ -931,7 +866,6 @@ const adjective = [
|
||||
'reliable',
|
||||
'relieved',
|
||||
'remarkable',
|
||||
'remorseful',
|
||||
'remote',
|
||||
'repentant',
|
||||
'required',
|
||||
@@ -949,7 +883,6 @@ const adjective = [
|
||||
'robust',
|
||||
'rosy',
|
||||
'rotating',
|
||||
'rotten',
|
||||
'rough',
|
||||
'round',
|
||||
'rowdy',
|
||||
@@ -963,7 +896,6 @@ const adjective = [
|
||||
'rusty',
|
||||
'sad',
|
||||
'safe',
|
||||
'salty',
|
||||
'same',
|
||||
'sandy',
|
||||
'sane',
|
||||
@@ -998,7 +930,6 @@ const adjective = [
|
||||
'shadowy',
|
||||
'shady',
|
||||
'shallow',
|
||||
'shameful',
|
||||
'shameless',
|
||||
'sharp',
|
||||
'shimmering',
|
||||
@@ -1011,7 +942,6 @@ const adjective = [
|
||||
'showy',
|
||||
'shrill',
|
||||
'shy',
|
||||
'sick',
|
||||
'silent',
|
||||
'silky',
|
||||
'silly',
|
||||
@@ -1027,7 +957,6 @@ const adjective = [
|
||||
'sleepy',
|
||||
'slight',
|
||||
'slim',
|
||||
'slimy',
|
||||
'slippery',
|
||||
'slow',
|
||||
'slushy',
|
||||
@@ -1136,7 +1065,6 @@ const adjective = [
|
||||
'tender',
|
||||
'tense',
|
||||
'tepid',
|
||||
'terrible',
|
||||
'terrific',
|
||||
'testy',
|
||||
'thankful',
|
||||
@@ -1183,7 +1111,6 @@ const adjective = [
|
||||
'tubby',
|
||||
'turbulent',
|
||||
'twin',
|
||||
'ugly',
|
||||
'ultimate',
|
||||
'unacceptable',
|
||||
'unaware',
|
||||
@@ -1198,14 +1125,12 @@ const adjective = [
|
||||
'unfolded',
|
||||
'unfortunate',
|
||||
'unhappy',
|
||||
'unhealthy',
|
||||
'uniform',
|
||||
'unimportant',
|
||||
'unique',
|
||||
'united',
|
||||
'unkempt',
|
||||
'unknown',
|
||||
'unlawful',
|
||||
'unlined',
|
||||
'unlucky',
|
||||
'unnatural',
|
||||
@@ -1235,7 +1160,6 @@ const adjective = [
|
||||
'usable',
|
||||
'used',
|
||||
'useful',
|
||||
'useless',
|
||||
'utilized',
|
||||
'utter',
|
||||
'vacant',
|
||||
@@ -1271,7 +1195,6 @@ const adjective = [
|
||||
'warmhearted',
|
||||
'warped',
|
||||
'wary',
|
||||
'wasteful',
|
||||
'watchful',
|
||||
'waterlogged',
|
||||
'watery',
|
||||
@@ -1611,7 +1534,6 @@ const object = [
|
||||
'classroom',
|
||||
'delivery',
|
||||
'device',
|
||||
'difficulty',
|
||||
'drama',
|
||||
'election',
|
||||
'engine',
|
||||
@@ -1625,7 +1547,6 @@ const object = [
|
||||
'suggestion',
|
||||
'tension',
|
||||
'variation',
|
||||
'anxiety',
|
||||
'atmosphere',
|
||||
'awareness',
|
||||
'bread',
|
||||
@@ -1761,7 +1682,6 @@ const object = [
|
||||
'drawer',
|
||||
'establishment',
|
||||
'examination',
|
||||
'garbage',
|
||||
'grocery',
|
||||
'honey',
|
||||
'impression',
|
||||
@@ -1886,7 +1806,6 @@ const object = [
|
||||
'group',
|
||||
'risk',
|
||||
'word',
|
||||
'fat',
|
||||
'force',
|
||||
'key',
|
||||
'light',
|
||||
@@ -1934,7 +1853,6 @@ const object = [
|
||||
'coast',
|
||||
'action',
|
||||
'age',
|
||||
'bad',
|
||||
'boat',
|
||||
'record',
|
||||
'result',
|
||||
@@ -2223,7 +2141,6 @@ const object = [
|
||||
'pleasure',
|
||||
'score',
|
||||
'screw',
|
||||
'sex',
|
||||
'shop',
|
||||
'shower',
|
||||
'suit',
|
||||
@@ -2416,7 +2333,6 @@ const object = [
|
||||
'counter',
|
||||
'flower',
|
||||
'grandfather',
|
||||
'harm',
|
||||
'knee',
|
||||
'lawyer',
|
||||
'leather',
|
||||
@@ -2747,7 +2663,6 @@ const object = [
|
||||
'crazy',
|
||||
'escape',
|
||||
'gather',
|
||||
'hate',
|
||||
'prior',
|
||||
'repair',
|
||||
'rough',
|
||||
@@ -2757,19 +2672,15 @@ const object = [
|
||||
'strike',
|
||||
'employ',
|
||||
'external',
|
||||
'hurt',
|
||||
'illegal',
|
||||
'laugh',
|
||||
'lay',
|
||||
'mobile',
|
||||
'nasty',
|
||||
'ordinary',
|
||||
'respond',
|
||||
'royal',
|
||||
'senior',
|
||||
'split',
|
||||
'strain',
|
||||
'struggle',
|
||||
'swim',
|
||||
'train',
|
||||
'upper',
|
||||
@@ -2808,7 +2719,6 @@ const object = [
|
||||
'prompt',
|
||||
'quiet',
|
||||
'refuse',
|
||||
'regret',
|
||||
'reveal',
|
||||
'rush',
|
||||
'shake',
|
||||
@@ -2848,9 +2758,6 @@ const object = [
|
||||
'wake',
|
||||
'wrap',
|
||||
'yesterday',
|
||||
'Thomas',
|
||||
'Tom',
|
||||
'Lieuwe',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,3 +49,39 @@ export const validateFile = (file) => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Verifies if object is empty
|
||||
* @param {object} obj
|
||||
*/
|
||||
export const isEmptyObject = (obj) => {
|
||||
if (typeof obj === 'object' && obj !== null && !Array.isArray(obj)) {
|
||||
return Object.keys(obj).length === 0;
|
||||
}
|
||||
throw new Error('Variable is not an object');
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Merges two objects, suppressing undefined keys
|
||||
* @param {object} a
|
||||
* @param {object} b
|
||||
*/
|
||||
export const mergeObject = (a, b) => {
|
||||
const merged = {};
|
||||
Object.keys({ ...a, ...b }).map((key) => {
|
||||
merged[key] = typeof b[key] === 'undefined' ? a[key] : b[key];
|
||||
});
|
||||
return merged;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Removes undefined
|
||||
* @param {object} obj
|
||||
*/
|
||||
export const removeUndefined = (obj) => {
|
||||
const patched = {};
|
||||
Object.keys({ ...obj })
|
||||
.filter((key) => typeof obj[key] !== 'undefined')
|
||||
.map((key) => (patched[key] = obj[key]));
|
||||
return patched;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { isEmptyObject } from './parserUtils.js';
|
||||
|
||||
/**
|
||||
* @description initial checks for an empty of malformed request object
|
||||
* @param obj
|
||||
* @param res
|
||||
*/
|
||||
export const failEmptyObjects = (obj, res) => {
|
||||
let failed = false;
|
||||
try {
|
||||
if (isEmptyObject(obj)) {
|
||||
res.status(400).send('No object found in request');
|
||||
failed = true;
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(400).send(error);
|
||||
failed = true;
|
||||
}
|
||||
return failed;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description initial checks for an empty of malformed request object
|
||||
* @param obj
|
||||
* @param res
|
||||
*/
|
||||
export const failIsNotArray = (obj, res) => {
|
||||
let failed = false;
|
||||
try {
|
||||
if (!Array.isArray(obj)) {
|
||||
res.status(400).send('No array found in request');
|
||||
failed = true;
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(400).send(error);
|
||||
failed = true;
|
||||
}
|
||||
return failed;
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
import { check, validationResult } from 'express-validator';
|
||||
|
||||
/**
|
||||
* @description Validates object for POST /ontime/views
|
||||
*/
|
||||
export const viewValidator = [
|
||||
check('overrideStyles').isBoolean().withMessage('overrideStyles value must be boolean'),
|
||||
(req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||
next();
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user