mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-18 21:54:09 +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;
|
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
|
* @description HTTP request to retrieve view settings
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export default function AppSettingsModal() {
|
|||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
||||||
// set context
|
// set context
|
||||||
|
// TODO: add fast-equals here and check if event settings have changed
|
||||||
saveEventSettings(formSettings);
|
saveEventSettings(formSettings);
|
||||||
const validation = { isValid: false };
|
const validation = { isValid: false };
|
||||||
|
|
||||||
@@ -83,12 +84,17 @@ export default function AppSettingsModal() {
|
|||||||
if (!validation.isValid) {
|
if (!validation.isValid) {
|
||||||
emitError(`Invalid Input: ${validation.message}`);
|
emitError(`Invalid Input: ${validation.message}`);
|
||||||
} else {
|
} else {
|
||||||
await postSettings(formData);
|
try {
|
||||||
await refetch();
|
await postSettings(formData);
|
||||||
|
} catch (error) {
|
||||||
|
emitError(`Error saving settings: ${error}`)
|
||||||
|
} finally {
|
||||||
|
await refetch();
|
||||||
|
setChanged(false);
|
||||||
|
}
|
||||||
validation?.message && emitWarning(validation.message);
|
validation?.message && emitWarning(validation.message);
|
||||||
}
|
}
|
||||||
setSubmitting(false);
|
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 { ModalBody } from '@chakra-ui/modal';
|
||||||
import { FormLabel, Input, Textarea } from '@chakra-ui/react';
|
import { FormLabel, Input, Textarea } from '@chakra-ui/react';
|
||||||
import { EVENT_TABLE } from 'common/api/apiConstants';
|
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 { useFetch } from 'common/hooks/useFetch';
|
||||||
|
|
||||||
import { eventPlaceholderSettings } from '../../common/api/ontimeApi';
|
import { eventPlaceholderSettings } from '../../common/api/ontimeApi';
|
||||||
|
import { LoggingContext } from '../../common/context/LoggingContext';
|
||||||
|
|
||||||
import { inputProps } from './modalHelper';
|
import { inputProps } from './modalHelper';
|
||||||
import SubmitContainer from './SubmitContainer';
|
import SubmitContainer from './SubmitContainer';
|
||||||
@@ -14,6 +15,7 @@ import style from './Modals.module.scss';
|
|||||||
|
|
||||||
export default function SettingsModal() {
|
export default function SettingsModal() {
|
||||||
const { data, status, refetch } = useFetch(EVENT_TABLE, fetchEvent);
|
const { data, status, refetch } = useFetch(EVENT_TABLE, fetchEvent);
|
||||||
|
const { emitError } = useContext(LoggingContext);
|
||||||
const [formData, setFormData] = useState(eventPlaceholderSettings);
|
const [formData, setFormData] = useState(eventPlaceholderSettings);
|
||||||
const [changed, setChanged] = useState(false);
|
const [changed, setChanged] = useState(false);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
@@ -42,13 +44,18 @@ export default function SettingsModal() {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
||||||
await postEvent(formData);
|
try {
|
||||||
await refetch();
|
await postEvent(formData);
|
||||||
|
} catch (error) {
|
||||||
|
emitError(`Error saving event settings: ${error}`)
|
||||||
|
} finally {
|
||||||
|
await refetch();
|
||||||
|
setChanged(false);
|
||||||
|
}
|
||||||
|
|
||||||
setChanged(false);
|
|
||||||
setSubmitting(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 { FormControl, FormLabel, Input, Switch } from '@chakra-ui/react';
|
||||||
import { FiInfo } from '@react-icons/all-files/fi/FiInfo';
|
import { FiInfo } from '@react-icons/all-files/fi/FiInfo';
|
||||||
import { APP_TABLE } from 'common/api/apiConstants';
|
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 { useFetch } from 'common/hooks/useFetch';
|
||||||
|
|
||||||
import { LoggingContext } from '../../common/context/LoggingContext';
|
import { LoggingContext } from '../../common/context/LoggingContext';
|
||||||
@@ -50,7 +50,7 @@ export default function IntegrationSettingsModal() {
|
|||||||
if (e.status) {
|
if (e.status) {
|
||||||
emitError(`Invalid Input: ${e.message}`);
|
emitError(`Invalid Input: ${e.message}`);
|
||||||
} else {
|
} else {
|
||||||
await postInfo(f);
|
// call API endpoint here with value of f
|
||||||
setChanged(false);
|
setChanged(false);
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,10 +122,14 @@ export default function OscSettingsModal() {
|
|||||||
if (e.status) {
|
if (e.status) {
|
||||||
emitError(`Invalid Input: ${e.message}`);
|
emitError(`Invalid Input: ${e.message}`);
|
||||||
} else {
|
} else {
|
||||||
// Post here
|
try {
|
||||||
await postOSC(formData);
|
await postOSC(formData);
|
||||||
await refetch();
|
} catch (error){
|
||||||
setChanged(false);
|
emitError(`Error setting OSC: ${error}`)
|
||||||
|
} finally {
|
||||||
|
await refetch();
|
||||||
|
setChanged(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setSubmitting(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 { ModalBody } from '@chakra-ui/modal';
|
||||||
import { Input } from '@chakra-ui/react';
|
import { Input } from '@chakra-ui/react';
|
||||||
import { IoInformationCircleOutline } from '@react-icons/all-files/io5/IoInformationCircleOutline';
|
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 { useFetch } from 'common/hooks/useFetch';
|
||||||
|
|
||||||
import { getUserFields, postUserFields, userFieldsPlaceholder } from '../../common/api/ontimeApi';
|
import { getUserFields, postUserFields, userFieldsPlaceholder } from '../../common/api/ontimeApi';
|
||||||
|
import { LoggingContext } from '../../common/context/LoggingContext';
|
||||||
import { handleLinks, host } from '../../common/utils/linkUtils';
|
import { handleLinks, host } from '../../common/utils/linkUtils';
|
||||||
|
|
||||||
import SubmitContainer from './SubmitContainer';
|
import SubmitContainer from './SubmitContainer';
|
||||||
@@ -14,6 +15,7 @@ import style from './Modals.module.scss';
|
|||||||
|
|
||||||
export default function TableOptionsModal() {
|
export default function TableOptionsModal() {
|
||||||
const { data, status, refetch } = useFetch(USERFIELDS, getUserFields);
|
const { data, status, refetch } = useFetch(USERFIELDS, getUserFields);
|
||||||
|
const { emitError } = useContext(LoggingContext);
|
||||||
const [changed, setChanged] = useState(false);
|
const [changed, setChanged] = useState(false);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [userFields, setUserFields] = useState(userFieldsPlaceholder);
|
const [userFields, setUserFields] = useState(userFieldsPlaceholder);
|
||||||
@@ -43,13 +45,17 @@ export default function TableOptionsModal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
await postUserFields(validatedFields);
|
try {
|
||||||
|
await postUserFields(validatedFields);
|
||||||
|
} catch (error) {
|
||||||
|
emitError(`Error saving table options: ${error}`)
|
||||||
|
}
|
||||||
await refetch();
|
await refetch();
|
||||||
setChanged(false);
|
setChanged(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
},[refetch, userFields]);
|
},[emitError, refetch, userFields]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverts local state equals to server state
|
* 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 { ModalBody } from '@chakra-ui/modal';
|
||||||
import { FormControl, FormLabel } from '@chakra-ui/react';
|
import { FormControl, FormLabel } from '@chakra-ui/react';
|
||||||
import { IoInformationCircleOutline } from '@react-icons/all-files/io5/IoInformationCircleOutline';
|
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 { VIEW_SETTINGS } from '../../common/api/apiConstants';
|
||||||
import { getView, postView, viewsPlaceholder } from '../../common/api/ontimeApi';
|
import { getView, postView, viewsPlaceholder } from '../../common/api/ontimeApi';
|
||||||
import EnableBtn from '../../common/components/buttons/EnableBtn';
|
import EnableBtn from '../../common/components/buttons/EnableBtn';
|
||||||
|
import { LoggingContext } from '../../common/context/LoggingContext';
|
||||||
import { useFetch } from '../../common/hooks/useFetch';
|
import { useFetch } from '../../common/hooks/useFetch';
|
||||||
import { openLink } from '../../common/utils/linkUtils';
|
import { openLink } from '../../common/utils/linkUtils';
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ import style from './Modals.module.scss';
|
|||||||
|
|
||||||
export default function ViewsSettingsModal() {
|
export default function ViewsSettingsModal() {
|
||||||
const { data, status, refetch } = useFetch(VIEW_SETTINGS, getView);
|
const { data, status, refetch } = useFetch(VIEW_SETTINGS, getView);
|
||||||
|
const { emitError } = useContext(LoggingContext);
|
||||||
const [formData, setFormData] = useState(viewsPlaceholder);
|
const [formData, setFormData] = useState(viewsPlaceholder);
|
||||||
const [changed, setChanged] = useState(false);
|
const [changed, setChanged] = useState(false);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
@@ -35,12 +37,17 @@ export default function ViewsSettingsModal() {
|
|||||||
async (event) => {
|
async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
await postView(formData);
|
try {
|
||||||
await refetch();
|
await postView(formData);
|
||||||
setChanged(false);
|
} catch (error) {
|
||||||
|
emitError(`Error view settings: ${error}`)
|
||||||
|
} finally{
|
||||||
|
await refetch();
|
||||||
|
setChanged(false);
|
||||||
|
}
|
||||||
setSubmitting(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,
|
Notification,
|
||||||
} = require('electron');
|
} = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const electronConfig = require('./electron.config');
|
||||||
if (process.env.NODE_ENV === undefined) {
|
if (process.env.NODE_ENV === undefined) {
|
||||||
process.env.NODE_ENV = 'production';
|
process.env.NODE_ENV = 'production';
|
||||||
}
|
}
|
||||||
const env = process.env.NODE_ENV;
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
|
||||||
let loaded = 'Nothing loaded';
|
let loaded = 'Nothing loaded';
|
||||||
let isQuitting = false;
|
let isQuitting = false;
|
||||||
|
|
||||||
const nodePath =
|
const nodePath = isProduction
|
||||||
env !== 'production'
|
? path.join('file://', __dirname, '../', 'extraResources', 'src/app.js')
|
||||||
? path.join('file://', __dirname, 'src/app.js')
|
: path.join('file://', __dirname, 'src/app.js');
|
||||||
: path.join('file://', __dirname, '../', 'extraResources', 'src/app.js');
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -144,22 +143,25 @@ app.whenReady().then(() => {
|
|||||||
// give the nodejs server some time
|
// give the nodejs server some time
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Load page served by node
|
// Load page served by node
|
||||||
const reactApp =
|
const reactApp = isProduction
|
||||||
env === 'development' ? 'http://localhost:3000/editor' : 'http://localhost:4001/editor';
|
? electronConfig.reactAppUrl.production
|
||||||
|
: electronConfig.reactAppUrl.development;
|
||||||
|
|
||||||
win.loadURL(reactApp).then(() => {
|
win.loadURL(reactApp).then(() => {
|
||||||
win.webContents.setBackgroundThrottling(false);
|
win.webContents.setBackgroundThrottling(false);
|
||||||
|
|
||||||
// window stuff
|
|
||||||
win.show();
|
win.show();
|
||||||
win.focus();
|
win.focus();
|
||||||
|
|
||||||
splash.destroy();
|
splash.destroy();
|
||||||
|
|
||||||
// tray stuff
|
if (typeof loaded === 'string') {
|
||||||
tray.setToolTip(loaded);
|
tray.setToolTip(loaded);
|
||||||
|
} else {
|
||||||
|
tray.setToolTip('Initialising error: please restart ontime');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, 2000);
|
}, electronConfig.appIni.mainWindowWait);
|
||||||
|
|
||||||
// Hide on close
|
// Hide on close
|
||||||
win.on('close', function (event) {
|
win.on('close', function (event) {
|
||||||
@@ -173,7 +175,6 @@ app.whenReady().then(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// create tray
|
// create tray
|
||||||
// TODO: Design better icon
|
|
||||||
tray = new Tray(trayIcon);
|
tray = new Tray(trayIcon);
|
||||||
|
|
||||||
// Define context menu
|
// Define context menu
|
||||||
@@ -269,7 +270,7 @@ ipcMain.on('send-to-link', (event, arg) => {
|
|||||||
|
|
||||||
// send to help URL
|
// send to help URL
|
||||||
if (arg === 'help') {
|
if (arg === 'help') {
|
||||||
shell.openExternal('https://cpvalente.gitbook.io/ontime/');
|
shell.openExternal(electronConfig.externalUrls.help);
|
||||||
} else {
|
} else {
|
||||||
shell.openExternal(arg);
|
shell.openExternal(arg);
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-15
@@ -21,9 +21,11 @@ import { router as playbackRouter } from './routes/playbackRouter.js';
|
|||||||
|
|
||||||
// Global Objects
|
// Global Objects
|
||||||
import { EventTimer } from './classes/timer/EventTimer.js';
|
import { EventTimer } from './classes/timer/EventTimer.js';
|
||||||
|
import { socketProvider } from './classes/socket/SocketController.js';
|
||||||
// Start OSC server
|
// Start OSC server
|
||||||
import { initiateOSC, shutdownOSCServer } from './controllers/OscController.js';
|
import { initiateOSC, shutdownOSCServer } from './controllers/OscController.js';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
import { DataProvider } from './classes/data-provider/DataProvider.js';
|
||||||
|
|
||||||
// get environment
|
// get environment
|
||||||
const env = process.env.NODE_ENV || 'production';
|
const env = process.env.NODE_ENV || 'production';
|
||||||
@@ -31,9 +33,11 @@ const __filename = fileURLToPath(import.meta.url);
|
|||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
export const { db, data } = await loadDb(__dirname);
|
export const { db, data } = await loadDb(__dirname);
|
||||||
|
|
||||||
console.log(`Starting ontime version ${process.env.npm_package_version}`);
|
console.log(`Starting ontime version ${process.env.npm_package_version}`);
|
||||||
|
|
||||||
|
// import socket provider
|
||||||
|
const socket = socketProvider;
|
||||||
|
|
||||||
// Create express APP
|
// Create express APP
|
||||||
const app = express();
|
const app = express();
|
||||||
app.disable('x-powered-by');
|
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 oscIP = osc?.targetIP || config.osc.targetIP;
|
||||||
const oscOutPort = osc?.portOut || config.osc.portOut;
|
const oscOutPort = osc?.portOut || config.osc.portOut;
|
||||||
const oscInPort = osc?.port || config.osc.port;
|
const oscInPort = osc?.port || config.osc.port;
|
||||||
const oscInEnabled = osc?.enabled !== undefined ? osc.enabled : config.osc.inputEnabled;
|
const oscInEnabled = osc?.enabled !== undefined ? osc.enabled : config.osc.inputEnabled;
|
||||||
|
const serverPort = settings.serverPort || config.server.port;
|
||||||
const serverPort = data.settings.serverPort || config.server.port;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @description starts OSC server
|
||||||
* @description starts OSC server
|
* @description starts OSC server
|
||||||
* @param overrideConfig
|
* @param overrideConfig
|
||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
export const startOSCServer = async (overrideConfig = null) => {
|
export const startOSCServer = async (overrideConfig = null) => {
|
||||||
if (!oscInEnabled) {
|
if (!oscInEnabled) {
|
||||||
global.timer.info('RX', 'OSC Input Disabled');
|
socket.info('RX', 'OSC Input Disabled');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +109,7 @@ export const startOSCServer = async (overrideConfig = null) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Start OSC Server
|
// Start OSC Server
|
||||||
global.timer.info('RX', `Starting OSC Server on port: ${oscInPort}`);
|
socket.info('RX', `Starting OSC Server on port: ${oscInPort}`);
|
||||||
initiateOSC(oscSettings);
|
initiateOSC(oscSettings);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -119,11 +123,16 @@ const server = http.createServer(app);
|
|||||||
*/
|
*/
|
||||||
export const startServer = async (overrideConfig = null) => {
|
export const startServer = async (overrideConfig = null) => {
|
||||||
const port = 4001; // port hardcoded
|
const port = 4001; // port hardcoded
|
||||||
|
const { events, http } = DataProvider.getData();
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
const returnMessage = `Ontime is listening on port ${port}`;
|
const returnMessage = `Ontime is listening on port ${port}`;
|
||||||
server.listen(port, '0.0.0.0');
|
server.listen(port, '0.0.0.0');
|
||||||
|
|
||||||
|
// init socket controller
|
||||||
|
await socket.initServer(server);
|
||||||
|
socket.info('SERVER', 'Socket initialised');
|
||||||
|
|
||||||
// OSC Config
|
// OSC Config
|
||||||
const oscConfig = {
|
const oscConfig = {
|
||||||
ip: oscIP,
|
ip: oscIP,
|
||||||
@@ -131,9 +140,11 @@ export const startServer = async (overrideConfig = null) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// init timer
|
// init timer
|
||||||
global.timer = new EventTimer(server, config.timer, oscConfig, data.http);
|
global.timer = new EventTimer(socket, config.timer, oscConfig, http);
|
||||||
global.timer.setupWithEventList(data.events);
|
global.timer.setupWithEventList(events);
|
||||||
global.timer.info('SERVER', returnMessage);
|
|
||||||
|
socket.info('SERVER', returnMessage);
|
||||||
|
socket.startListener();
|
||||||
return returnMessage;
|
return returnMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -146,16 +157,14 @@ export const shutdown = async () => {
|
|||||||
// shutdown express server
|
// shutdown express server
|
||||||
server.close();
|
server.close();
|
||||||
|
|
||||||
// shutdown OSC Server
|
|
||||||
shutdownOSCServer();
|
shutdownOSCServer();
|
||||||
|
|
||||||
// shutdown timer
|
|
||||||
global.timer.shutdown();
|
global.timer.shutdown();
|
||||||
|
socket.shutdown();
|
||||||
};
|
};
|
||||||
|
|
||||||
// register shutdown signals
|
// register shutdown signals
|
||||||
process.once('SIGHUP', shutdown)
|
process.once('SIGHUP', shutdown);
|
||||||
process.once('SIGINT', shutdown)
|
process.once('SIGINT', shutdown);
|
||||||
process.once('SIGTERM', shutdown)
|
process.once('SIGTERM', shutdown);
|
||||||
|
|
||||||
export { server, app };
|
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 {
|
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
|
* Merges two data objects
|
||||||
* @param {object} existing
|
* @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 { Timer } from './Timer.js';
|
||||||
import { Server } from 'socket.io';
|
|
||||||
import { DAY_TO_MS, getSelectionByRoll, replacePlaceholder, updateRoll } from './classUtils.js';
|
import { DAY_TO_MS, getSelectionByRoll, replacePlaceholder, updateRoll } from './classUtils.js';
|
||||||
import { OSCIntegration } from './integrations/Osc.js';
|
import { OSCIntegration } from './integrations/Osc.js';
|
||||||
import { HTTPIntegration } from './integrations/Http.js';
|
import { HTTPIntegration } from './integrations/Http.js';
|
||||||
import { cleanURL } from '../../utils/url.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
|
* Class EventTimer adds functions specific to APP
|
||||||
@@ -16,12 +12,12 @@ import { stringFromMillis } from '../../utils/time.js';
|
|||||||
export class EventTimer extends Timer {
|
export class EventTimer extends Timer {
|
||||||
/**
|
/**
|
||||||
* Instantiates an event timer object
|
* Instantiates an event timer object
|
||||||
* @param {object} httpServer
|
* @param {object} socket
|
||||||
* @param {object} timerConfig
|
* @param {object} timerConfig
|
||||||
* @param {object} [oscConfig]
|
* @param {object} [oscConfig]
|
||||||
* @param {object} [httpConfig]
|
* @param {object} [httpConfig]
|
||||||
*/
|
*/
|
||||||
constructor(httpServer, timerConfig, oscConfig, httpConfig) {
|
constructor(socket, timerConfig, oscConfig, httpConfig) {
|
||||||
// call super constructor
|
// call super constructor
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -42,13 +38,15 @@ export class EventTimer extends Timer {
|
|||||||
this.ontimeCycle = 'idle';
|
this.ontimeCycle = 'idle';
|
||||||
this.prevCycle = null;
|
this.prevCycle = null;
|
||||||
|
|
||||||
|
// Socket Object
|
||||||
|
this.socket = socket;
|
||||||
|
|
||||||
// OSC Object
|
// OSC Object
|
||||||
this.osc = null;
|
this.osc = null;
|
||||||
|
|
||||||
// HTTP Client Object
|
// HTTP Client Object
|
||||||
this.http = null;
|
this.http = null;
|
||||||
|
|
||||||
this._numClients = 0;
|
|
||||||
this._interval = null;
|
this._interval = null;
|
||||||
|
|
||||||
this.presenter = {
|
this.presenter = {
|
||||||
@@ -69,26 +67,9 @@ export class EventTimer extends Timer {
|
|||||||
|
|
||||||
this._eventlist = [];
|
this._eventlist = [];
|
||||||
this.onAir = false;
|
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
|
// set recurrent emits
|
||||||
this._interval = setInterval(() => this.runCycle(), timerConfig?.refresh || 1000);
|
this._interval = setInterval(() => this.runCycle(), timerConfig?.refresh || 1000);
|
||||||
|
|
||||||
// listen to new connections
|
|
||||||
this._listenToConnections();
|
|
||||||
|
|
||||||
if (oscConfig != null) {
|
if (oscConfig != null) {
|
||||||
this._initOscClient(oscConfig);
|
this._initOscClient(oscConfig);
|
||||||
}
|
}
|
||||||
@@ -103,17 +84,12 @@ export class EventTimer extends Timer {
|
|||||||
*/
|
*/
|
||||||
shutdown() {
|
shutdown() {
|
||||||
clearInterval(this._interval);
|
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) {
|
if (this.osc != null) {
|
||||||
this.info('TX', '... Closing OSC Client');
|
this.socket.info('TX', '... Closing OSC Client');
|
||||||
this.osc.shutdown();
|
this.osc.shutdown();
|
||||||
}
|
}
|
||||||
if (this.http != null) {
|
if (this.http != null) {
|
||||||
this.info('TX', '... Closing HTTP Client');
|
this.socket.info('TX', '... Closing HTTP Client');
|
||||||
this.http.shutdown();
|
this.http.shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,7 +102,7 @@ export class EventTimer extends Timer {
|
|||||||
_initOscClient(oscConfig) {
|
_initOscClient(oscConfig) {
|
||||||
this.osc = new OSCIntegration();
|
this.osc = new OSCIntegration();
|
||||||
const r = this.osc.init(oscConfig);
|
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
|
* @private
|
||||||
*/
|
*/
|
||||||
_initHTTPClient(httpConfig) {
|
_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 = new HTTPIntegration();
|
||||||
this.http.init(httpConfig);
|
this.http.init(httpConfig);
|
||||||
this.httpMessages = httpConfig.messages;
|
this.httpMessages = httpConfig.messages;
|
||||||
@@ -146,7 +122,7 @@ export class EventTimer extends Timer {
|
|||||||
*/
|
*/
|
||||||
broadcastTimer() {
|
broadcastTimer() {
|
||||||
// through websockets
|
// 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(),
|
expectedFinish: this._getExpectedFinish(),
|
||||||
startedAt: this._startedAt,
|
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,
|
selectedEventId: this.selectedEventId,
|
||||||
nextEventId: this.nextEventId,
|
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,
|
lower: this.lower,
|
||||||
onAir: this.onAir,
|
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,
|
selectedEventId: this.selectedEventId,
|
||||||
numEvents: this._eventlist.length,
|
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,
|
selectedEventIndex: this.selectedEventIndex,
|
||||||
numEvents: this._eventlist.length,
|
numEvents: this._eventlist.length,
|
||||||
};
|
};
|
||||||
this.io.emit('ontime-feat-info', featureData);
|
this.socket.send('ontime-feat-info', featureData);
|
||||||
}
|
}
|
||||||
|
|
||||||
_broadcastFeatureCuesheet() {
|
_broadcastFeatureCuesheet() {
|
||||||
@@ -227,7 +203,7 @@ export class EventTimer extends Timer {
|
|||||||
numEvents: this._eventlist.length,
|
numEvents: this._eventlist.length,
|
||||||
titleNow: this.titles.titleNow,
|
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;
|
const numEvents = this._eventlist.length;
|
||||||
this.broadcastTimer();
|
this.broadcastTimer();
|
||||||
this.io.emit('playstate', this.state);
|
this.socket.send('playstate', this.state);
|
||||||
this.io.emit('selected', {
|
this.socket.send('selected', {
|
||||||
id: this.selectedEventId,
|
id: this.selectedEventId,
|
||||||
index: this.selectedEventIndex,
|
index: this.selectedEventIndex,
|
||||||
total: numEvents,
|
total: numEvents,
|
||||||
});
|
});
|
||||||
this.io.emit('selected-id', this.selectedEventId);
|
this.socket.send('selected-id', this.selectedEventId);
|
||||||
this.io.emit('next-id', this.nextEventId);
|
this.socket.send('next-id', this.nextEventId);
|
||||||
this.io.emit('numevents', numEvents);
|
this.socket.send('numevents', numEvents);
|
||||||
this.io.emit('publicselected-id', this.selectedPublicEventId);
|
this.socket.send('publicselected-id', this.selectedPublicEventId);
|
||||||
this.io.emit('publicnext-id', this.nextPublicEventId);
|
this.socket.send('publicnext-id', this.nextPublicEventId);
|
||||||
this.io.emit('titles', this.titles);
|
this.socket.send('titles', this.titles);
|
||||||
this.io.emit('publictitles', this.titlesPublic);
|
this.socket.send('publictitles', this.titlesPublic);
|
||||||
this.io.emit('onAir', this.onAir);
|
this.socket.send('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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -282,7 +249,7 @@ export class EventTimer extends Timer {
|
|||||||
case 'start': {
|
case 'start': {
|
||||||
if (!numEvents) return false;
|
if (!numEvents) return false;
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
this.info('PLAYBACK', 'Play Mode Start');
|
this.socket.info('PLAYBACK', 'Play Mode Start');
|
||||||
this.start();
|
this.start();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -290,8 +257,8 @@ export class EventTimer extends Timer {
|
|||||||
if (!numEvents) return false;
|
if (!numEvents) return false;
|
||||||
const loaded = this.loadEventById(payload);
|
const loaded = this.loadEventById(payload);
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
this.info('PLAYBACK', `Loaded event with ID ${payload}`);
|
this.socket.info('PLAYBACK', `Loaded event with ID ${payload}`);
|
||||||
this.info('PLAYBACK', 'Play Mode Start');
|
this.socket.info('PLAYBACK', 'Play Mode Start');
|
||||||
this.start();
|
this.start();
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@@ -302,8 +269,8 @@ export class EventTimer extends Timer {
|
|||||||
if (!numEvents) return false;
|
if (!numEvents) return false;
|
||||||
const loaded = this.loadEventByIndex(payload);
|
const loaded = this.loadEventByIndex(payload);
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
this.info('PLAYBACK', `Loaded event with index ${payload}`);
|
this.socket.info('PLAYBACK', `Loaded event with index ${payload}`);
|
||||||
this.info('PLAYBACK', 'Play Mode Start');
|
this.socket.info('PLAYBACK', 'Play Mode Start');
|
||||||
this.start();
|
this.start();
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@@ -313,35 +280,35 @@ export class EventTimer extends Timer {
|
|||||||
case 'pause': {
|
case 'pause': {
|
||||||
if (!numEvents) return false;
|
if (!numEvents) return false;
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
this.info('PLAYBACK', 'Play Mode Pause');
|
this.socket.info('PLAYBACK', 'Play Mode Pause');
|
||||||
this.pause();
|
this.pause();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'stop': {
|
case 'stop': {
|
||||||
if (!numEvents) return false;
|
if (!numEvents) return false;
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
this.info('PLAYBACK', 'Play Mode Stop');
|
this.socket.info('PLAYBACK', 'Play Mode Stop');
|
||||||
this.stop();
|
this.stop();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'roll': {
|
case 'roll': {
|
||||||
if (!numEvents) return false;
|
if (!numEvents) return false;
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
this.info('PLAYBACK', 'Play Mode Roll');
|
this.socket.info('PLAYBACK', 'Play Mode Roll');
|
||||||
this.roll();
|
this.roll();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'previous': {
|
case 'previous': {
|
||||||
if (!numEvents) return false;
|
if (!numEvents) return false;
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
this.info('PLAYBACK', 'Play Mode Previous');
|
this.socket.info('PLAYBACK', 'Play Mode Previous');
|
||||||
this.previous();
|
this.previous();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'next': {
|
case 'next': {
|
||||||
if (!numEvents) return false;
|
if (!numEvents) return false;
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
this.info('PLAYBACK', 'Play Mode Next');
|
this.socket.info('PLAYBACK', 'Play Mode Next');
|
||||||
this.next();
|
this.next();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -349,7 +316,7 @@ export class EventTimer extends Timer {
|
|||||||
if (!numEvents) return false;
|
if (!numEvents) return false;
|
||||||
const loaded = this.loadEventById(payload);
|
const loaded = this.loadEventById(payload);
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
this.info('PLAYBACK', `Loaded event with ID ${payload}`);
|
this.socket.info('PLAYBACK', `Loaded event with ID ${payload}`);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -359,7 +326,7 @@ export class EventTimer extends Timer {
|
|||||||
if (!numEvents) return false;
|
if (!numEvents) return false;
|
||||||
const loaded = this.loadEventByIndex(payload);
|
const loaded = this.loadEventByIndex(payload);
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
this.info('PLAYBACK', `Loaded event with index ${payload}`);
|
this.socket.info('PLAYBACK', `Loaded event with index ${payload}`);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -368,32 +335,32 @@ export class EventTimer extends Timer {
|
|||||||
case 'unload': {
|
case 'unload': {
|
||||||
if (!numEvents) return false;
|
if (!numEvents) return false;
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
this.info('PLAYBACK', 'Events unloaded');
|
this.socket.info('PLAYBACK', 'Events unloaded');
|
||||||
this.unload();
|
this.unload();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'reload': {
|
case 'reload': {
|
||||||
if (!numEvents) return false;
|
if (!numEvents) return false;
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
this.info('PLAYBACK', 'Reloaded event');
|
this.socket.info('PLAYBACK', 'Reloaded event');
|
||||||
this.reload();
|
this.reload();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'onAir': {
|
case 'onAir': {
|
||||||
// Call action
|
// Call action
|
||||||
this.info('PLAYBACK', 'Going On Air');
|
this.socket.info('PLAYBACK', 'Going On Air');
|
||||||
this.setonAir(true);
|
this.setonAir(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'offAir': {
|
case 'offAir': {
|
||||||
// Call action and force update
|
// Call action and force update
|
||||||
this.info('PLAYBACK', 'Going Off Air');
|
this.socket.info('PLAYBACK', 'Going Off Air');
|
||||||
this.setonAir(false);
|
this.setonAir(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
// Error, disable flag
|
// Error, disable flag
|
||||||
this.error('RX', `Unhandled action triggered ${action}`);
|
this.socket.error('RX', `Unhandled action triggered ${action}`);
|
||||||
success = false;
|
success = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -518,7 +485,7 @@ export class EventTimer extends Timer {
|
|||||||
this.ontimeCycle = this.cycleState.onUpdate;
|
this.ontimeCycle = this.cycleState.onUpdate;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.error('SERVER', `Unhandled cycle: ${this.ontimeCycle}`);
|
this.socket.error('SERVER', `Unhandled cycle: ${this.ontimeCycle}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// send http message if any
|
// send http message if any
|
||||||
@@ -546,13 +513,13 @@ export class EventTimer extends Timer {
|
|||||||
|
|
||||||
update() {
|
update() {
|
||||||
// if there is nothing selected, update clock
|
// if there is nothing selected, update clock
|
||||||
this.clock = this._getCurrentTime();
|
this.clock = Timer.getCurrentTime();
|
||||||
this._broadcastFeatureTimer();
|
this._broadcastFeatureTimer();
|
||||||
this.broadcastTimer();
|
this.broadcastTimer();
|
||||||
|
|
||||||
// if we are not updating, send the timers
|
// if we are not updating, send the timers
|
||||||
if (this.ontimeCycle !== this.cycleState.onUpdate) {
|
if (this.ontimeCycle !== this.cycleState.onUpdate) {
|
||||||
this.io.emit('timer', this.getTimeObject());
|
this.socket.send('timer', this.getTimeObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Have we skipped onStart?
|
// Have we skipped onStart?
|
||||||
@@ -616,33 +583,33 @@ export class EventTimer extends Timer {
|
|||||||
// Presenter message
|
// Presenter message
|
||||||
case 'set-timer-text':
|
case 'set-timer-text':
|
||||||
this.presenter.text = payload;
|
this.presenter.text = payload;
|
||||||
this.broadcastThis('messages-timer', this.presenter);
|
this.socket.send('messages-timer', this.presenter);
|
||||||
break;
|
break;
|
||||||
case 'set-timer-visible':
|
case 'set-timer-visible':
|
||||||
this.presenter.visible = payload;
|
this.presenter.visible = payload;
|
||||||
this.broadcastThis('messages-timer', this.presenter);
|
this.socket.send('messages-timer', this.presenter);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/*******************************************/
|
/*******************************************/
|
||||||
// Public message
|
// Public message
|
||||||
case 'set-public-text':
|
case 'set-public-text':
|
||||||
this.public.text = payload;
|
this.public.text = payload;
|
||||||
this.broadcastThis('messages-public', this.public);
|
this.socket.send('messages-public', this.public);
|
||||||
break;
|
break;
|
||||||
case 'set-public-visible':
|
case 'set-public-visible':
|
||||||
this.public.visible = payload;
|
this.public.visible = payload;
|
||||||
this.broadcastThis('messages-public', this.public);
|
this.socket.send('messages-public', this.public);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/*******************************************/
|
/*******************************************/
|
||||||
// Lower third message
|
// Lower third message
|
||||||
case 'set-lower-text':
|
case 'set-lower-text':
|
||||||
this.lower.text = payload;
|
this.lower.text = payload;
|
||||||
this.broadcastThis('messages-lower', this.lower);
|
this.socket.send('messages-lower', this.lower);
|
||||||
break;
|
break;
|
||||||
case 'set-lower-visible':
|
case 'set-lower-visible':
|
||||||
this.lower.visible = payload;
|
this.lower.visible = payload;
|
||||||
this.broadcastThis('messages-lower', this.lower);
|
this.socket.send('messages-lower', this.lower);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
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
|
* Deletes running event list from object
|
||||||
*/
|
*/
|
||||||
@@ -944,7 +631,7 @@ export class EventTimer extends Timer {
|
|||||||
this.ontimeCycle = this.cycleState.onStop;
|
this.ontimeCycle = this.cycleState.onStop;
|
||||||
|
|
||||||
// update clients
|
// 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
|
// find object in events
|
||||||
const eventIndex = this._eventlist.findIndex((e) => e.id === id);
|
const eventIndex = this._eventlist.findIndex((e) => e.id === id);
|
||||||
if (eventIndex === -1) {
|
if (eventIndex === -1) {
|
||||||
throw 'Event not found';
|
throw new Error('Event not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if event is set to be skipped
|
// check if event is set to be skipped
|
||||||
@@ -1068,7 +755,7 @@ export class EventTimer extends Timer {
|
|||||||
this._loadTitlesNow();
|
this._loadTitlesNow();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.error('SERVER', error);
|
this.socket.error('SERVER', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// update clients
|
// update clients
|
||||||
@@ -1117,7 +804,7 @@ export class EventTimer extends Timer {
|
|||||||
this._loadTitlesNow();
|
this._loadTitlesNow();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.error('SERVER', error);
|
this.socket.error('SERVER', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// update clients
|
// update clients
|
||||||
@@ -1228,7 +915,7 @@ export class EventTimer extends Timer {
|
|||||||
this.selectedEventIndex = eventIndex;
|
this.selectedEventIndex = eventIndex;
|
||||||
this.selectedEventId = e.id;
|
this.selectedEventId = e.id;
|
||||||
} else if (type === 'reload') {
|
} else if (type === 'reload') {
|
||||||
const now = this._getCurrentTime();
|
const now = Timer.getCurrentTime();
|
||||||
const elapsed = this.getElapsed();
|
const elapsed = this.getElapsed();
|
||||||
|
|
||||||
this.duration = end - start;
|
this.duration = end - start;
|
||||||
@@ -1441,7 +1128,7 @@ export class EventTimer extends Timer {
|
|||||||
setonAir(onAir) {
|
setonAir(onAir) {
|
||||||
this.onAir = onAir;
|
this.onAir = onAir;
|
||||||
// broadcast change
|
// 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
|
* @description Look for current event considering local clock
|
||||||
*/
|
*/
|
||||||
rollLoad() {
|
rollLoad() {
|
||||||
const now = this._getCurrentTime();
|
const now = Timer.getCurrentTime();
|
||||||
const prevLoaded = this.selectedEventId;
|
const prevLoaded = this.selectedEventId;
|
||||||
|
|
||||||
// maybe roll has already been loaded
|
// maybe roll has already been loaded
|
||||||
@@ -1524,7 +1211,7 @@ export class EventTimer extends Timer {
|
|||||||
// nothing to play, unload
|
// nothing to play, unload
|
||||||
if (nowIndex === null && nextIndex === null) {
|
if (nowIndex === null && nextIndex === null) {
|
||||||
this.unload();
|
this.unload();
|
||||||
this.warning('SERVER', 'Roll: no events found');
|
this.socket.warning('SERVER', 'Roll: no events found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1551,7 +1238,7 @@ export class EventTimer extends Timer {
|
|||||||
if (nowIndex === null) {
|
if (nowIndex === null) {
|
||||||
// only warn the first time
|
// only warn the first time
|
||||||
if (this.secondaryTimer === null) {
|
if (this.secondaryTimer === null) {
|
||||||
this.info('SERVER', 'Roll: waiting for event start');
|
this.socket.info('SERVER', 'Roll: waiting for event start');
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset running timer
|
// 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
|
* Integrations
|
||||||
* -------------
|
* -------------
|
||||||
@@ -1765,20 +1388,20 @@ export class EventTimer extends Timer {
|
|||||||
async sendOsc(message, payload) {
|
async sendOsc(message, payload) {
|
||||||
const reply = await this.osc.send(message, payload);
|
const reply = await this.osc.send(message, payload);
|
||||||
if (!reply.success) {
|
if (!reply.success) {
|
||||||
this.error('TX', reply.message);
|
this.socket.error('TX', reply.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds sync object
|
* @description Builds sync object
|
||||||
* @returns {{running: number, timer: (null|string|*), presenter: null, playback: string, clock: null, title: null}}
|
|
||||||
*/
|
*/
|
||||||
poll() {
|
poll() {
|
||||||
return {
|
return {
|
||||||
clock: this.clock,
|
currentId: this.selectedEventId,
|
||||||
running: Timer.toSeconds(this.current),
|
|
||||||
timer: this.timeTag,
|
timer: this.timeTag,
|
||||||
|
clock: this.clock,
|
||||||
playback: this.state,
|
playback: this.state,
|
||||||
|
currentColour: null,
|
||||||
title: this.titles.titleNow,
|
title: this.titles.titleNow,
|
||||||
presenter: this.titles.presenterNow,
|
presenter: this.titles.presenterNow,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,39 +11,12 @@ export class Timer {
|
|||||||
this.state = 'stop';
|
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
|
* @description updates the running timer
|
||||||
*/
|
*/
|
||||||
update() {
|
update() {
|
||||||
// get current time
|
// get current time
|
||||||
const now = this._getCurrentTime();
|
const now = Timer.getCurrentTime();
|
||||||
this.clock = now;
|
this.clock = now;
|
||||||
let checkFinish = false;
|
let checkFinish = false;
|
||||||
|
|
||||||
@@ -106,7 +79,7 @@ export class Timer {
|
|||||||
* @return {number}
|
* @return {number}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_getCurrentTime() {
|
static getCurrentTime() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
// extract milliseconds since midnight
|
// 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
|
// playback
|
||||||
/**
|
/**
|
||||||
* @description start current time
|
* @description start current time
|
||||||
@@ -197,7 +160,7 @@ export class Timer {
|
|||||||
if (this.state === 'start') return;
|
if (this.state === 'start') return;
|
||||||
else if (this._startedAt == null) {
|
else if (this._startedAt == null) {
|
||||||
// it hasn't started yet
|
// it hasn't started yet
|
||||||
const now = this._getCurrentTime();
|
const now = Timer.getCurrentTime();
|
||||||
// set start time as now
|
// set start time as now
|
||||||
this._startedAt = now;
|
this._startedAt = now;
|
||||||
// calculate expected finish time
|
// calculate expected finish time
|
||||||
@@ -230,7 +193,7 @@ export class Timer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set pause time
|
// set pause time
|
||||||
this._pausedAt = this._getCurrentTime();
|
this._pausedAt = Timer.getCurrentTime();
|
||||||
|
|
||||||
// change state
|
// change state
|
||||||
this.state = 'pause';
|
this.state = 'pause';
|
||||||
@@ -259,7 +222,7 @@ export class Timer {
|
|||||||
|
|
||||||
if (amount < 0 && Math.abs(amount) > this.current) {
|
if (amount < 0 && Math.abs(amount) > this.current) {
|
||||||
// if we will make the clock negative
|
// 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) {
|
} else if (this.current < 0 && this.current + amount > 0) {
|
||||||
// clock will go from negative to positive
|
// clock will go from negative to positive
|
||||||
this._finishedAt = null;
|
this._finishedAt = null;
|
||||||
|
|||||||
@@ -1,24 +1,28 @@
|
|||||||
// get database
|
import { removeUndefined } from '../utils/parserUtils.js';
|
||||||
import { db, data } from '../app.js';
|
import { failEmptyObjects } from '../utils/routerUtils.js';
|
||||||
|
import { DataProvider } from '../classes/data-provider/DataProvider.js';
|
||||||
|
|
||||||
// Create controller for GET request to 'event'
|
// Create controller for GET request to 'event'
|
||||||
// Returns ACK message
|
|
||||||
export const getEvent = async (req, res) => {
|
export const getEvent = async (req, res) => {
|
||||||
res.json(data.event);
|
res.json(DataProvider.getEventData());
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for POST request to 'event'
|
// Create controller for POST request to 'event'
|
||||||
// Returns ACK message
|
|
||||||
export const postEvent = async (req, res) => {
|
export const postEvent = async (req, res) => {
|
||||||
if (!req.body) {
|
if (failEmptyObjects(req.body, res)) {
|
||||||
res.status(400).send('No object found in request');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: validate data
|
|
||||||
try {
|
try {
|
||||||
data.event = { ...data.event, ...req.body };
|
const newEvent = removeUndefined({
|
||||||
await db.write();
|
title: req.body?.title,
|
||||||
res.sendStatus(200);
|
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) {
|
} catch (error) {
|
||||||
res.status(400).send(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 {
|
import {
|
||||||
block as blockDef,
|
block as blockDef,
|
||||||
delay as delayDef,
|
delay as delayDef,
|
||||||
@@ -10,87 +6,36 @@ import {
|
|||||||
import { generateId } from '../utils/generate_id.js';
|
import { generateId } from '../utils/generate_id.js';
|
||||||
import { MAX_EVENTS } from '../settings.js';
|
import { MAX_EVENTS } from '../settings.js';
|
||||||
import { getPreviousPlayable } from '../utils/eventUtils.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) {
|
async function _insertAndSync(newEvent) {
|
||||||
if (newEvent.order) {
|
if (newEvent.order) {
|
||||||
const events = data.events;
|
const events = DataProvider.getEvents();
|
||||||
await _insertAt(newEvent, newEvent.order);
|
await DataProvider.insertEventAt(newEvent, newEvent.order);
|
||||||
const previousId = events?.[newEvent.order - 1]?.id;
|
const previousId = events?.[newEvent.order - 1]?.id;
|
||||||
_insertEventInTimerAfterId(newEvent, previousId);
|
_insertEventInTimerAfterId(newEvent, previousId);
|
||||||
} else if (newEvent.after) {
|
} else if (newEvent.after) {
|
||||||
await _insertAfterId(newEvent, newEvent.after);
|
await DataProvider.insertEventAfterId(newEvent, newEvent.after);
|
||||||
_insertEventInTimerAfterId(newEvent, newEvent.after);
|
_insertEventInTimerAfterId(newEvent, newEvent.after);
|
||||||
} else {
|
} else {
|
||||||
await _insertAt(newEvent, 0);
|
await DataProvider.insertEventAt(newEvent, 0);
|
||||||
_insertEventInTimerAfterId(newEvent);
|
_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
|
* @description returns all events of type event
|
||||||
* @return {unknown[]}
|
* @return {unknown[]}
|
||||||
*/
|
*/
|
||||||
function getEventEvents() {
|
function getEventEvents() {
|
||||||
// return data.events.filter((e) => e.type === 'event');
|
// 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
|
// Updates timer object
|
||||||
@@ -112,7 +57,7 @@ function _insertEventInTimerAfterId(event, previousId) {
|
|||||||
try {
|
try {
|
||||||
global.timer.insertEventAfterId(event, previousId);
|
global.timer.insertEventAfterId(event, previousId);
|
||||||
} catch (error) {
|
} 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'
|
// Create controller for GET request to '/events'
|
||||||
// Returns -
|
// Returns -
|
||||||
export const eventsGetAll = async (req, res) => {
|
export const eventsGetAll = async (req, res) => {
|
||||||
res.json(data.events);
|
res.json(DataProvider.getEvents());
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for GET request to '/events/:eventId'
|
// Create controller for GET request to '/events/:eventId'
|
||||||
@@ -146,7 +91,7 @@ export const eventsGetById = async (req, res) => {
|
|||||||
if (id == null) {
|
if (id == null) {
|
||||||
res.status(400).send(`No eventId found in request`);
|
res.status(400).send(`No eventId found in request`);
|
||||||
} else {
|
} else {
|
||||||
const event = data.events.find((e) => e.id === id);
|
const event = DataProvider.getEventById(id);
|
||||||
res.json(event);
|
res.json(event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -154,13 +99,12 @@ export const eventsGetById = async (req, res) => {
|
|||||||
// Create controller for POST request to '/events/'
|
// Create controller for POST request to '/events/'
|
||||||
// Returns -
|
// Returns -
|
||||||
export const eventsPost = async (req, res) => {
|
export const eventsPost = async (req, res) => {
|
||||||
// TODO: Validate event
|
if (failEmptyObjects(req.body, res)) {
|
||||||
if (!req.body) {
|
|
||||||
res.status(400).send(`No object found in request`);
|
|
||||||
return;
|
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`;
|
const error = `ERROR: Reached limit number of ${MAX_EVENTS} events`;
|
||||||
res.status(400).send(error);
|
res.status(400).send(error);
|
||||||
return;
|
return;
|
||||||
@@ -187,7 +131,7 @@ export const eventsPost = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_insertAndSync(newEvent);
|
await _insertAndSync(newEvent);
|
||||||
res.sendStatus(201);
|
res.sendStatus(201);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(400).send(error);
|
res.status(400).send(error);
|
||||||
@@ -197,41 +141,33 @@ export const eventsPost = async (req, res) => {
|
|||||||
// Create controller for PUT request to '/events/'
|
// Create controller for PUT request to '/events/'
|
||||||
// Returns -
|
// Returns -
|
||||||
export const eventsPut = async (req, res) => {
|
export const eventsPut = async (req, res) => {
|
||||||
// no valid params
|
if (failEmptyObjects(req.body, res)) {
|
||||||
if (!req.body) {
|
|
||||||
res.status(400).send(`No object found`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventId = req.body.id;
|
const eventDataFromRequest = req.body;
|
||||||
if (!eventId) {
|
const eventId = eventDataFromRequest.id;
|
||||||
res.status(400).send(`Object malformed: id missing`);
|
const event = DataProvider.getEventById(eventId);
|
||||||
return;
|
if (typeof event === 'undefined') {
|
||||||
}
|
res.status(400).send(`No event with ID found`);
|
||||||
|
|
||||||
const eventIndex = data.events.findIndex((e) => e.id === eventId);
|
|
||||||
if (eventIndex === -1) {
|
|
||||||
res.status(400).send(`No Id found found`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const e = data.events[eventIndex];
|
const newData = DataProvider.updateEventById(eventId, eventDataFromRequest);
|
||||||
data.events[eventIndex] = { ...e, ...req.body };
|
|
||||||
data.events[eventIndex].revision++;
|
|
||||||
await db.write();
|
|
||||||
|
|
||||||
if (data.events[eventIndex].skip) {
|
if (newData.skip) {
|
||||||
_deleteTimerId(eventId);
|
_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
|
// event id might already not exist
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
_updateTimersSingle(eventId, req.body);
|
_updateTimersSingle(newData.id, eventDataFromRequest);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error === 'Event not found') {
|
if (error === 'Event not found') {
|
||||||
const { id: previousId } = getPreviousPlayable(data.events, e.id);
|
const events = DataProvider.getEvents();
|
||||||
_insertEventInTimerAfterId(data.events[eventIndex], previousId);
|
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) => {
|
export const eventsReorder = async (req, res) => {
|
||||||
// TODO: Validate event
|
if (failEmptyObjects(req.body, res)) {
|
||||||
if (!req.body) {
|
|
||||||
res.status(400).send(`No object found in request`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { index, from, to } = req.body;
|
const { index, from, to } = req.body;
|
||||||
|
|
||||||
// get events
|
// get events
|
||||||
const events = data.events;
|
const events = DataProvider.getEvents();
|
||||||
const idx = events.findIndex((e) => e.id === index, from);
|
const idx = events.findIndex((e) => e.id === index, from);
|
||||||
|
|
||||||
// Check if item is at given index
|
// Check if item is at given index
|
||||||
@@ -276,8 +210,7 @@ export const eventsReorder = async (req, res) => {
|
|||||||
events.splice(to, 0, reorderedItem);
|
events.splice(to, 0, reorderedItem);
|
||||||
|
|
||||||
// save events
|
// save events
|
||||||
data.events = events;
|
await DataProvider.setEventData(events);
|
||||||
await db.write();
|
|
||||||
|
|
||||||
// update timer
|
// update timer
|
||||||
_updateTimers();
|
_updateTimers();
|
||||||
@@ -291,15 +224,9 @@ export const eventsReorder = async (req, res) => {
|
|||||||
// Create controller for PATCH request to '/events/applydelay/:eventId'
|
// Create controller for PATCH request to '/events/applydelay/:eventId'
|
||||||
// Returns -
|
// Returns -
|
||||||
export const eventsApplyDelay = async (req, res) => {
|
export const eventsApplyDelay = async (req, res) => {
|
||||||
// no valid params
|
|
||||||
if (!req.params.eventId) {
|
|
||||||
res.status(400).send(`No id found in request`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// get events
|
// get events
|
||||||
const events = data.events;
|
const events = DataProvider.getEvents();
|
||||||
|
|
||||||
// AUX
|
// AUX
|
||||||
let delayIndex = null;
|
let delayIndex = null;
|
||||||
@@ -340,8 +267,7 @@ export const eventsApplyDelay = async (req, res) => {
|
|||||||
if (blockIndex) events.splice(blockIndex - 1, 1);
|
if (blockIndex) events.splice(blockIndex - 1, 1);
|
||||||
|
|
||||||
// update events
|
// update events
|
||||||
data.events = events;
|
await DataProvider.setEvents(events);
|
||||||
await db.write();
|
|
||||||
|
|
||||||
// update timer
|
// update timer
|
||||||
_updateTimers();
|
_updateTimers();
|
||||||
@@ -355,20 +281,15 @@ export const eventsApplyDelay = async (req, res) => {
|
|||||||
// Create controller for DELETE request to '/events/:eventId'
|
// Create controller for DELETE request to '/events/:eventId'
|
||||||
// Returns -
|
// Returns -
|
||||||
export const eventsDelete = async (req, res) => {
|
export const eventsDelete = async (req, res) => {
|
||||||
// no valid params
|
|
||||||
if (!req.params.eventId) {
|
|
||||||
res.status(400).send(`No id found in request`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const eventId = req.params.eventId;
|
||||||
|
|
||||||
// remove new event
|
// remove new event
|
||||||
await _removeById(req.params.eventId);
|
await DataProvider.deleteEvent(eventId);
|
||||||
|
|
||||||
// update timer
|
// update timer
|
||||||
_deleteTimerId(req.params.eventId);
|
_deleteTimerId(eventId);
|
||||||
|
|
||||||
res.sendStatus(201);
|
res.sendStatus(204);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(400).send(error);
|
res.status(400).send(error);
|
||||||
}
|
}
|
||||||
@@ -378,14 +299,9 @@ export const eventsDelete = async (req, res) => {
|
|||||||
// Returns -
|
// Returns -
|
||||||
export const eventsDeleteAll = async (req, res) => {
|
export const eventsDeleteAll = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
// set with nothing
|
await DataProvider.deleteAllEvents();
|
||||||
data.events = [];
|
|
||||||
await db.write();
|
|
||||||
|
|
||||||
// update timer object
|
|
||||||
global.timer.clearEventList();
|
global.timer.clearEventList();
|
||||||
|
res.sendStatus(204);
|
||||||
res.sendStatus(201);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(400).send(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 fs from 'fs';
|
||||||
import { data, db } from '../app.js';
|
|
||||||
import { networkInterfaces } from 'os';
|
import { networkInterfaces } from 'os';
|
||||||
import { fileHandler } from '../utils/parser.js';
|
import { fileHandler } from '../utils/parser.js';
|
||||||
import { generateId } from '../utils/generate_id.js';
|
import { generateId } from '../utils/generate_id.js';
|
||||||
import { resolveDbPath } from '../modules/loadDb.js';
|
import { resolveDbPath } from '../modules/loadDb.js';
|
||||||
import { DataProvider } from '../classes/data-provider/DataProvider.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'
|
// Create controller for GET request to '/ontime/poll'
|
||||||
// Returns data for current state
|
// Returns data for current state
|
||||||
@@ -22,7 +23,8 @@ export const poll = async (req, res) => {
|
|||||||
// Create controller for GET request to '/ontime/db'
|
// Create controller for GET request to '/ontime/db'
|
||||||
// Returns -
|
// Returns -
|
||||||
export const dbDownload = async (req, res) => {
|
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();
|
const dbInDisk = resolveDbPath();
|
||||||
|
|
||||||
res.download(dbInDisk, `${fileTitle}.json`, (err) => {
|
res.download(dbInDisk, `${fileTitle}.json`, (err) => {
|
||||||
@@ -48,18 +50,13 @@ const uploadAndParse = async (file, req, res, options) => {
|
|||||||
} else if (result.message === 'success') {
|
} else if (result.message === 'success') {
|
||||||
// explicitly write objects
|
// explicitly write objects
|
||||||
if (typeof result !== 'undefined') {
|
if (typeof result !== 'undefined') {
|
||||||
if (!options.onlyEvents) {
|
const newEvents = result.data.events || [];
|
||||||
const mergedData = DataProvider.safeMerge(data, result.data);
|
if (options.onlyEvents) {
|
||||||
data.event = mergedData.event;
|
await DataProvider.setEvents(newEvents);
|
||||||
data.settings = mergedData.settings;
|
} else {
|
||||||
data.osc = mergedData.osc;
|
await DataProvider.mergeIntoData(result.data);
|
||||||
data.http = mergedData.http;
|
|
||||||
data.aliases = mergedData.aliases;
|
|
||||||
data.userFields = mergedData.userFields;
|
|
||||||
}
|
}
|
||||||
data.events = result.data.events || [];
|
global.timer.setupWithEventList(newEvents);
|
||||||
global.timer.setupWithEventList(result.data.events || []);
|
|
||||||
await db.write();
|
|
||||||
}
|
}
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} else {
|
} 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}
|
* @returns {array} - Array of objects {name: ip}
|
||||||
*/
|
*/
|
||||||
const getNetworkInterfaces = () => {
|
const getNetworkInterfaces = () => {
|
||||||
@@ -96,15 +93,8 @@ const getNetworkInterfaces = () => {
|
|||||||
// Create controller for POST request to '/ontime/info'
|
// Create controller for POST request to '/ontime/info'
|
||||||
// Returns -
|
// Returns -
|
||||||
export const getInfo = async (req, res) => {
|
export const getInfo = async (req, res) => {
|
||||||
const version = data.settings.version;
|
const { version, serverPort } = DataProvider.getSettings();
|
||||||
const serverPort = data.settings.serverPort;
|
const osc = DataProvider.getOsc();
|
||||||
|
|
||||||
const osc = {
|
|
||||||
port: data.osc.port,
|
|
||||||
portOut: data.osc.portOut,
|
|
||||||
targetIP: data.osc.targetIP,
|
|
||||||
enabled: data.osc.enabled,
|
|
||||||
};
|
|
||||||
|
|
||||||
// get nif and inject localhost
|
// get nif and inject localhost
|
||||||
const ni = getNetworkInterfaces();
|
const ni = getNetworkInterfaces();
|
||||||
@@ -122,18 +112,16 @@ export const getInfo = async (req, res) => {
|
|||||||
// Create controller for POST request to '/ontime/aliases'
|
// Create controller for POST request to '/ontime/aliases'
|
||||||
// Returns -
|
// Returns -
|
||||||
export const getAliases = async (req, res) => {
|
export const getAliases = async (req, res) => {
|
||||||
// send aliases array
|
const aliases = DataProvider.getAliases();
|
||||||
res.status(200).send(data.aliases);
|
res.status(200).send(aliases);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for POST request to '/ontime/aliases'
|
// Create controller for POST request to '/ontime/aliases'
|
||||||
// Returns ACK message
|
// Returns ACK message
|
||||||
export const postAliases = async (req, res) => {
|
export const postAliases = async (req, res) => {
|
||||||
if (!req.body) {
|
if (failIsNotArray()) {
|
||||||
res.status(400).send('No object found in request');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: validate data
|
|
||||||
try {
|
try {
|
||||||
const newAliases = [];
|
const newAliases = [];
|
||||||
req.body.forEach((a) => {
|
req.body.forEach((a) => {
|
||||||
@@ -144,9 +132,8 @@ export const postAliases = async (req, res) => {
|
|||||||
pathAndParams: a.pathAndParams,
|
pathAndParams: a.pathAndParams,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
data.aliases = newAliases;
|
await DataProvider.setAliases(newAliases);
|
||||||
await db.write();
|
res.status(200).send(newAliases);
|
||||||
res.sendStatus(200);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(400).send(error);
|
res.status(400).send(error);
|
||||||
}
|
}
|
||||||
@@ -155,27 +142,21 @@ export const postAliases = async (req, res) => {
|
|||||||
// Create controller for GET request to '/ontime/userfields'
|
// Create controller for GET request to '/ontime/userfields'
|
||||||
// Returns -
|
// Returns -
|
||||||
export const getUserFields = async (req, res) => {
|
export const getUserFields = async (req, res) => {
|
||||||
// send userFields array
|
const userFields = DataProvider.getUserFields();
|
||||||
res.status(200).send(data.userFields);
|
res.status(200).send(userFields);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for POST request to '/ontime/userfields'
|
// Create controller for POST request to '/ontime/userfields'
|
||||||
// Returns ACK message
|
// Returns ACK message
|
||||||
export const postUserFields = async (req, res) => {
|
export const postUserFields = async (req, res) => {
|
||||||
if (!req.body) {
|
if (failEmptyObjects(req.body, res)) {
|
||||||
res.status(400).send('No object found in request');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const newUserFields = { ...data.userFields };
|
const persistedData = DataProvider.getUserFields();
|
||||||
for (const field in newUserFields) {
|
const newData = mergeObject(persistedData, req.body);
|
||||||
if (typeof req.body[field] !== 'undefined') {
|
await DataProvider.setUserFields(newData);
|
||||||
newUserFields[field] = req.body[field];
|
res.status(200).send(newData);
|
||||||
}
|
|
||||||
}
|
|
||||||
data.userFields = newUserFields;
|
|
||||||
await db.write();
|
|
||||||
res.sendStatus(200);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(400).send(error);
|
res.status(400).send(error);
|
||||||
}
|
}
|
||||||
@@ -184,12 +165,8 @@ export const postUserFields = async (req, res) => {
|
|||||||
// Create controller for POST request to '/ontime/settings'
|
// Create controller for POST request to '/ontime/settings'
|
||||||
// Returns -
|
// Returns -
|
||||||
export const getSettings = async (req, res) => {
|
export const getSettings = async (req, res) => {
|
||||||
const version = data.settings.version;
|
const { version, serverPort, pinCode, timeFormat } = DataProvider.getSettings();
|
||||||
const serverPort = data.settings.serverPort;
|
|
||||||
const pinCode = data.settings.pinCode;
|
|
||||||
const timeFormat = data.settings.timeFormat;
|
|
||||||
|
|
||||||
// send object with network information
|
|
||||||
res.status(200).send({
|
res.status(200).send({
|
||||||
version,
|
version,
|
||||||
serverPort,
|
serverPort,
|
||||||
@@ -201,12 +178,12 @@ export const getSettings = async (req, res) => {
|
|||||||
// Create controller for POST request to '/ontime/settings'
|
// Create controller for POST request to '/ontime/settings'
|
||||||
// Returns ACK message
|
// Returns ACK message
|
||||||
export const postSettings = async (req, res) => {
|
export const postSettings = async (req, res) => {
|
||||||
if (!req.body) {
|
if (failEmptyObjects(req.body, res)) {
|
||||||
res.status(400).send('No object found in request');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
let pin = data.settings.pinCode;
|
const settings = DataProvider.getSettings();
|
||||||
|
let pin = settings.pinCode;
|
||||||
if (typeof req.body?.pinCode === 'string') {
|
if (typeof req.body?.pinCode === 'string') {
|
||||||
if (req.body?.pinCode.length === 0) {
|
if (req.body?.pinCode.length === 0) {
|
||||||
pin = null;
|
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 (typeof req.body?.timeFormat === 'string') {
|
||||||
if (req.body?.timeFormat === '12' || req.body?.timeFormat === '24') {
|
if (req.body?.timeFormat === '12' || req.body?.timeFormat === '24') {
|
||||||
timeFormat = req.body.timeFormat;
|
format = req.body.timeFormat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.settings = {
|
const newData = {
|
||||||
...data.settings,
|
...settings,
|
||||||
pinCode: pin,
|
pinCode: pin,
|
||||||
timeFormat: timeFormat,
|
timeFormat: format,
|
||||||
};
|
};
|
||||||
await db.write();
|
DataProvider.setSettings(newData);
|
||||||
res.sendStatus(200);
|
res.status(200).send(newData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(400).send(error);
|
res.status(400).send(error);
|
||||||
}
|
}
|
||||||
@@ -239,7 +216,8 @@ export const postSettings = async (req, res) => {
|
|||||||
* @method GET
|
* @method GET
|
||||||
*/
|
*/
|
||||||
export const getViewSettings = async (req, res) => {
|
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
|
* @method POST
|
||||||
*/
|
*/
|
||||||
export const postViewSettings = async (req, res) => {
|
export const postViewSettings = async (req, res) => {
|
||||||
if (!req.body) {
|
if (failEmptyObjects(req.body, res)) {
|
||||||
res.status(400).send('No object found in request');
|
|
||||||
return;
|
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 {
|
try {
|
||||||
data.settings = { ...data.settings, ...req.body };
|
const newData = { overrideStyles: req.body.overrideStyles };
|
||||||
await db.write();
|
DataProvider.setViews(newData);
|
||||||
res.sendStatus(200);
|
res.status(200).send(newData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(400).send(error);
|
res.status(400).send(error);
|
||||||
}
|
}
|
||||||
@@ -282,25 +241,22 @@ export const postInfo = async (req, res) => {
|
|||||||
// Create controller for POST request to '/ontime/osc'
|
// Create controller for POST request to '/ontime/osc'
|
||||||
// Returns -
|
// Returns -
|
||||||
export const getOSC = async (req, res) => {
|
export const getOSC = async (req, res) => {
|
||||||
// send object with network information
|
const osc = DataProvider.getOsc();
|
||||||
res.status(200).send(data.osc);
|
res.status(200).send(osc);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for POST request to '/ontime/osc'
|
// Create controller for POST request to '/ontime/osc'
|
||||||
// Returns ACK message
|
// Returns ACK message
|
||||||
export const postOSC = async (req, res) => {
|
export const postOSC = async (req, res) => {
|
||||||
if (!req.body) {
|
if (failEmptyObjects(req.body, res)) {
|
||||||
res.status(400).send('No object found in request');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: validate data
|
|
||||||
try {
|
try {
|
||||||
data.osc = { ...data.osc, ...req.body };
|
await DataProvider.setOsc(req.body);
|
||||||
await db.write();
|
res.send(req.body).status(200);
|
||||||
res.sendStatus(200);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(400).send(error);
|
res.status(400).send(error);
|
||||||
console.log(error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -313,7 +269,7 @@ export const dbUpload = async (req, res) => {
|
|||||||
}
|
}
|
||||||
const options = req.query;
|
const options = req.query;
|
||||||
const file = req.file.path;
|
const file = req.file.path;
|
||||||
uploadAndParse(file, req, res, options);
|
await uploadAndParse(file, req, res, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create controller for POST request to '/ontime/dbpath'
|
// 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' });
|
res.status(400).send({ message: 'Path to file not found' });
|
||||||
return;
|
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 event controller
|
||||||
import { getEvent, postEvent } from '../controllers/eventController.js';
|
import { getEvent, postEvent } from '../controllers/eventController.js';
|
||||||
|
import { eventSanitizer } from '../controllers/eventController.validate.js';
|
||||||
|
|
||||||
// create route between controller and 'GET /event' endpoint
|
// create route between controller and 'GET /event' endpoint
|
||||||
router.get('/', getEvent);
|
router.get('/', getEvent);
|
||||||
|
|
||||||
// create route between controller and 'POST /event' endpoint
|
// create route between controller and 'POST /event' endpoint
|
||||||
router.post('/', postEvent);
|
router.post('/', eventSanitizer, postEvent);
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ import {
|
|||||||
eventsDeleteAll,
|
eventsDeleteAll,
|
||||||
eventsDelete,
|
eventsDelete,
|
||||||
} from '../controllers/eventsController.js';
|
} from '../controllers/eventsController.js';
|
||||||
|
import {
|
||||||
|
eventsPutValidator,
|
||||||
|
paramsMustHaveEventId,
|
||||||
|
} from '../controllers/eventsController.validate.js';
|
||||||
|
|
||||||
// create route between controller and '/events/' endpoint
|
// create route between controller and '/events/' endpoint
|
||||||
router.get('/', eventsGetAll);
|
router.get('/', eventsGetAll);
|
||||||
@@ -24,7 +28,7 @@ router.get('/:eventId', eventsGetById);
|
|||||||
router.post('/', eventsPost);
|
router.post('/', eventsPost);
|
||||||
|
|
||||||
// create route between controller and '/events/' endpoint
|
// create route between controller and '/events/' endpoint
|
||||||
router.put('/', eventsPut);
|
router.put('/', eventsPutValidator, eventsPut);
|
||||||
|
|
||||||
// create route between controller and '/events/' endpoint
|
// create route between controller and '/events/' endpoint
|
||||||
// DEPRECATED
|
// DEPRECATED
|
||||||
@@ -34,10 +38,10 @@ router.patch('/', eventsPatch);
|
|||||||
router.patch('/reorder/', eventsReorder);
|
router.patch('/reorder/', eventsReorder);
|
||||||
|
|
||||||
// create route between controller and '/events/applydelay/:eventId' endpoint
|
// 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
|
// create route between controller and '/events/all' endpoint
|
||||||
router.delete('/all', eventsDeleteAll);
|
router.delete('/all', eventsDeleteAll);
|
||||||
|
|
||||||
// create route between controller and '/events/:eventId' endpoint
|
// create route between controller and '/events/:eventId' endpoint
|
||||||
router.delete('/:eventId', eventsDelete);
|
router.delete('/:eventId', paramsMustHaveEventId, eventsDelete);
|
||||||
|
|||||||
@@ -12,14 +12,19 @@ import {
|
|||||||
getViewSettings,
|
getViewSettings,
|
||||||
poll,
|
poll,
|
||||||
postAliases,
|
postAliases,
|
||||||
postInfo,
|
|
||||||
postOSC,
|
postOSC,
|
||||||
postSettings,
|
postSettings,
|
||||||
postUserFields,
|
postUserFields,
|
||||||
postViewSettings,
|
postViewSettings,
|
||||||
} from '../controllers/ontimeController.js';
|
} 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();
|
export const router = express.Router();
|
||||||
|
|
||||||
@@ -36,7 +41,7 @@ router.post('/db', uploadFile, dbUpload);
|
|||||||
router.get('/settings', getSettings);
|
router.get('/settings', getSettings);
|
||||||
|
|
||||||
// create route between controller and '/ontime/settings' endpoint
|
// 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
|
// create route between controller and '/ontime/views' endpoint
|
||||||
router.get('/views', getViewSettings);
|
router.get('/views', getViewSettings);
|
||||||
@@ -48,25 +53,22 @@ router.post('/views', viewValidator, postViewSettings);
|
|||||||
router.get('/aliases', getAliases);
|
router.get('/aliases', getAliases);
|
||||||
|
|
||||||
// create route between controller and '/ontime/aliases' endpoint
|
// 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
|
// create route between controller and '/ontime/aliases' endpoint
|
||||||
router.get('/userfields', getUserFields);
|
router.get('/userfields', getUserFields);
|
||||||
|
|
||||||
// create route between controller and '/ontime/aliases' endpoint
|
// 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
|
// create route between controller and '/ontime/info' endpoint
|
||||||
router.get('/info', getInfo);
|
router.get('/info', getInfo);
|
||||||
|
|
||||||
// create route between controller and '/ontime/info' endpoint
|
|
||||||
router.post('/info', postInfo);
|
|
||||||
|
|
||||||
// create route between controller and '/ontime/osc' endpoint
|
// create route between controller and '/ontime/osc' endpoint
|
||||||
router.get('/osc', getOSC);
|
router.get('/osc', getOSC);
|
||||||
|
|
||||||
// create route between controller and '/ontime/osc' endpoint
|
// 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
|
// create route between controller and '/ontime/dbpath' endpoint
|
||||||
router.post('/dbpath', dbPathToUpload);
|
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 = [
|
const adjective = [
|
||||||
'abandoned',
|
|
||||||
'able',
|
|
||||||
'absolute',
|
|
||||||
'adorable',
|
|
||||||
'adventurous',
|
|
||||||
'academic',
|
'academic',
|
||||||
'acceptable',
|
|
||||||
'acclaimed',
|
'acclaimed',
|
||||||
'accomplished',
|
'accomplished',
|
||||||
'accurate',
|
'accurate',
|
||||||
'aching',
|
|
||||||
'acidic',
|
|
||||||
'acrobatic',
|
'acrobatic',
|
||||||
'active',
|
'active',
|
||||||
'actual',
|
'adaptable',
|
||||||
|
'adorable',
|
||||||
|
'adventurous',
|
||||||
'adept',
|
'adept',
|
||||||
'admirable',
|
'admirable',
|
||||||
'admired',
|
'admired',
|
||||||
@@ -23,16 +17,11 @@ const adjective = [
|
|||||||
'advanced',
|
'advanced',
|
||||||
'afraid',
|
'afraid',
|
||||||
'affectionate',
|
'affectionate',
|
||||||
'aged',
|
|
||||||
'aggravating',
|
|
||||||
'aggressive',
|
|
||||||
'agile',
|
'agile',
|
||||||
'agitated',
|
'agitated',
|
||||||
'agonizing',
|
|
||||||
'agreeable',
|
'agreeable',
|
||||||
'ajar',
|
'ajar',
|
||||||
'alarmed',
|
'alarmed',
|
||||||
'alarming',
|
|
||||||
'alert',
|
'alert',
|
||||||
'alienated',
|
'alienated',
|
||||||
'alive',
|
'alive',
|
||||||
@@ -52,13 +41,11 @@ const adjective = [
|
|||||||
'annual',
|
'annual',
|
||||||
'another',
|
'another',
|
||||||
'antique',
|
'antique',
|
||||||
'anxious',
|
|
||||||
'any',
|
'any',
|
||||||
'apprehensive',
|
'apprehensive',
|
||||||
'appropriate',
|
'appropriate',
|
||||||
'apt',
|
'apt',
|
||||||
'arctic',
|
'arctic',
|
||||||
'arid',
|
|
||||||
'aromatic',
|
'aromatic',
|
||||||
'artistic',
|
'artistic',
|
||||||
'ashamed',
|
'ashamed',
|
||||||
@@ -73,18 +60,12 @@ const adjective = [
|
|||||||
'authorized',
|
'authorized',
|
||||||
'automatic',
|
'automatic',
|
||||||
'avaricious',
|
'avaricious',
|
||||||
'average',
|
|
||||||
'aware',
|
'aware',
|
||||||
'awesome',
|
'awesome',
|
||||||
'awful',
|
|
||||||
'awkward',
|
|
||||||
'babyish',
|
|
||||||
'bad',
|
'bad',
|
||||||
'back',
|
'back',
|
||||||
'baggy',
|
|
||||||
'bare',
|
'bare',
|
||||||
'barren',
|
'barren',
|
||||||
'basic',
|
|
||||||
'beautiful',
|
'beautiful',
|
||||||
'belated',
|
'belated',
|
||||||
'beloved',
|
'beloved',
|
||||||
@@ -112,7 +93,6 @@ const adjective = [
|
|||||||
'boiling',
|
'boiling',
|
||||||
'bold',
|
'bold',
|
||||||
'bony',
|
'bony',
|
||||||
'boring',
|
|
||||||
'bossy',
|
'bossy',
|
||||||
'both',
|
'both',
|
||||||
'bouncy',
|
'bouncy',
|
||||||
@@ -127,12 +107,10 @@ const adjective = [
|
|||||||
'broken',
|
'broken',
|
||||||
'bronze',
|
'bronze',
|
||||||
'brown',
|
'brown',
|
||||||
'bruised',
|
|
||||||
'bubbly',
|
'bubbly',
|
||||||
'bulky',
|
'bulky',
|
||||||
'bumpy',
|
'bumpy',
|
||||||
'buoyant',
|
'buoyant',
|
||||||
'burdensome',
|
|
||||||
'burly',
|
'burly',
|
||||||
'bustling',
|
'bustling',
|
||||||
'busy',
|
'busy',
|
||||||
@@ -195,7 +173,6 @@ const adjective = [
|
|||||||
'cooperative',
|
'cooperative',
|
||||||
'coordinated',
|
'coordinated',
|
||||||
'corny',
|
'corny',
|
||||||
'corrupt',
|
|
||||||
'costly',
|
'costly',
|
||||||
'courageous',
|
'courageous',
|
||||||
'courteous',
|
'courteous',
|
||||||
@@ -203,8 +180,6 @@ const adjective = [
|
|||||||
'crazy',
|
'crazy',
|
||||||
'creamy',
|
'creamy',
|
||||||
'creative',
|
'creative',
|
||||||
'creepy',
|
|
||||||
'criminal',
|
|
||||||
'crisp',
|
'crisp',
|
||||||
'critical',
|
'critical',
|
||||||
'crooked',
|
'crooked',
|
||||||
@@ -219,7 +194,6 @@ const adjective = [
|
|||||||
'curvy',
|
'curvy',
|
||||||
'cute',
|
'cute',
|
||||||
'cylindrical',
|
'cylindrical',
|
||||||
'damaged',
|
|
||||||
'damp',
|
'damp',
|
||||||
'dangerous',
|
'dangerous',
|
||||||
'dapper',
|
'dapper',
|
||||||
@@ -236,10 +210,8 @@ const adjective = [
|
|||||||
'decimal',
|
'decimal',
|
||||||
'decisive',
|
'decisive',
|
||||||
'deep',
|
'deep',
|
||||||
'defenseless',
|
|
||||||
'defensive',
|
'defensive',
|
||||||
'defiant',
|
'defiant',
|
||||||
'deficient',
|
|
||||||
'definite',
|
'definite',
|
||||||
'definitive',
|
'definitive',
|
||||||
'delayed',
|
'delayed',
|
||||||
@@ -249,7 +221,6 @@ const adjective = [
|
|||||||
'delirious',
|
'delirious',
|
||||||
'demanding',
|
'demanding',
|
||||||
'dense',
|
'dense',
|
||||||
'dental',
|
|
||||||
'dependable',
|
'dependable',
|
||||||
'dependent',
|
'dependent',
|
||||||
'descriptive',
|
'descriptive',
|
||||||
@@ -258,29 +229,19 @@ const adjective = [
|
|||||||
'determined',
|
'determined',
|
||||||
'devoted',
|
'devoted',
|
||||||
'different',
|
'different',
|
||||||
'difficult',
|
|
||||||
'digital',
|
'digital',
|
||||||
'diligent',
|
'diligent',
|
||||||
'dim',
|
'dim',
|
||||||
'dimpled',
|
'dimpled',
|
||||||
'dimwitted',
|
|
||||||
'direct',
|
|
||||||
'disastrous',
|
|
||||||
'discrete',
|
'discrete',
|
||||||
'disfigured',
|
|
||||||
'disgusting',
|
|
||||||
'disloyal',
|
|
||||||
'dismal',
|
'dismal',
|
||||||
'distant',
|
'distant',
|
||||||
'downright',
|
'downright',
|
||||||
'dreary',
|
'dreary',
|
||||||
'dirty',
|
|
||||||
'disguised',
|
'disguised',
|
||||||
'dishonest',
|
|
||||||
'dismal',
|
'dismal',
|
||||||
'distant',
|
'distant',
|
||||||
'distinct',
|
'distinct',
|
||||||
'distorted',
|
|
||||||
'dizzy',
|
'dizzy',
|
||||||
'dopey',
|
'dopey',
|
||||||
'doting',
|
'doting',
|
||||||
@@ -293,7 +254,6 @@ const adjective = [
|
|||||||
'droopy',
|
'droopy',
|
||||||
'dry',
|
'dry',
|
||||||
'dual',
|
'dual',
|
||||||
'dull',
|
|
||||||
'dutiful',
|
'dutiful',
|
||||||
'each',
|
'each',
|
||||||
'eager',
|
'eager',
|
||||||
@@ -474,20 +434,16 @@ const adjective = [
|
|||||||
'great',
|
'great',
|
||||||
'greedy',
|
'greedy',
|
||||||
'green',
|
'green',
|
||||||
'gregarious',
|
|
||||||
'grim',
|
'grim',
|
||||||
'grimy',
|
'grimy',
|
||||||
'gripping',
|
'gripping',
|
||||||
'grizzled',
|
'grizzled',
|
||||||
'gross',
|
|
||||||
'grotesque',
|
|
||||||
'grouchy',
|
'grouchy',
|
||||||
'grounded',
|
'grounded',
|
||||||
'growing',
|
'growing',
|
||||||
'growling',
|
'growling',
|
||||||
'grown',
|
'grown',
|
||||||
'grubby',
|
'grubby',
|
||||||
'gruesome',
|
|
||||||
'grumpy',
|
'grumpy',
|
||||||
'guilty',
|
'guilty',
|
||||||
'gullible',
|
'gullible',
|
||||||
@@ -506,7 +462,6 @@ const adjective = [
|
|||||||
'harmonious',
|
'harmonious',
|
||||||
'harsh',
|
'harsh',
|
||||||
'hasty',
|
'hasty',
|
||||||
'hateful',
|
|
||||||
'haunting',
|
'haunting',
|
||||||
'healthy',
|
'healthy',
|
||||||
'heartfelt',
|
'heartfelt',
|
||||||
@@ -528,16 +483,13 @@ const adjective = [
|
|||||||
'honorable',
|
'honorable',
|
||||||
'honored',
|
'honored',
|
||||||
'hopeful',
|
'hopeful',
|
||||||
'horrible',
|
|
||||||
'hospitable',
|
'hospitable',
|
||||||
'hot',
|
'hot',
|
||||||
'huge',
|
'huge',
|
||||||
'humble',
|
'humble',
|
||||||
'humiliating',
|
|
||||||
'humming',
|
'humming',
|
||||||
'humongous',
|
'humongous',
|
||||||
'hungry',
|
'hungry',
|
||||||
'hurtful',
|
|
||||||
'husky',
|
'husky',
|
||||||
'icky',
|
'icky',
|
||||||
'icy',
|
'icy',
|
||||||
@@ -548,10 +500,7 @@ const adjective = [
|
|||||||
'idiotic',
|
'idiotic',
|
||||||
'idolized',
|
'idolized',
|
||||||
'ignorant',
|
'ignorant',
|
||||||
'ill',
|
|
||||||
'illegal',
|
'illegal',
|
||||||
'ill-fated',
|
|
||||||
'ill-informed',
|
|
||||||
'illiterate',
|
'illiterate',
|
||||||
'illustrious',
|
'illustrious',
|
||||||
'imaginary',
|
'imaginary',
|
||||||
@@ -566,15 +515,11 @@ const adjective = [
|
|||||||
'imperfect',
|
'imperfect',
|
||||||
'imperturbable',
|
'imperturbable',
|
||||||
'impish',
|
'impish',
|
||||||
'impolite',
|
|
||||||
'important',
|
'important',
|
||||||
'impossible',
|
'impossible',
|
||||||
'impractical',
|
|
||||||
'impressionable',
|
'impressionable',
|
||||||
'impressive',
|
'impressive',
|
||||||
'improbable',
|
'improbable',
|
||||||
'impure',
|
|
||||||
'inborn',
|
|
||||||
'incomparable',
|
'incomparable',
|
||||||
'incompatible',
|
'incompatible',
|
||||||
'incomplete',
|
'incomplete',
|
||||||
@@ -592,7 +537,6 @@ const adjective = [
|
|||||||
'innocent',
|
'innocent',
|
||||||
'insecure',
|
'insecure',
|
||||||
'insidious',
|
'insidious',
|
||||||
'insignificant',
|
|
||||||
'insistent',
|
'insistent',
|
||||||
'instructive',
|
'instructive',
|
||||||
'insubstantial',
|
'insubstantial',
|
||||||
@@ -639,7 +583,6 @@ const adjective = [
|
|||||||
'known',
|
'known',
|
||||||
'kooky',
|
'kooky',
|
||||||
'kosher',
|
'kosher',
|
||||||
'lame',
|
|
||||||
'lanky',
|
'lanky',
|
||||||
'large',
|
'large',
|
||||||
'last',
|
'last',
|
||||||
@@ -722,9 +665,7 @@ const adjective = [
|
|||||||
'miniature',
|
'miniature',
|
||||||
'minor',
|
'minor',
|
||||||
'minty',
|
'minty',
|
||||||
'miserable',
|
|
||||||
'miserly',
|
'miserly',
|
||||||
'misguided',
|
|
||||||
'misty',
|
'misty',
|
||||||
'mixed',
|
'mixed',
|
||||||
'modern',
|
'modern',
|
||||||
@@ -777,18 +718,15 @@ const adjective = [
|
|||||||
'noteworthy',
|
'noteworthy',
|
||||||
'novel',
|
'novel',
|
||||||
'noxious',
|
'noxious',
|
||||||
'numb',
|
|
||||||
'nutritious',
|
'nutritious',
|
||||||
'nutty',
|
'nutty',
|
||||||
'obedient',
|
'obedient',
|
||||||
'obese',
|
|
||||||
'oblong',
|
'oblong',
|
||||||
'oily',
|
'oily',
|
||||||
'oblong',
|
'oblong',
|
||||||
'obvious',
|
'obvious',
|
||||||
'occasional',
|
'occasional',
|
||||||
'odd',
|
'odd',
|
||||||
'oddball',
|
|
||||||
'offbeat',
|
'offbeat',
|
||||||
'offensive',
|
'offensive',
|
||||||
'official',
|
'official',
|
||||||
@@ -836,7 +774,6 @@ const adjective = [
|
|||||||
'personal',
|
'personal',
|
||||||
'pertinent',
|
'pertinent',
|
||||||
'pesky',
|
'pesky',
|
||||||
'pessimistic',
|
|
||||||
'petty',
|
'petty',
|
||||||
'phony',
|
'phony',
|
||||||
'physical',
|
'physical',
|
||||||
@@ -858,7 +795,6 @@ const adjective = [
|
|||||||
'pointed',
|
'pointed',
|
||||||
'pointless',
|
'pointless',
|
||||||
'poised',
|
'poised',
|
||||||
'poor',
|
|
||||||
'popular',
|
'popular',
|
||||||
'portly',
|
'portly',
|
||||||
'posh',
|
'posh',
|
||||||
@@ -903,7 +839,6 @@ const adjective = [
|
|||||||
'quarterly',
|
'quarterly',
|
||||||
'queasy',
|
'queasy',
|
||||||
'querulous',
|
'querulous',
|
||||||
'questionable',
|
|
||||||
'quick',
|
'quick',
|
||||||
'quick-witted',
|
'quick-witted',
|
||||||
'quiet',
|
'quiet',
|
||||||
@@ -931,7 +866,6 @@ const adjective = [
|
|||||||
'reliable',
|
'reliable',
|
||||||
'relieved',
|
'relieved',
|
||||||
'remarkable',
|
'remarkable',
|
||||||
'remorseful',
|
|
||||||
'remote',
|
'remote',
|
||||||
'repentant',
|
'repentant',
|
||||||
'required',
|
'required',
|
||||||
@@ -949,7 +883,6 @@ const adjective = [
|
|||||||
'robust',
|
'robust',
|
||||||
'rosy',
|
'rosy',
|
||||||
'rotating',
|
'rotating',
|
||||||
'rotten',
|
|
||||||
'rough',
|
'rough',
|
||||||
'round',
|
'round',
|
||||||
'rowdy',
|
'rowdy',
|
||||||
@@ -963,7 +896,6 @@ const adjective = [
|
|||||||
'rusty',
|
'rusty',
|
||||||
'sad',
|
'sad',
|
||||||
'safe',
|
'safe',
|
||||||
'salty',
|
|
||||||
'same',
|
'same',
|
||||||
'sandy',
|
'sandy',
|
||||||
'sane',
|
'sane',
|
||||||
@@ -998,7 +930,6 @@ const adjective = [
|
|||||||
'shadowy',
|
'shadowy',
|
||||||
'shady',
|
'shady',
|
||||||
'shallow',
|
'shallow',
|
||||||
'shameful',
|
|
||||||
'shameless',
|
'shameless',
|
||||||
'sharp',
|
'sharp',
|
||||||
'shimmering',
|
'shimmering',
|
||||||
@@ -1011,7 +942,6 @@ const adjective = [
|
|||||||
'showy',
|
'showy',
|
||||||
'shrill',
|
'shrill',
|
||||||
'shy',
|
'shy',
|
||||||
'sick',
|
|
||||||
'silent',
|
'silent',
|
||||||
'silky',
|
'silky',
|
||||||
'silly',
|
'silly',
|
||||||
@@ -1027,7 +957,6 @@ const adjective = [
|
|||||||
'sleepy',
|
'sleepy',
|
||||||
'slight',
|
'slight',
|
||||||
'slim',
|
'slim',
|
||||||
'slimy',
|
|
||||||
'slippery',
|
'slippery',
|
||||||
'slow',
|
'slow',
|
||||||
'slushy',
|
'slushy',
|
||||||
@@ -1136,7 +1065,6 @@ const adjective = [
|
|||||||
'tender',
|
'tender',
|
||||||
'tense',
|
'tense',
|
||||||
'tepid',
|
'tepid',
|
||||||
'terrible',
|
|
||||||
'terrific',
|
'terrific',
|
||||||
'testy',
|
'testy',
|
||||||
'thankful',
|
'thankful',
|
||||||
@@ -1183,7 +1111,6 @@ const adjective = [
|
|||||||
'tubby',
|
'tubby',
|
||||||
'turbulent',
|
'turbulent',
|
||||||
'twin',
|
'twin',
|
||||||
'ugly',
|
|
||||||
'ultimate',
|
'ultimate',
|
||||||
'unacceptable',
|
'unacceptable',
|
||||||
'unaware',
|
'unaware',
|
||||||
@@ -1198,14 +1125,12 @@ const adjective = [
|
|||||||
'unfolded',
|
'unfolded',
|
||||||
'unfortunate',
|
'unfortunate',
|
||||||
'unhappy',
|
'unhappy',
|
||||||
'unhealthy',
|
|
||||||
'uniform',
|
'uniform',
|
||||||
'unimportant',
|
'unimportant',
|
||||||
'unique',
|
'unique',
|
||||||
'united',
|
'united',
|
||||||
'unkempt',
|
'unkempt',
|
||||||
'unknown',
|
'unknown',
|
||||||
'unlawful',
|
|
||||||
'unlined',
|
'unlined',
|
||||||
'unlucky',
|
'unlucky',
|
||||||
'unnatural',
|
'unnatural',
|
||||||
@@ -1235,7 +1160,6 @@ const adjective = [
|
|||||||
'usable',
|
'usable',
|
||||||
'used',
|
'used',
|
||||||
'useful',
|
'useful',
|
||||||
'useless',
|
|
||||||
'utilized',
|
'utilized',
|
||||||
'utter',
|
'utter',
|
||||||
'vacant',
|
'vacant',
|
||||||
@@ -1271,7 +1195,6 @@ const adjective = [
|
|||||||
'warmhearted',
|
'warmhearted',
|
||||||
'warped',
|
'warped',
|
||||||
'wary',
|
'wary',
|
||||||
'wasteful',
|
|
||||||
'watchful',
|
'watchful',
|
||||||
'waterlogged',
|
'waterlogged',
|
||||||
'watery',
|
'watery',
|
||||||
@@ -1611,7 +1534,6 @@ const object = [
|
|||||||
'classroom',
|
'classroom',
|
||||||
'delivery',
|
'delivery',
|
||||||
'device',
|
'device',
|
||||||
'difficulty',
|
|
||||||
'drama',
|
'drama',
|
||||||
'election',
|
'election',
|
||||||
'engine',
|
'engine',
|
||||||
@@ -1625,7 +1547,6 @@ const object = [
|
|||||||
'suggestion',
|
'suggestion',
|
||||||
'tension',
|
'tension',
|
||||||
'variation',
|
'variation',
|
||||||
'anxiety',
|
|
||||||
'atmosphere',
|
'atmosphere',
|
||||||
'awareness',
|
'awareness',
|
||||||
'bread',
|
'bread',
|
||||||
@@ -1761,7 +1682,6 @@ const object = [
|
|||||||
'drawer',
|
'drawer',
|
||||||
'establishment',
|
'establishment',
|
||||||
'examination',
|
'examination',
|
||||||
'garbage',
|
|
||||||
'grocery',
|
'grocery',
|
||||||
'honey',
|
'honey',
|
||||||
'impression',
|
'impression',
|
||||||
@@ -1886,7 +1806,6 @@ const object = [
|
|||||||
'group',
|
'group',
|
||||||
'risk',
|
'risk',
|
||||||
'word',
|
'word',
|
||||||
'fat',
|
|
||||||
'force',
|
'force',
|
||||||
'key',
|
'key',
|
||||||
'light',
|
'light',
|
||||||
@@ -1934,7 +1853,6 @@ const object = [
|
|||||||
'coast',
|
'coast',
|
||||||
'action',
|
'action',
|
||||||
'age',
|
'age',
|
||||||
'bad',
|
|
||||||
'boat',
|
'boat',
|
||||||
'record',
|
'record',
|
||||||
'result',
|
'result',
|
||||||
@@ -2223,7 +2141,6 @@ const object = [
|
|||||||
'pleasure',
|
'pleasure',
|
||||||
'score',
|
'score',
|
||||||
'screw',
|
'screw',
|
||||||
'sex',
|
|
||||||
'shop',
|
'shop',
|
||||||
'shower',
|
'shower',
|
||||||
'suit',
|
'suit',
|
||||||
@@ -2416,7 +2333,6 @@ const object = [
|
|||||||
'counter',
|
'counter',
|
||||||
'flower',
|
'flower',
|
||||||
'grandfather',
|
'grandfather',
|
||||||
'harm',
|
|
||||||
'knee',
|
'knee',
|
||||||
'lawyer',
|
'lawyer',
|
||||||
'leather',
|
'leather',
|
||||||
@@ -2747,7 +2663,6 @@ const object = [
|
|||||||
'crazy',
|
'crazy',
|
||||||
'escape',
|
'escape',
|
||||||
'gather',
|
'gather',
|
||||||
'hate',
|
|
||||||
'prior',
|
'prior',
|
||||||
'repair',
|
'repair',
|
||||||
'rough',
|
'rough',
|
||||||
@@ -2757,19 +2672,15 @@ const object = [
|
|||||||
'strike',
|
'strike',
|
||||||
'employ',
|
'employ',
|
||||||
'external',
|
'external',
|
||||||
'hurt',
|
|
||||||
'illegal',
|
|
||||||
'laugh',
|
'laugh',
|
||||||
'lay',
|
'lay',
|
||||||
'mobile',
|
'mobile',
|
||||||
'nasty',
|
|
||||||
'ordinary',
|
'ordinary',
|
||||||
'respond',
|
'respond',
|
||||||
'royal',
|
'royal',
|
||||||
'senior',
|
'senior',
|
||||||
'split',
|
'split',
|
||||||
'strain',
|
'strain',
|
||||||
'struggle',
|
|
||||||
'swim',
|
'swim',
|
||||||
'train',
|
'train',
|
||||||
'upper',
|
'upper',
|
||||||
@@ -2808,7 +2719,6 @@ const object = [
|
|||||||
'prompt',
|
'prompt',
|
||||||
'quiet',
|
'quiet',
|
||||||
'refuse',
|
'refuse',
|
||||||
'regret',
|
|
||||||
'reveal',
|
'reveal',
|
||||||
'rush',
|
'rush',
|
||||||
'shake',
|
'shake',
|
||||||
@@ -2848,9 +2758,6 @@ const object = [
|
|||||||
'wake',
|
'wake',
|
||||||
'wrap',
|
'wrap',
|
||||||
'yesterday',
|
'yesterday',
|
||||||
'Thomas',
|
|
||||||
'Tom',
|
|
||||||
'Lieuwe',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -49,3 +49,39 @@ export const validateFile = (file) => {
|
|||||||
return false;
|
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