mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-16 04:43:35 +00:00
Feat/62 logger (#79)
* feat/62-logger request log data in app * feat/62-logger refact osc integration * feat/62-logger feedback on osc * feat/62-logger refact broadcast on triggers * feat/62-logger log triggers * feat/62-logger add link to studio * feat/62-logger replace toasts with logger context * feat/62-logger style improvements * feat/62-logger refactor code duplications * feat/62-logger cleanup and version bump
This commit is contained in:
+1
-1
@@ -153,7 +153,7 @@ export const startServer = async (overrideConfig = null) => {
|
||||
const port = 4001;
|
||||
|
||||
// Start server
|
||||
const returnMessage = `HTTP Server is listening on port ${port}`;
|
||||
const returnMessage = `Ontime is listening on port ${port}`;
|
||||
server.listen(port, '0.0.0.0', () => console.log(returnMessage));
|
||||
|
||||
// OSC Config
|
||||
|
||||
+326
-264
File diff suppressed because it is too large
Load Diff
+18
-30
@@ -7,22 +7,11 @@
|
||||
import { stringFromMillis } from '../utils/time.js';
|
||||
|
||||
export class Timer {
|
||||
clock = null;
|
||||
duration = null;
|
||||
current = null;
|
||||
timeTag = null;
|
||||
secondaryTimer = null;
|
||||
_secondaryTarget = null;
|
||||
_finishAt = null;
|
||||
_finishedAt = null;
|
||||
_finishedFlag = false;
|
||||
_startedAt = null;
|
||||
_pausedAt = null;
|
||||
_pausedInterval = null;
|
||||
_pausedTotal = null;
|
||||
state = 'stop';
|
||||
|
||||
constructor() {}
|
||||
constructor() {
|
||||
this.clock = null;
|
||||
this._resetTimers(true);
|
||||
this.state = 'stop';
|
||||
}
|
||||
|
||||
// call setup separately
|
||||
setupWithSeconds(seconds, autoStart = false) {
|
||||
@@ -74,11 +63,11 @@ export class Timer {
|
||||
if (this._startedAt != null) {
|
||||
// update current timer
|
||||
this.current =
|
||||
this._startedAt
|
||||
+ this.duration
|
||||
+ this._pausedTotal
|
||||
+ this._pausedInterval
|
||||
- now;
|
||||
this._startedAt +
|
||||
this.duration +
|
||||
this._pausedTotal +
|
||||
this._pausedInterval -
|
||||
now;
|
||||
}
|
||||
|
||||
// enable flag
|
||||
@@ -92,13 +81,14 @@ export class Timer {
|
||||
if (checkFinish) {
|
||||
// is event finished?
|
||||
const isTimeOver = this.current <= 0;
|
||||
const isUpdating = (this.state !== 'pause');
|
||||
const isUpdating = this.state !== 'pause';
|
||||
|
||||
if (isTimeOver && isUpdating && this._finishedAt == null) {
|
||||
if (this._finishedAt === null) this._finishedAt = now;
|
||||
this._finishedFlag = true;
|
||||
}
|
||||
}
|
||||
this.timeTag = stringFromMillis(this.current);
|
||||
}
|
||||
|
||||
// helpers
|
||||
@@ -136,6 +126,7 @@ export class Timer {
|
||||
_resetTimers(total = false) {
|
||||
if (total) this.duration = null;
|
||||
this.current = this.duration;
|
||||
this.timeTag = null;
|
||||
this.running = null;
|
||||
this.secondaryTimer = null;
|
||||
this._secondaryTarget = null;
|
||||
@@ -153,14 +144,11 @@ export class Timer {
|
||||
return this.duration - this.current;
|
||||
}
|
||||
|
||||
// get time object
|
||||
getTimes(update = true) {
|
||||
// update timer
|
||||
if (update) this.update();
|
||||
|
||||
// update timetag
|
||||
this.timeTag = stringFromMillis(this.current);
|
||||
|
||||
/**
|
||||
* Builds time object
|
||||
* @returns {{running: number, secondary: number, expectedFinish: number, durationSeconds: number, startedAt: null, clock: null}}
|
||||
*/
|
||||
getTimeObject() {
|
||||
return {
|
||||
clock: this.clock,
|
||||
running: Timer.toSeconds(this.current),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* Utility variable: 24 hour in milliseconds .
|
||||
* @type {number}
|
||||
@@ -11,7 +10,8 @@ export const DAY_TO_MS = 86400000;
|
||||
* @param {number} end - When does the event end
|
||||
* @returns {number} normalised time
|
||||
*/
|
||||
export const normaliseEndTime = (start, end) => (end < start ? end + DAY_TO_MS : end);
|
||||
export const normaliseEndTime = (start, end) =>
|
||||
end < start ? end + DAY_TO_MS : end;
|
||||
|
||||
/**
|
||||
* @description Sorts an array of objects by given property
|
||||
@@ -36,7 +36,6 @@ export const sortArrayByProperty = (arr, property) => {
|
||||
export const replacePlaceholder = (str, values) => {
|
||||
for (let [k, v] of Object.entries(values)) {
|
||||
str = str.replace(k, v);
|
||||
console.log(k, v);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
@@ -72,7 +71,10 @@ export const getSelectionByRoll = (arr, now) => {
|
||||
|
||||
// exit early if we are past the events
|
||||
const lastEvent = orderedEvents[orderedEvents.length - 1];
|
||||
const lastNormalEnd = normaliseEndTime(lastEvent.timeStart, lastEvent.timeEnd);
|
||||
const lastNormalEnd = normaliseEndTime(
|
||||
lastEvent.timeStart,
|
||||
lastEvent.timeEnd
|
||||
);
|
||||
if (now > lastNormalEnd) {
|
||||
return {
|
||||
nowIndex,
|
||||
@@ -128,7 +130,7 @@ export const getSelectionByRoll = (arr, now) => {
|
||||
// check how far the start is from now
|
||||
const wait = e.timeStart - now;
|
||||
|
||||
if (nextIndex === null || wait < timeToNext) {
|
||||
if (nextIndex == null || wait < timeToNext) {
|
||||
timeToNext = wait;
|
||||
nextIndex = arr.findIndex((a) => a.id === e.id);
|
||||
}
|
||||
@@ -161,8 +163,14 @@ export const getSelectionByRoll = (arr, now) => {
|
||||
* @returns {object} object with selection variables
|
||||
*/
|
||||
export const updateRoll = (currentTimers) => {
|
||||
|
||||
const {selectedEventId,current,_finishAt,clock,secondaryTimer,_secondaryTarget} = currentTimers;
|
||||
const {
|
||||
selectedEventId,
|
||||
current,
|
||||
_finishAt,
|
||||
clock,
|
||||
secondaryTimer,
|
||||
_secondaryTarget,
|
||||
} = currentTimers;
|
||||
|
||||
// timers
|
||||
let updatedTimer = current;
|
||||
@@ -181,8 +189,6 @@ export const updateRoll = (currentTimers) => {
|
||||
if (updatedTimer < 0) {
|
||||
isFinished = true;
|
||||
}
|
||||
console.log(updatedTimer, isFinished, _finishAt)
|
||||
|
||||
} else if (secondaryTimer >= 0) {
|
||||
// if secondaryTimer is running we are in waiting to roll
|
||||
|
||||
@@ -202,6 +208,5 @@ export const updateRoll = (currentTimers) => {
|
||||
doRollLoad = true;
|
||||
}
|
||||
|
||||
return {updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished};
|
||||
}
|
||||
|
||||
return { updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished };
|
||||
};
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
/** Class contains logic towards outgoing OSC communications. */
|
||||
import {Client, Message} from 'node-osc';
|
||||
import { Client, Message } from 'node-osc';
|
||||
|
||||
export class OSCIntegration {
|
||||
|
||||
ADDRESS = '/ontime';
|
||||
|
||||
constructor() {
|
||||
// OSC Client
|
||||
this.ADDRESS = '/ontime';
|
||||
this.oscClient = null;
|
||||
}
|
||||
|
||||
@@ -26,8 +24,8 @@ export class OSCIntegration {
|
||||
time: 'time',
|
||||
overtime: 'overtime',
|
||||
title: 'title',
|
||||
presenter:'presenter',
|
||||
}
|
||||
presenter: 'presenter',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,13 +35,19 @@ export class OSCIntegration {
|
||||
* @param {number} oscConfig.port - OSC Destination Port
|
||||
*/
|
||||
init(oscConfig) {
|
||||
const {ip, port} = oscConfig;
|
||||
const { ip, port } = oscConfig;
|
||||
try {
|
||||
this.oscClient = new Client(ip, port);
|
||||
console.log(`Initialised OSC Client at ${ip}:${port}`);
|
||||
return {
|
||||
success: true,
|
||||
message: `Initialised OSC Client at ${ip}:${port}`,
|
||||
};
|
||||
} catch (error) {
|
||||
this.oscClient = null;
|
||||
console.log(`Failed initialising OSC Client: ${error}`);
|
||||
return {
|
||||
success: true,
|
||||
message: `Failed initialising OSC Client: ${error}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,14 +57,21 @@ export class OSCIntegration {
|
||||
* @param {string} [payload] - optional payload required in some message types
|
||||
*/
|
||||
async send(messageType, payload) {
|
||||
const reply = {
|
||||
success: true,
|
||||
message: 'OSC Message sent',
|
||||
};
|
||||
|
||||
if (this.oscClient == null) {
|
||||
console.log('OSC ERROR: Client not initialised');
|
||||
return;
|
||||
reply.success = false;
|
||||
reply.message = 'Client not initialised';
|
||||
return reply;
|
||||
}
|
||||
|
||||
if (messageType == null) {
|
||||
console.log('OSC ERROR: Message undefined');
|
||||
return;
|
||||
reply.success = false;
|
||||
reply.message = 'Message undefined';
|
||||
return reply;
|
||||
}
|
||||
|
||||
// only specify special cases
|
||||
@@ -68,35 +79,58 @@ export class OSCIntegration {
|
||||
case 'overtime':
|
||||
// Whether timer is negative
|
||||
this.oscClient.send(`${this.ADDRESS}/overtime`, payload, (err) => {
|
||||
if (err) console.error(err);
|
||||
if (err) {
|
||||
reply.success = false;
|
||||
reply.message = err;
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 'title':
|
||||
if (payload != null && payload !== "") {
|
||||
if (payload != null && payload !== '') {
|
||||
// Send Title of current event
|
||||
this.oscClient.send(`${this.ADDRESS}/title`, payload, (err) => {
|
||||
if (err) console.error(err);
|
||||
if (err) {
|
||||
reply.success = false;
|
||||
reply.message = err;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reply.success = false;
|
||||
reply.message = 'Missing message data';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'presenter':
|
||||
if (payload != null && payload !== "") {
|
||||
if (payload != null && payload !== '') {
|
||||
// Send presenter data on current event
|
||||
this.oscClient.send(`${this.ADDRESS}/presenter`, payload, (err) => {
|
||||
if (err) console.error(err);
|
||||
if (err) {
|
||||
reply.success = false;
|
||||
reply.message = err;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reply.success = false;
|
||||
reply.message = 'Missing message data';
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// catch all for messages, allows to add new messages
|
||||
// but should be used with the integrations definition
|
||||
const message = new Message(`${this.ADDRESS}/${messageType}`)
|
||||
if (payload != null) message.append(payload)
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const message = new Message(`${this.ADDRESS}/${messageType}`);
|
||||
if (payload != null) message.append(payload);
|
||||
this.oscClient.send(message, (err) => {
|
||||
if (err) console.error(err);
|
||||
if (err) {
|
||||
reply.success = false;
|
||||
reply.message = err;
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
|
||||
shutdown() {
|
||||
@@ -104,4 +138,4 @@ export class OSCIntegration {
|
||||
this.oscClient.close();
|
||||
this.oscClient = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,7 @@ export const shutdownOSCServer = () => {
|
||||
};
|
||||
|
||||
export const initiateOSC = (config) => {
|
||||
oscServer = new Server(config.port, '0.0.0.0', () => {
|
||||
console.log(`OSC Server is listening on port ${config.port}`);
|
||||
});
|
||||
oscServer = new Server(config.port, '0.0.0.0');
|
||||
|
||||
// error
|
||||
oscServer.on('error', console.error);
|
||||
@@ -19,7 +17,6 @@ export const initiateOSC = (config) => {
|
||||
// ontime: fixed message for app
|
||||
// path: command to be called
|
||||
// args: extra data, only used on some of the API entries (delay, goto)
|
||||
console.log('OSC received', msg);
|
||||
|
||||
// split message
|
||||
const [, address, path] = msg[0].split('/');
|
||||
@@ -46,40 +43,35 @@ export const initiateOSC = (config) => {
|
||||
break;
|
||||
case 'start':
|
||||
case 'play':
|
||||
console.log('calling play');
|
||||
global.timer.trigger('start');
|
||||
break;
|
||||
case 'pause':
|
||||
console.log('calling pause');
|
||||
global.timer.trigger('pause');
|
||||
break;
|
||||
case 'prev':
|
||||
console.log('calling prev');
|
||||
global.timer.trigger('previous');
|
||||
break;
|
||||
case 'next':
|
||||
console.log('calling next');
|
||||
global.timer.trigger('next');
|
||||
break;
|
||||
case 'unload':
|
||||
case 'stop':
|
||||
console.log('calling unload');
|
||||
global.timer.trigger('unload');
|
||||
break;
|
||||
case 'reload':
|
||||
console.log('calling reload');
|
||||
global.timer.trigger('reload');
|
||||
break;
|
||||
case 'roll':
|
||||
console.log('calling roll');
|
||||
global.timer.trigger('roll');
|
||||
break;
|
||||
case 'delay':
|
||||
console.log('calling delay with', args);
|
||||
try {
|
||||
const t = parseInt(args);
|
||||
if (isNaN(t)) {
|
||||
console.error(`OSC IN: delay time not recognised ${args}`);
|
||||
global.timer.error(
|
||||
'RX',
|
||||
`OSC IN: delay time not recognised ${args}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
global.timer.increment(t * 1000 * 60);
|
||||
@@ -88,36 +80,37 @@ export const initiateOSC = (config) => {
|
||||
}
|
||||
break;
|
||||
case 'goto':
|
||||
console.log('calling goto with', args);
|
||||
try {
|
||||
const eventIndex = parseInt(args);
|
||||
if (isNaN(eventIndex) || eventIndex <= 0 || eventIndex == null) {
|
||||
console.error(
|
||||
global.timer.error(
|
||||
'RX',
|
||||
`OSC IN: event index not recognised or out of range ${eventIndex}`
|
||||
);
|
||||
}
|
||||
global.timer.loadEventByIndex(eventIndex - 1);
|
||||
} catch (error) {
|
||||
console.log('error calling goto: ', error);
|
||||
global.timer.error('RX', `OSC IN: error calling goto ${error}`);
|
||||
}
|
||||
break;
|
||||
case 'gotoid':
|
||||
console.log('calling gotoid with', args);
|
||||
if (args == null) {
|
||||
console.error(
|
||||
`OSC IN: event id not recognised or out of range ${args}`
|
||||
global.timer.error(
|
||||
'RX',
|
||||
`OSC IN: event id not recognised or out of range ${args}}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
global.timer.loadEventById(args.toString().toLowerCase());
|
||||
} catch (error) {
|
||||
console.log('error calling goto: ', error);
|
||||
global.timer.error('RX', `OSC IN: error calling goto ${error}`);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(`Error: unhandled message ${path}`);
|
||||
global.timer.warning('RX', `OSC IN: unhandled message ${path}`);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -21,6 +21,5 @@ export const postEvent = async (req, res) => {
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
res.status(400).send(error);
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -130,7 +130,6 @@ export const postInfo = async (req, res) => {
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
res.status(400).send(error);
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -7,69 +7,59 @@ export const pbGet = async (req, res) => {
|
||||
// Create controller for GET request to '/playback/onAir'
|
||||
// Turns onAir flag to true
|
||||
export const onAir = async (req, res) => {
|
||||
console.log('Setting onAir to true');
|
||||
global.timer.trigger('onAir') ? res.sendStatus(200) : res.sendStatus(400);
|
||||
};
|
||||
|
||||
// Create controller for GET request to '/playback/onAir'
|
||||
// Turns onAir flag to true
|
||||
export const offAir = async (req, res) => {
|
||||
console.log('Setting onAir to false');
|
||||
global.timer.trigger('offAir') ? res.sendStatus(200) : res.sendStatus(400);
|
||||
};
|
||||
|
||||
// Create controller for GET request to '/playback/start'
|
||||
// Starts timer object
|
||||
export const pbStart = async (req, res) => {
|
||||
console.log('Calling start');
|
||||
global.timer.trigger('start') ? res.sendStatus(200) : res.sendStatus(400);
|
||||
};
|
||||
|
||||
// Create controller for GET request to '/playback/pause'
|
||||
// Pauses timer object
|
||||
export const pbPause = async (req, res) => {
|
||||
console.log('Calling pause');
|
||||
global.timer.trigger('pause') ? res.sendStatus(200) : res.sendStatus(400);
|
||||
};
|
||||
|
||||
// Create controller for GET request to '/playback/stop'
|
||||
// Stops timer object
|
||||
export const pbStop = async (req, res) => {
|
||||
console.log('Calling stop');
|
||||
global.timer.trigger('stop') ? res.sendStatus(200) : res.sendStatus(400);
|
||||
};
|
||||
|
||||
// Create controller for GET request to '/playback/roll'
|
||||
// Sets timer object to roll mode
|
||||
export const pbRoll = async (req, res) => {
|
||||
console.log('Calling roll');
|
||||
global.timer.trigger('roll') ? res.sendStatus(200) : res.sendStatus(400);
|
||||
};
|
||||
|
||||
// Create controller for GET request to '/playback/previous'
|
||||
// Sets timer object to roll mode
|
||||
export const pbPrevious = async (req, res) => {
|
||||
console.log('Calling previous');
|
||||
global.timer.trigger('previous') ? res.sendStatus(200) : res.sendStatus(400);
|
||||
};
|
||||
|
||||
// Create controller for GET request to '/playback/next'
|
||||
// Sets timer object to roll mode
|
||||
export const pbNext = async (req, res) => {
|
||||
console.log('Calling next');
|
||||
global.timer.trigger('next') ? res.sendStatus(200) : res.sendStatus(400);
|
||||
};
|
||||
|
||||
// Create controller for GET request to '/playback/unload'
|
||||
// Unloads any events
|
||||
export const pbUnload = async (req, res) => {
|
||||
console.log('Calling unload');
|
||||
global.timer.trigger('unload') ? res.sendStatus(200) : res.sendStatus(400);
|
||||
};
|
||||
|
||||
// Create controller for GET request to '/playback/reload'
|
||||
// Reloads current event
|
||||
export const pbReload = async (req, res) => {
|
||||
console.log('Calling reload');
|
||||
global.timer.trigger('reload') ? res.sendStatus(200) : res.sendStatus(400);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"name": "ontime-server",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"body-parser": "~1.19.0",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import getRandomName from '../getRandomName.js';
|
||||
|
||||
test('generates 500 unique names', () => {
|
||||
let names = [];
|
||||
for (let i = 0; i < 500; i++) {
|
||||
names.push(getRandomName());
|
||||
}
|
||||
|
||||
const unique = [...new Set(names)];
|
||||
expect(names.length).toBe(unique.length);
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,26 @@ const mts = 1000; // millis to seconds
|
||||
const mtm = 1000 * 60; // millis to minutes
|
||||
const mth = 1000 * 60 * 60; // millis to hours
|
||||
|
||||
/**
|
||||
* Returns current time in milliseconds
|
||||
* @returns {number}
|
||||
*/
|
||||
export const nowInMillis = () => {
|
||||
const now = new Date();
|
||||
|
||||
// extract milliseconds since midnight
|
||||
let elapsed = now.getHours() * 3600000;
|
||||
elapsed += now.getMinutes() * 60000;
|
||||
elapsed += now.getSeconds() * 1000;
|
||||
elapsed += now.getMilliseconds();
|
||||
|
||||
return elapsed;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Converts milliseconds to string representing time
|
||||
* @param {number} ms - time in milliseconds
|
||||
* @param {boolean} showSeconds - wether to show the seconds
|
||||
* @param {boolean} showSeconds - weather to show the seconds
|
||||
* @param {string} delim - character between HH MM SS
|
||||
* @param {string} ifNull - what to return if value is null
|
||||
* @returns {string} String representing time 00:12:02
|
||||
@@ -17,7 +33,7 @@ export const stringFromMillis = (
|
||||
delim = ':',
|
||||
ifNull = '...'
|
||||
) => {
|
||||
if (ms === null || isNaN(ms)) return ifNull;
|
||||
if (ms == null || isNaN(ms)) return ifNull;
|
||||
const isNegative = ms < 0 ? '-' : '';
|
||||
const millis = Math.abs(ms);
|
||||
|
||||
@@ -36,7 +52,7 @@ export const stringFromMillis = (
|
||||
/**
|
||||
* @description Converts an excel date to milliseconds
|
||||
* @argument {string} excelDate - excel string date
|
||||
* @returns {number} - time in millisenconds
|
||||
* @returns {number} - time in milliseconds
|
||||
*/
|
||||
export const excelDateStringToMillis = (excelDate) => {
|
||||
const date = new Date(excelDate);
|
||||
@@ -47,5 +63,5 @@ export const excelDateStringToMillis = (excelDate) => {
|
||||
|
||||
return h * mth + m * mtm + s * mts;
|
||||
}
|
||||
return null;
|
||||
return 0;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user