diff --git a/.gitignore b/.gitignore index 6e0e84d..7f82705 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .env tracker.json node_modules -dist/ \ No newline at end of file +dist/ +tkrcfg.json \ No newline at end of file diff --git a/src/exceptions/InvalidHTConfigException.ts b/src/exceptions/InvalidHTConfigException.ts new file mode 100644 index 0000000..8074b50 --- /dev/null +++ b/src/exceptions/InvalidHTConfigException.ts @@ -0,0 +1,5 @@ +export default class InvalidHTConfigException extends Error { + constructor() { + super('Invalid HTConfig file!') + } +} \ No newline at end of file diff --git a/src/exceptions/NotImplementedException.ts b/src/exceptions/NotImplementedException.ts new file mode 100644 index 0000000..07daf5e --- /dev/null +++ b/src/exceptions/NotImplementedException.ts @@ -0,0 +1,5 @@ +export default class NotImplementedException extends Error { + constructor() { + super('Method is not implemented') + } +} diff --git a/src/index.ts b/src/index.ts index ef99397..2316e8b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,42 +8,62 @@ import gameCheck from './checks/game.check.js' import { init, serializeDbToDisk } from './utils/db2.js' import cron from 'node-cron' -const timeoutTime = parseInt((process.env.TIMEOUT_TIME as string)) +import getConfig from './utils/config-parser.js' + +const { config } = await getConfig() + +const timeoutTime = config.timeoutMs let cycle = 0 debug('HypeTrack started on %s.', new Date()) // TODO: Web and Mesa. -// Initialize DB2. -await init() -await serializeDbToDisk() // Initially serialize a copy of the database to disk. +// Initialize DB2 if config says to do so. +config.db2.initializeOnBoot && await init() -// This cron job serializes the database to disk every five minutes. -cron.schedule('*/5 * * * *', async () => { - await serializeDbToDisk() -}) +if (config.db2.serializeOnBoot) { + await serializeDbToDisk() // Initially serialize a copy of the database to disk. +} + +if (typeof config.db2.serializeCronString === 'string') { + // This cron job serializes the database to disk every five minutes. + cron.schedule(config.db2.serializeCronString, async () => { + await serializeDbToDisk() + }) +} // This detects interrupts, and will serialize the DB to disk before breaking. // TODO: I don't think this works on Windows. process.on('SIGINT', async () => { - console.log('Interrupt caught! Serializing database to disk.') - await serializeDbToDisk() + console.log('Interrupt caught!') + if (config.db2.serializeOnInterrupt) { + console.log('Serializing database to disk') + await serializeDbToDisk() + } process.exit(0) }) -// TODO: Have a way to have this dynamically expandable. -// TODO: Figure out what needs priority here. +const commonChecks = config.checks.filter(check => check.type === 'common') +const streamChecks = config.checks.filter(check => check.type === 'stream') +const gameChecks = config.checks.filter(check => check.type === 'game') + +let checkCount = 0; +[commonChecks, streamChecks, gameChecks].forEach(checks => checkCount += checks.length) +debug('Starting %d checks.', checkCount) setInterval(async () => { debug('We\'re on check cycle %d.', cycle) - await commonCheck('https://api.prod.hype.space', 'hypeapi') - await commonCheck('https://ws.prod.hype.space', 'hypeapi-websocket') - await commonCheck('https://telemetry.prod.hype.space', 'api-telemetry') - await streamCheck('internet_high') - await streamCheck('intranet_high') - await streamCheck('wirecast_high') + for (const check of commonChecks) { + await commonCheck(check.params[0], check.params[1]) + } + + for (const check of streamChecks) { + await streamCheck(check.params[0]) + } - typeof process.env.HQ_TOKEN !== 'undefined' && await gameCheck() + if (gameChecks.length > 0) { + await gameCheck() + } // Increment cycle debug('Cycle %d ended.', cycle) diff --git a/src/types/HTCheck.type.ts b/src/types/HTCheck.type.ts new file mode 100644 index 0000000..837f5e9 --- /dev/null +++ b/src/types/HTCheck.type.ts @@ -0,0 +1,10 @@ +import { type HTCheckType } from "./HTCheckType.type" + +export type HTCheck = { + /** Type of check */ + type: HTCheckType, + + /** Params to pass to the check method. */ + // rome-ignore lint/suspicious/noExplicitAny: Types can be anything on this, honestly. Too lazy to give it explicit types. + params: any[] +} \ No newline at end of file diff --git a/src/types/HTCheckType.type.ts b/src/types/HTCheckType.type.ts new file mode 100644 index 0000000..1fba41f --- /dev/null +++ b/src/types/HTCheckType.type.ts @@ -0,0 +1,4 @@ +export type HTCheckType = + | 'common' + | 'stream' + | 'game' \ No newline at end of file diff --git a/src/types/HTConfig.type.ts b/src/types/HTConfig.type.ts new file mode 100644 index 0000000..74babcb --- /dev/null +++ b/src/types/HTConfig.type.ts @@ -0,0 +1,12 @@ +import { type HTDB2Config } from "./HTDB2Config.type" +import { type HTCheck } from "./HTCheck.type" +export type HTConfig = { + /** Options to configure DB2 (the in-memory database) */ + db2: HTDB2Config + + /** The time to wait between checks. */ + timeoutMs: number, + + /** Configured checks to run. */ + checks: HTCheck[] +} \ No newline at end of file diff --git a/src/types/HTConfigFile.type.ts b/src/types/HTConfigFile.type.ts new file mode 100644 index 0000000..189ec1f --- /dev/null +++ b/src/types/HTConfigFile.type.ts @@ -0,0 +1,9 @@ +import { type HTConfig } from './HTConfig.type' + +export type HTConfigFile = { + /** Schema version */ + __v: number, + + /** Config root */ + config: HTConfig +} \ No newline at end of file diff --git a/src/types/HTDB2Config.type.ts b/src/types/HTDB2Config.type.ts new file mode 100644 index 0000000..516d5d6 --- /dev/null +++ b/src/types/HTDB2Config.type.ts @@ -0,0 +1,13 @@ +export type HTDB2Config = { + /** Whether or not the database should be initialized on start of tracker. */ + initializeOnBoot: boolean + + /** Whether or not to serialize the database to disk when the program starts */ + serializeOnBoot: boolean + + /** Cron string to determine how often the database gets serialized to disk. */ + serializeCronString: string + + /** Whether or not to serialize the DB to disk on interrupt. */ + serializeOnInterrupt: boolean +} \ No newline at end of file diff --git a/src/utils/config-parser.ts b/src/utils/config-parser.ts new file mode 100644 index 0000000..7f0b35a --- /dev/null +++ b/src/utils/config-parser.ts @@ -0,0 +1,31 @@ +import fs from 'node:fs/promises' +import InvalidHTConfigException from '../exceptions/InvalidHTConfigException' +import { type HTConfigFile } from '../types/HTConfigFile.type' + +let config: HTConfigFile + +export default async function getConfigFile (fileName: string = "tkrcfg.json") { + if (config) { + return config + } + + // Open file. + const file = await fs.open(fileName, 'r') + + // Read file. + const fileContents = await file.readFile() + + // Parse file. + const parsedFile = JSON.parse(fileContents.toString()) as HTConfigFile + + // Check if file is valid. + if (!parsedFile) { + throw new InvalidHTConfigException() + } + + // Save config. + config = parsedFile + + // Return json. + return parsedFile +} \ No newline at end of file diff --git a/tkrcfg.example.json b/tkrcfg.example.json new file mode 100644 index 0000000..8ca011f --- /dev/null +++ b/tkrcfg.example.json @@ -0,0 +1,49 @@ +{ + "__v": 1, + "config": { + "db2": { + "serializeOnBoot": true, + "serializeCronString": "*/5 * * * *" + }, + "timeoutMs": 5000, + "checks": [ + { + "type": "common", + "params": [ + "api.prod.hype.space", + "hypeapi" + ] + }, + { + "type": "common", + "params": [ + "ws.prod.hype.space", + "hypeapi-websocket" + ] + }, + { + "type": "common", + "params": [ + "telemetry.prod.hype.space", + "api-telemetry" + ] + }, + { + "type": "stream", + "params": ["internet_high"] + }, + { + "type": "stream", + "params": ["intranet_high"] + }, + { + "type": "stream", + "params": ["wirecast_high"] + }, + { + "type": "game", + "params": [] + } + ] + } +} \ No newline at end of file