// get environment vars import 'dotenv/config'; import { sessionId, user } from './utils/analytics.js'; user.screenview('Node service', 'ontime').send(); user.event('NODE', 'started', 'starting node service').send(); // import config import { config } from './config/config.js'; // init database import { Low, JSONFile } from 'lowdb'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const env = process.env.NODE_ENV || 'prod'; const file = path.join(__dirname, 'data/', config.database.filename); const adapter = new JSONFile(file); export const db = new Low(adapter); // dependencies import express from 'express'; import http from 'http'; import cors from 'cors'; import { dbModelv1 as dbModel } from './models/dataModel.js'; import { parseJson_v1 as parseJson } from './utils/parser.js'; import { validateFile } from './utils/parserUtils.js'; import ua from 'universal-analytics'; // validate JSON before attempting read let isValid = validateFile(file); if (isValid) { // Read data from JSON file, this will set db.data content await db.read(); } // If file.json doesn't exist, db.data will be null // Set default data // db.data ||= { events: [] }; NODE v15 - v16 if (db.data == null || !isValid) { db.data = dbModel; await db.write(); } // get data // there is also the case of the db being corrupt // try to parse the data, make sure that all fields exist (enforce) export const data = await parseJson(db.data, true); db.data = data; await db.write(); // Import Routes import { router as eventsRouter } from './routes/eventsRouter.js'; import { router as eventRouter } from './routes/eventRouter.js'; import { router as ontimeRouter } from './routes/ontimeRouter.js'; import { router as playbackRouter } from './routes/playbackRouter.js'; // Global Objects import { EventTimer } from './classes/EventTimer.js'; // Create express APP const app = express(); // setup cors for all routes app.use(cors()); // enable pre-flight cors app.options('*', cors()); // Implement middleware app.use(ua.middleware(process.env.ANALYTICS_ID, sessionId)); app.use(express.urlencoded({ extended: true })); app.use(express.json({ limit: '1mb' })); app.use('/uploads', express.static('uploads')); // Implement route endpoints app.use('/events', eventsRouter); app.use('/event', eventRouter); app.use('/ontime', ontimeRouter); app.use('/playback', playbackRouter); // serve react app.use( express.static( path.join(__dirname, env === 'prod' ? '../' : '../../', 'client/build'), ), ); app.get('*', (req, res) => { res.sendFile( path.resolve( __dirname, env === 'prod' ? '../' : '../../', 'client', 'build', 'index.html', ), ); }); // Implement route for errors app.use((err, req, res, next) => { res.status(500).send(err.stack); }); /*************** START SERVICES ***************/ /* Override config * ---------------- * * Configuration of services comes from app general config * It can be overridden here by the settings in the db * It can also be overridden on call * */ const osc = data.osc; const oscIP = osc?.targetIP || config.osc.targetIP; const oscOutPort = osc?.portOut || config.osc.portOut; const oscInPort = osc?.port || config.osc.port; const oscInEnabled = osc?.enabled !== undefined ? osc.enabled : config.osc.inputEnabled; const serverPort = data.settings.serverPort || config.server.port; // Start OSC server import { initiateOSC, shutdownOSCServer } from './controllers/OscController.js'; export const startOSCServer = async (overrideConfig = null) => { if (!oscInEnabled) { global.timer.info('RX', 'OSC Input Disabled') return; } // Setup default port const oscSettings = { port: overrideConfig?.port || oscInPort, }; // Start OSC Server global.timer.info('RX', `Starting OSC Server on port: ${oscInPort}`) initiateOSC(oscSettings); }; // create HTTP server const server = http.createServer(app); export const startServer = async (overrideConfig = null) => { // Setup default port // const port = overrideConfig?.port || serverPort; /* Note: Port is hardcoded * need to find a good solution for sharing * the dynamic info with the frontend */ const port = 4001; // Start server const returnMessage = `Ontime is listening on port ${port}`; server.listen(port, '0.0.0.0'); // OSC Config const oscConfig = { ip: oscIP, port: overrideConfig?.port || oscOutPort, }; // init timer global.timer = new EventTimer(server, config.timer, oscConfig, data.http); global.timer.setupWithEventList(data.events); global.timer.info('SERVER', returnMessage); return returnMessage; }; export const shutdown = async () => { console.log('Node service shutdown'); user.event('NODE', 'shutdown', 'requesting node shutdown').send(); // shutdown express server server.close(); // shutdown OSC Server shutdownOSCServer(); // shutdown timer global.timer.shutdown(); }; export { server, app };