diff --git a/packages/mos-gateway/src/connector.ts b/packages/mos-gateway/src/connector.ts index a6ea0de258..2f34686763 100644 --- a/packages/mos-gateway/src/connector.ts +++ b/packages/mos-gateway/src/connector.ts @@ -5,6 +5,10 @@ import { PeripheralDeviceId, loadCertificatesFromDisk, CertificatesConfig, + stringifyError, + HealthConfig, + HealthEndpoints, + IConnector, } from '@sofie-automation/server-core-integration' export interface Config { @@ -12,12 +16,16 @@ export interface Config { device: DeviceConfig core: CoreConfig mos: MosConfig + health: HealthConfig } export interface DeviceConfig { deviceId: PeripheralDeviceId deviceToken: string } -export class Connector { +export class Connector implements IConnector { + public initialized = false + public initializedError: string | undefined = undefined + private mosHandler: MosHandler | undefined private coreHandler: CoreHandler | undefined private _config: Config | undefined @@ -38,11 +46,18 @@ export class Connector { this._logger.info('Initializing Core...') await this.initCore(certificates) + if (!this.coreHandler) throw Error('coreHandler is undefined!') + + new HealthEndpoints(this, this.coreHandler, config.health) + this._logger.info('Initializing Mos...') await this.initMos() this._logger.info('Initialization done') + this.initialized = true } catch (e: any) { + this.initializedError = stringifyError(e) + this._logger.error('Error during initialization:', e, e.stack) this._logger.info('Shutting down in 10 seconds!') diff --git a/packages/mos-gateway/src/coreHandler.ts b/packages/mos-gateway/src/coreHandler.ts index e6a38c5849..3f3a85b129 100644 --- a/packages/mos-gateway/src/coreHandler.ts +++ b/packages/mos-gateway/src/coreHandler.ts @@ -10,6 +10,7 @@ import { stringifyError, PeripheralDevicePubSub, PeripheralDevicePubSubCollectionsNames, + ICoreHandler, } from '@sofie-automation/server-core-integration' import * as Winston from 'winston' @@ -29,14 +30,16 @@ export interface CoreConfig { /** * Represents a connection between mos-integration and Core */ -export class CoreHandler { +export class CoreHandler implements ICoreHandler { core: CoreConnection | undefined logger: Winston.Logger public _observers: Array> = [] + public connectedToCore = false private _deviceOptions: DeviceConfig private _coreMosHandlers: Array = [] private _onConnected?: () => any private _isInitialized = false + private _isDestroyed = false private _executedFunctions = new Set() private _coreConfig?: CoreConfig private _certificates?: Buffer[] @@ -54,10 +57,12 @@ export class CoreHandler { this.core.onConnected(() => { this.logger.info('Core Connected!') + this.connectedToCore = true if (this._isInitialized) this.onConnectionRestored() }) this.core.onDisconnected(() => { this.logger.info('Core Disconnected!') + this.connectedToCore = false }) this.core.onError((err) => { this.logger.error('Core Error: ' + (typeof err === 'string' ? err : err.message || err.toString())) @@ -74,31 +79,45 @@ export class CoreHandler { } await this.core.init(ddpConfig) - if (!this.core) { - throw Error('core is undefined!') - } - - this.core - .setStatus({ - statusCode: StatusCode.GOOD, - // messages: [] - }) - .catch((e) => this.logger.warn('Error when setting status:' + e)) - // nothing - await this.setupSubscriptionsAndObservers() this._isInitialized = true + + await this.updateCoreStatus() + } + getCoreStatus(): { + statusCode: StatusCode + messages: string[] + } { + let statusCode = StatusCode.GOOD + const messages: string[] = [] + + if (!this._isInitialized) { + statusCode = StatusCode.BAD + messages.push('Starting up...') + } + if (this._isDestroyed) { + statusCode = StatusCode.FATAL + messages.push('Shut down') + } + return { + statusCode, + messages, + } } + async updateCoreStatus(): Promise { + if (!this.core) throw Error('core is undefined!') + + await this.core.setStatus(this.getCoreStatus()) + } + async dispose(): Promise { + this._isDestroyed = true if (!this.core) { throw Error('core is undefined!') } - await this.core.setStatus({ - statusCode: StatusCode.FATAL, - messages: ['Shutting down'], - }) + await this.updateCoreStatus() await Promise.all( this._coreMosHandlers.map(async (cmh: CoreMosDeviceHandler) => { diff --git a/packages/mos-gateway/src/index.ts b/packages/mos-gateway/src/index.ts index 1b3b9b1a7c..b6b2819fad 100644 --- a/packages/mos-gateway/src/index.ts +++ b/packages/mos-gateway/src/index.ts @@ -14,6 +14,7 @@ let deviceToken: string = process.env.DEVICE_TOKEN || '' let disableWatchdog: boolean = process.env.DISABLE_WATCHDOG === '1' || false let unsafeSSL: boolean = process.env.UNSAFE_SSL === '1' || false const certs: string[] = (process.env.CERTIFICATES || '').split(';') || [] +let healthPort: number | undefined = parseInt(process.env.HEALTH_PORT + '') || undefined let debug = false let printHelp = false @@ -46,6 +47,8 @@ process.argv.forEach((val) => { } else if (val.match(/-unsafeSSL/i)) { // Will cause the Node applocation to blindly accept all certificates. Not recommenced unless in local, controlled networks. unsafeSSL = true + } else if (prevProcessArg.match(/-healthPort/i)) { + healthPort = parseInt(val) } prevProcessArg = nextPrevProcessArg + '' }) @@ -207,6 +210,9 @@ const config: Config = { port: port, watchdog: !disableWatchdog, }, + health: { + port: healthPort, + }, mos: { self: { debug: debug, diff --git a/packages/playout-gateway/src/config.ts b/packages/playout-gateway/src/config.ts index 8893f34742..5f9f122bb7 100644 --- a/packages/playout-gateway/src/config.ts +++ b/packages/playout-gateway/src/config.ts @@ -20,6 +20,8 @@ let influxUser: string | undefined = process.env.INFLUX_USER || 'sofie' let influxPassword: string | undefined = process.env.INFLUX_PASSWORD || undefined let influxDatabase: string | undefined = process.env.INFLUX_DB || 'sofie' +let healthPort: number | undefined = parseInt(process.env.HEALTH_PORT + '') || undefined + let prevProcessArg = '' process.argv.forEach((val) => { val = val + '' @@ -50,6 +52,8 @@ process.argv.forEach((val) => { influxPassword = val } else if (prevProcessArg.match(/-influxDatabase/i)) { influxDatabase = val + } else if (prevProcessArg.match(/-healthPort/i)) { + healthPort = parseInt(val) // arguments with no options: } else if (val.match(/-disableWatchdog/i)) { @@ -57,7 +61,9 @@ process.argv.forEach((val) => { } else if (val.match(/-disableAtemUpload/i)) { disableAtemUpload = true } else if (val.match(/-unsafeSSL/i)) { - // Will cause the Node applocation to blindly accept all certificates. Not recommenced unless in local, controlled networks. + // Will cause the Node application to blindly accept all certificates. + // Not recommenced unless in local, controlled networks. + // Instead use "-certificates cert1 cert2" unsafeSSL = true } prevProcessArg = nextPrevProcessArg + '' @@ -85,6 +91,9 @@ const config: Config = { password: influxPassword, database: influxDatabase, }, + health: { + port: healthPort, + }, } export { config, logPath, logLevel, disableWatchdog, disableAtemUpload } diff --git a/packages/playout-gateway/src/connector.ts b/packages/playout-gateway/src/connector.ts index cc9dea1a19..71dbaf6eed 100644 --- a/packages/playout-gateway/src/connector.ts +++ b/packages/playout-gateway/src/connector.ts @@ -6,6 +6,10 @@ import { CertificatesConfig, PeripheralDeviceId, loadCertificatesFromDisk, + stringifyError, + HealthConfig, + HealthEndpoints, + IConnector, } from '@sofie-automation/server-core-integration' export interface Config { @@ -14,13 +18,17 @@ export interface Config { core: CoreConfig tsr: TSRConfig influx: InfluxConfig + health: HealthConfig } export interface DeviceConfig { deviceId: PeripheralDeviceId deviceToken: string } -export class Connector { +export class Connector implements IConnector { + public initialized = false + public initializedError: string | undefined = undefined + private tsrHandler: TSRHandler | undefined private coreHandler: CoreHandler | undefined private _logger: Logger @@ -38,6 +46,8 @@ export class Connector { this._logger.info('Initializing Core...') this.coreHandler = new CoreHandler(this._logger, config.device) + new HealthEndpoints(this, this.coreHandler, config.health) + await this.coreHandler.init(config.core, this._certificates) this._logger.info('Core initialized') @@ -47,12 +57,15 @@ export class Connector { this._logger.info('TSR initialized') this._logger.info('Initialization done') + this.initialized = true return } catch (e: any) { this._logger.error('Error during initialization:') this._logger.error(e) this._logger.error(e.stack) + this.initializedError = stringifyError(e) + try { if (this.coreHandler) { this.coreHandler.destroy().catch(this._logger.error) diff --git a/packages/playout-gateway/src/coreHandler.ts b/packages/playout-gateway/src/coreHandler.ts index b482424bc0..4b969ddf3e 100644 --- a/packages/playout-gateway/src/coreHandler.ts +++ b/packages/playout-gateway/src/coreHandler.ts @@ -11,6 +11,7 @@ import { stringifyError, PeripheralDevicePubSub, PeripheralDevicePubSubCollectionsNames, + ICoreHandler, } from '@sofie-automation/server-core-integration' import { MediaObject, DeviceOptionsAny, ActionExecutionResult } from 'timeline-state-resolver' import _ from 'underscore' @@ -40,7 +41,7 @@ export interface MemoryUsageReport { /** * Represents a connection between the Gateway and Core */ -export class CoreHandler { +export class CoreHandler implements ICoreHandler { core!: CoreConnection logger: Logger public _observers: Array> = [] @@ -59,6 +60,8 @@ export class CoreHandler { private _statusInitialized = false private _statusDestroyed = false + public connectedToCore = false + constructor(logger: Logger, deviceOptions: DeviceConfig) { this.logger = logger this._deviceOptions = deviceOptions @@ -73,11 +76,13 @@ export class CoreHandler { this.core.onConnected(() => { this.logger.info('Core Connected!') + this.connectedToCore = true if (this._onConnected) this._onConnected() }) this.core.onDisconnected(() => { this.logger.warn('Core Disconnected!') + this.connectedToCore = false }) this.core.onError((err: any) => { this.logger.error('Core Error: ' + (typeof err === 'string' ? err : err.message || err.toString() || err)) @@ -375,9 +380,12 @@ export class CoreHandler { return Object.fromEntries(this._tsrHandler.getDebugStates().entries()) } - async updateCoreStatus(): Promise { + getCoreStatus(): { + statusCode: StatusCode + messages: string[] + } { let statusCode = StatusCode.GOOD - const messages: Array = [] + const messages: string[] = [] if (!this._statusInitialized) { statusCode = StatusCode.BAD @@ -387,11 +395,13 @@ export class CoreHandler { statusCode = StatusCode.BAD messages.push('Shut down') } - - return this.core.setStatus({ - statusCode: statusCode, - messages: messages, - }) + return { + statusCode, + messages, + } + } + async updateCoreStatus(): Promise { + return this.core.setStatus(this.getCoreStatus()) } } diff --git a/packages/server-core-integration/package.json b/packages/server-core-integration/package.json index 701afcb0c7..7c6fa5f169 100644 --- a/packages/server-core-integration/package.json +++ b/packages/server-core-integration/package.json @@ -69,11 +69,17 @@ "rundown", "production" ], + "devDependencies": { + "@types/koa": "^3.0.0", + "@types/koa__router": "^12.0.4" + }, "dependencies": { + "@koa/router": "^14.0.0", "@sofie-automation/shared-lib": "1.53.0-in-development", "ejson": "^2.2.3", "faye-websocket": "^0.11.4", "got": "^11.8.6", + "koa": "^3.0.1", "tslib": "^2.8.1", "underscore": "^1.13.7" }, diff --git a/packages/server-core-integration/src/index.ts b/packages/server-core-integration/src/index.ts index e864f65557..4318915d86 100644 --- a/packages/server-core-integration/src/index.ts +++ b/packages/server-core-integration/src/index.ts @@ -1,6 +1,8 @@ export * from './lib/coreConnection.js' export * from './lib/configManifest.js' export * from './lib/ddpClient.js' +export * from './lib/gateway-types.js' +export * from './lib/health.js' export * from './lib/methods.js' export * from './lib/process.js' export { SubscriptionId } from './lib/subscriptions.js' diff --git a/packages/server-core-integration/src/lib/gateway-types.ts b/packages/server-core-integration/src/lib/gateway-types.ts new file mode 100644 index 0000000000..e2745a6aa8 --- /dev/null +++ b/packages/server-core-integration/src/lib/gateway-types.ts @@ -0,0 +1,11 @@ +import { StatusCode } from '@sofie-automation/shared-lib/dist/lib/status' + +export interface IConnector { + initialized: boolean + initializedError: string | undefined +} + +export interface ICoreHandler { + getCoreStatus: () => { statusCode: StatusCode; messages: string[] } + connectedToCore: boolean +} diff --git a/packages/server-core-integration/src/lib/health.ts b/packages/server-core-integration/src/lib/health.ts new file mode 100644 index 0000000000..05c2e6c9c9 --- /dev/null +++ b/packages/server-core-integration/src/lib/health.ts @@ -0,0 +1,70 @@ +import Koa from 'koa' +import Router from '@koa/router' +import { StatusCode } from '@sofie-automation/shared-lib/dist/lib/status' +import { assertNever } from '@sofie-automation/shared-lib/dist/lib/lib' +import { IConnector, ICoreHandler } from './gateway-types.js' + +export interface HealthConfig { + /** If set, exposes health HTTP endpoints on the given port */ + port?: number +} + +/** + * Exposes health endpoints for Kubernetes or other orchestrators to monitor + * see https://kubernetes.io/docs/concepts/configuration/liveness-readiness-startup-probes + */ +export class HealthEndpoints { + private app = new Koa() + constructor( + private connector: IConnector, + private coreHandler: ICoreHandler, + private config: HealthConfig + ) { + if (!config.port) return // disabled + + const router = new Router() + + router.get('/healthz', async (ctx) => { + if (this.connector.initializedError !== undefined) { + ctx.status = 503 + ctx.body = `Error during initialization: ${this.connector.initializedError}` + return + } + if (!this.connector.initialized) { + ctx.status = 503 + ctx.body = 'Not initialized' + return + } + + const coreStatus = this.coreHandler.getCoreStatus() + + if (coreStatus.statusCode === StatusCode.UNKNOWN) ctx.status = 503 + else if (coreStatus.statusCode === StatusCode.FATAL) ctx.status = 503 + else if (coreStatus.statusCode === StatusCode.BAD) ctx.status = 503 + else if (coreStatus.statusCode === StatusCode.WARNING_MAJOR) ctx.status = 200 + else if (coreStatus.statusCode === StatusCode.WARNING_MINOR) ctx.status = 200 + else if (coreStatus.statusCode === StatusCode.GOOD) ctx.status = 200 + else assertNever(coreStatus.statusCode) + + if (ctx.status !== 200) { + ctx.body = `Status: ${StatusCode[coreStatus.statusCode]}, messages: ${coreStatus.messages.join(', ')}` + } else { + ctx.body = 'OK' + } + }) + + router.get('/readyz', async (ctx) => { + if (!this.coreHandler.connectedToCore) { + ctx.status = 503 + ctx.body = 'Not connected to Core' + return + } + // else + ctx.status = 200 + ctx.body = 'READY' + }) + + this.app.use(router.routes()).use(router.allowedMethods()) + this.app.listen(this.config.port) + } +} diff --git a/packages/yarn.lock b/packages/yarn.lock index a1b2774e80..883f845e84 100644 --- a/packages/yarn.lock +++ b/packages/yarn.lock @@ -4209,6 +4209,18 @@ __metadata: languageName: node linkType: hard +"@koa/router@npm:^14.0.0": + version: 14.0.0 + resolution: "@koa/router@npm:14.0.0" + dependencies: + debug: "npm:^4.4.1" + http-errors: "npm:^2.0.0" + koa-compose: "npm:^4.1.0" + path-to-regexp: "npm:^8.2.0" + checksum: 10/f5f9bedd4c163ad376bcf9626ebb13f35febc44c1f81545ee5efaceb67324e3caf476f9d2a966b4590cac41ab9994b1bcb11f050afbdccd6343f27f31758ff68 + languageName: node + linkType: hard + "@kwsites/file-exists@npm:^1.1.1": version: 1.1.1 resolution: "@kwsites/file-exists@npm:1.1.1" @@ -6359,10 +6371,14 @@ __metadata: version: 0.0.0-use.local resolution: "@sofie-automation/server-core-integration@workspace:server-core-integration" dependencies: + "@koa/router": "npm:^14.0.0" "@sofie-automation/shared-lib": "npm:1.53.0-in-development" + "@types/koa": "npm:^3.0.0" + "@types/koa__router": "npm:^12.0.4" ejson: "npm:^2.2.3" faye-websocket: "npm:^0.11.4" got: "npm:^11.8.6" + koa: "npm:^3.0.1" tslib: "npm:^2.8.1" underscore: "npm:^1.13.7" languageName: unknown @@ -7169,6 +7185,15 @@ __metadata: languageName: node linkType: hard +"@types/accepts@npm:*": + version: 1.3.7 + resolution: "@types/accepts@npm:1.3.7" + dependencies: + "@types/node": "npm:*" + checksum: 10/7678cf74976e16093aff6e6f9755826faf069ac1e30179276158ce46ea246348ff22ca6bdd46cef08428881337d9ceefbf00bab08a7731646eb9fc9449d6a1e7 + languageName: node + linkType: hard + "@types/acorn@npm:^4.0.0": version: 4.0.6 resolution: "@types/acorn@npm:4.0.6" @@ -7303,6 +7328,25 @@ __metadata: languageName: node linkType: hard +"@types/content-disposition@npm:*": + version: 0.5.9 + resolution: "@types/content-disposition@npm:0.5.9" + checksum: 10/d895703c0027ca6c4c0c1ede363909dbb590deec3b206a84b88a49c8b2840bcbd31b4ddeb2e1e6caa1af00801cc79c5b69a5c75a8152f2959810b10fe75a4e1f + languageName: node + linkType: hard + +"@types/cookies@npm:*": + version: 0.9.1 + resolution: "@types/cookies@npm:0.9.1" + dependencies: + "@types/connect": "npm:*" + "@types/express": "npm:*" + "@types/keygrip": "npm:*" + "@types/node": "npm:*" + checksum: 10/512635d55e25fb113412a8f0edc59a00f68ab79646ce19ebd95f8e97815f9ea506b69ec61da12e024565bb03d238fbb7983805c53a6f8d92684c45869a9ac7cf + languageName: node + linkType: hard + "@types/d3-array@npm:*": version: 3.2.1 resolution: "@types/d3-array@npm:3.2.1" @@ -7741,6 +7785,13 @@ __metadata: languageName: node linkType: hard +"@types/http-assert@npm:*": + version: 1.5.6 + resolution: "@types/http-assert@npm:1.5.6" + checksum: 10/dfe1010164ba633859d90a50c4c53e69a38a16972061ef614acc1b0bdb7e53a1c923a11b4169a4a7eedc20b2303962d761727a212ae099717327cf4f38293817 + languageName: node + linkType: hard + "@types/http-cache-semantics@npm:*, @types/http-cache-semantics@npm:^4.0.2": version: 4.0.4 resolution: "@types/http-cache-semantics@npm:4.0.4" @@ -7755,6 +7806,13 @@ __metadata: languageName: node linkType: hard +"@types/http-errors@npm:^2": + version: 2.0.5 + resolution: "@types/http-errors@npm:2.0.5" + checksum: 10/a88da669366bc483e8f3b3eb3d34ada5f8d13eeeef851b1204d77e2ba6fc42aba4566d877cca5c095204a3f4349b87fe397e3e21288837bdd945dd514120755b + languageName: node + linkType: hard + "@types/http-proxy@npm:^1.17.8": version: 1.17.12 resolution: "@types/http-proxy@npm:1.17.12" @@ -7817,6 +7875,13 @@ __metadata: languageName: node linkType: hard +"@types/keygrip@npm:*": + version: 1.0.6 + resolution: "@types/keygrip@npm:1.0.6" + checksum: 10/d157f60bf920492347791d2b26d530d5069ce05796549fbacd4c24d66ffbebbcb0ab67b21e7a1b80a593b9fd4b67dc4843dec04c12bbc2e0fddfb8577a826c41 + languageName: node + linkType: hard + "@types/keyv@npm:^3.1.4": version: 3.1.4 resolution: "@types/keyv@npm:3.1.4" @@ -7826,6 +7891,40 @@ __metadata: languageName: node linkType: hard +"@types/koa-compose@npm:*": + version: 3.2.8 + resolution: "@types/koa-compose@npm:3.2.8" + dependencies: + "@types/koa": "npm:*" + checksum: 10/95c32bdee738ac7c10439bbf6342ca3b9f0aafd7e8118739eac7fb0fa703a23cfe4c88f63e13a69a16fbde702e0bcdc62b272aa734325fc8efa7e5625479752e + languageName: node + linkType: hard + +"@types/koa@npm:*, @types/koa@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/koa@npm:3.0.0" + dependencies: + "@types/accepts": "npm:*" + "@types/content-disposition": "npm:*" + "@types/cookies": "npm:*" + "@types/http-assert": "npm:*" + "@types/http-errors": "npm:^2" + "@types/keygrip": "npm:*" + "@types/koa-compose": "npm:*" + "@types/node": "npm:*" + checksum: 10/db461810b71afe7b73dc849c0832669d1d838edd15b81b0c07d37d32545620dfb0efc19cc120485f5e06a805a151cd89696eb53a5bfad8e223d7e593080069cb + languageName: node + linkType: hard + +"@types/koa__router@npm:^12.0.4": + version: 12.0.4 + resolution: "@types/koa__router@npm:12.0.4" + dependencies: + "@types/koa": "npm:*" + checksum: 10/c01311980bf9a921b77cca5a93cc85522a6d13fe49575e6190fa80407a60237e7351d99a399316dda3119641d498f5d8236b905cd3b4f54fad2c0839ab655dd4 + languageName: node + linkType: hard + "@types/lodash@npm:^4.14.168": version: 4.14.198 resolution: "@types/lodash@npm:4.14.198" @@ -8710,7 +8809,7 @@ __metadata: languageName: node linkType: hard -"accepts@npm:~1.3.4, accepts@npm:~1.3.5, accepts@npm:~1.3.8": +"accepts@npm:^1.3.8, accepts@npm:~1.3.4, accepts@npm:~1.3.5, accepts@npm:~1.3.8": version: 1.3.8 resolution: "accepts@npm:1.3.8" dependencies: @@ -11208,7 +11307,7 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard -"content-disposition@npm:0.5.4": +"content-disposition@npm:0.5.4, content-disposition@npm:~0.5.4": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" dependencies: @@ -11217,7 +11316,7 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard -"content-type@npm:^1.0.4, content-type@npm:~1.0.4": +"content-type@npm:^1.0.4, content-type@npm:^1.0.5, content-type@npm:~1.0.4": version: 1.0.5 resolution: "content-type@npm:1.0.5" checksum: 10/585847d98dc7fb8035c02ae2cb76c7a9bd7b25f84c447e5ed55c45c2175e83617c8813871b4ee22f368126af6b2b167df655829007b21aa10302873ea9c62662 @@ -11352,6 +11451,16 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard +"cookies@npm:~0.9.1": + version: 0.9.1 + resolution: "cookies@npm:0.9.1" + dependencies: + depd: "npm:~2.0.0" + keygrip: "npm:~1.1.0" + checksum: 10/4816461a38d907b20f3fb7a2bc4741fe580e7a195f3e248ef7025cb3be56a07638a0f4e72553a5f535554ca30172c8a3245c63ac72c9737cec034e9a47773392 + languageName: node + linkType: hard + "copy-text-to-clipboard@npm:^3.2.0": version: 3.2.0 resolution: "copy-text-to-clipboard@npm:3.2.0" @@ -12526,6 +12635,18 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard +"debug@npm:^4.4.1": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10/9ada3434ea2993800bd9a1e320bd4aa7af69659fb51cca685d390949434bc0a8873c21ed7c9b852af6f2455a55c6d050aa3937d52b3c69f796dab666f762acad + languageName: node + linkType: hard + "debuglog@npm:^1.0.1": version: 1.0.1 resolution: "debuglog@npm:1.0.1" @@ -12608,6 +12729,13 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard +"deep-equal@npm:~1.0.1": + version: 1.0.1 + resolution: "deep-equal@npm:1.0.1" + checksum: 10/cbecc071afb2891334ced9e9de5834889b9a9992ae8d8369b7eb74c513529eb6d1f6c04d4e2b5f34d8386f7816cd7a6cda45edff847695faea45e43c23973f45 + languageName: node + linkType: hard + "deep-extend@npm:0.6.0, deep-extend@npm:^0.6.0": version: 0.6.0 resolution: "deep-extend@npm:0.6.0" @@ -12757,7 +12885,7 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard -"depd@npm:2.0.0": +"depd@npm:2.0.0, depd@npm:~2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: 10/c0c8ff36079ce5ada64f46cc9d6fd47ebcf38241105b6e0c98f412e8ad91f084bcf906ff644cc3a4bd876ca27a62accb8b0fff72ea6ed1a414b89d8506f4a5ca @@ -12802,7 +12930,7 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard -"destroy@npm:1.2.0": +"destroy@npm:1.2.0, destroy@npm:^1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" checksum: 10/0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38 @@ -13361,6 +13489,13 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard +"encodeurl@npm:^2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 10/abf5cd51b78082cf8af7be6785813c33b6df2068ce5191a40ca8b1afe6a86f9230af9a9ce694a5ce4665955e5c1120871826df9c128a642e09c58d592e2807fe + languageName: node + linkType: hard + "encodeurl@npm:~1.0.2": version: 1.0.2 resolution: "encodeurl@npm:1.0.2" @@ -15030,7 +15165,7 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard -"fresh@npm:0.5.2": +"fresh@npm:0.5.2, fresh@npm:~0.5.2": version: 0.5.2 resolution: "fresh@npm:0.5.2" checksum: 10/64c88e489b5d08e2f29664eb3c79c705ff9a8eb15d3e597198ef76546d4ade295897a44abb0abd2700e7ef784b2e3cbf1161e4fbf16f59129193fd1030d16da1 @@ -16239,6 +16374,16 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard +"http-assert@npm:^1.5.0": + version: 1.5.0 + resolution: "http-assert@npm:1.5.0" + dependencies: + deep-equal: "npm:~1.0.1" + http-errors: "npm:~1.8.0" + checksum: 10/69c9b3c14cf8b2822916360a365089ce936c883c49068f91c365eccba5c141a9964d19fdda589150a480013bf503bf37d8936c732e9635819339e730ab0e7527 + languageName: node + linkType: hard + "http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.0, http-cache-semantics@npm:^4.1.1": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" @@ -16253,7 +16398,7 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard -"http-errors@npm:2.0.0": +"http-errors@npm:2.0.0, http-errors@npm:^2.0.0": version: 2.0.0 resolution: "http-errors@npm:2.0.0" dependencies: @@ -16278,6 +16423,19 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard +"http-errors@npm:~1.8.0": + version: 1.8.1 + resolution: "http-errors@npm:1.8.1" + dependencies: + depd: "npm:~1.1.2" + inherits: "npm:2.0.4" + setprototypeof: "npm:1.2.0" + statuses: "npm:>= 1.5.0 < 2" + toidentifier: "npm:1.0.1" + checksum: 10/76fc491bd8df2251e21978e080d5dae20d9736cfb29bb72b5b76ec1bcebb1c14f0f58a3a128dd89288934379d2173cfb0421c571d54103e93dd65ef6243d64d8 + languageName: node + linkType: hard + "http-headers@npm:^3.0.2": version: 3.0.2 resolution: "http-headers@npm:3.0.2" @@ -18539,6 +18697,15 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard +"keygrip@npm:~1.1.0": + version: 1.1.0 + resolution: "keygrip@npm:1.1.0" + dependencies: + tsscmp: "npm:1.0.6" + checksum: 10/078cd16a463d187121f0a27c1c9c95c52ad392b620f823431689f345a0501132cee60f6e96914b07d570105af470b96960402accd6c48a0b1f3cd8fac4fa2cae + languageName: node + linkType: hard + "keyv@npm:^4.0.0, keyv@npm:^4.5.3, keyv@npm:^4.5.4": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -18576,6 +18743,39 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard +"koa-compose@npm:^4.1.0": + version: 4.1.0 + resolution: "koa-compose@npm:4.1.0" + checksum: 10/46cb16792d96425e977c2ae4e5cb04930280740e907242ec9c25e3fb8b4a1d7b54451d7432bc24f40ec62255edea71894d2ceeb8238501842b4e48014f2e83db + languageName: node + linkType: hard + +"koa@npm:^3.0.1": + version: 3.0.1 + resolution: "koa@npm:3.0.1" + dependencies: + accepts: "npm:^1.3.8" + content-disposition: "npm:~0.5.4" + content-type: "npm:^1.0.5" + cookies: "npm:~0.9.1" + delegates: "npm:^1.0.0" + destroy: "npm:^1.2.0" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + fresh: "npm:~0.5.2" + http-assert: "npm:^1.5.0" + http-errors: "npm:^2.0.0" + koa-compose: "npm:^4.1.0" + mime-types: "npm:^3.0.1" + on-finished: "npm:^2.4.1" + parseurl: "npm:^1.3.3" + statuses: "npm:^2.0.1" + type-is: "npm:^2.0.1" + vary: "npm:^1.1.2" + checksum: 10/0e56f77f7192c10be6a3f5c4b248ec10b9b223e6894065a431df3c5c425db439fcc28ead29e7145087974550b51538790aafee6aeb5b0e26a32307d02a52bd41 + languageName: node + linkType: hard + "kolorist@npm:^1.8.0": version: 1.8.0 resolution: "kolorist@npm:1.8.0" @@ -19789,6 +19989,13 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard +"media-typer@npm:^1.1.0": + version: 1.1.0 + resolution: "media-typer@npm:1.1.0" + checksum: 10/a58dd60804df73c672942a7253ccc06815612326dc1c0827984b1a21704466d7cde351394f47649e56cf7415e6ee2e26e000e81b51b3eebb5a93540e8bf93cbd + languageName: node + linkType: hard + "memfs@npm:^3.1.2, memfs@npm:^3.4.3": version: 3.5.3 resolution: "memfs@npm:3.5.3" @@ -20437,6 +20644,13 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard +"mime-db@npm:^1.54.0": + version: 1.54.0 + resolution: "mime-db@npm:1.54.0" + checksum: 10/9e7834be3d66ae7f10eaa69215732c6d389692b194f876198dca79b2b90cbf96688d9d5d05ef7987b20f749b769b11c01766564264ea5f919c88b32a29011311 + languageName: node + linkType: hard + "mime-db@npm:~1.33.0": version: 1.33.0 resolution: "mime-db@npm:1.33.0" @@ -20462,6 +20676,15 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard +"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1": + version: 3.0.1 + resolution: "mime-types@npm:3.0.1" + dependencies: + mime-db: "npm:^1.54.0" + checksum: 10/fa1d3a928363723a8046c346d87bf85d35014dae4285ad70a3ff92bd35957992b3094f8417973cfe677330916c6ef30885109624f1fb3b1e61a78af509dba120 + languageName: node + linkType: hard + "mime@npm:1.6.0": version: 1.6.0 resolution: "mime@npm:1.6.0" @@ -21968,7 +22191,7 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard -"on-finished@npm:2.4.1": +"on-finished@npm:2.4.1, on-finished@npm:^2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" dependencies: @@ -22766,7 +22989,7 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard -"parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": +"parseurl@npm:^1.3.3, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" checksum: 10/407cee8e0a3a4c5cd472559bca8b6a45b82c124e9a4703302326e9ab60fc1081442ada4e02628efef1eb16197ddc7f8822f5a91fd7d7c86b51f530aedb17dfa2 @@ -22920,6 +23143,13 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard +"path-to-regexp@npm:^8.2.0": + version: 8.3.0 + resolution: "path-to-regexp@npm:8.3.0" + checksum: 10/568f148fc64f5fd1ecebf44d531383b28df924214eabf5f2570dce9587a228e36c37882805ff02d71c6209b080ea3ee6a4d2b712b5df09741b67f1f3cf91e55a + languageName: node + linkType: hard + "path-type@npm:^3.0.0": version: 3.0.0 resolution: "path-type@npm:3.0.0" @@ -27176,13 +27406,20 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard -"statuses@npm:>= 1.4.0 < 2": +"statuses@npm:>= 1.4.0 < 2, statuses@npm:>= 1.5.0 < 2": version: 1.5.0 resolution: "statuses@npm:1.5.0" checksum: 10/c469b9519de16a4bb19600205cffb39ee471a5f17b82589757ca7bd40a8d92ebb6ed9f98b5a540c5d302ccbc78f15dc03cc0280dd6e00df1335568a5d5758a5c languageName: node linkType: hard +"statuses@npm:^2.0.1": + version: 2.0.2 + resolution: "statuses@npm:2.0.2" + checksum: 10/6927feb50c2a75b2a4caab2c565491f7a93ad3d8dbad7b1398d52359e9243a20e2ebe35e33726dee945125ef7a515e9097d8a1b910ba2bbd818265a2f6c39879 + languageName: node + linkType: hard + "std-env@npm:^3.7.0": version: 3.8.0 resolution: "std-env@npm:3.8.0" @@ -28340,6 +28577,13 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard +"tsscmp@npm:1.0.6": + version: 1.0.6 + resolution: "tsscmp@npm:1.0.6" + checksum: 10/850405080ea3ecb158e9e01bc4e87c9edb94a829d8ad8747f30ba103fcc41a287d7949ab84d7b27c36294036a2c9878f050db15b73a1a1961abfb7688b82ac53 + languageName: node + linkType: hard + "tty-browserify@npm:0.0.1": version: 0.0.1 resolution: "tty-browserify@npm:0.0.1" @@ -28462,6 +28706,17 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard +"type-is@npm:^2.0.1": + version: 2.0.1 + resolution: "type-is@npm:2.0.1" + dependencies: + content-type: "npm:^1.0.5" + media-typer: "npm:^1.1.0" + mime-types: "npm:^3.0.0" + checksum: 10/bacdb23c872dacb7bd40fbd9095e6b2fca2895eedbb689160c05534d7d4810a7f4b3fd1ae87e96133c505958f6d602967a68db5ff577b85dd6be76eaa75d58af + languageName: node + linkType: hard + "type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" @@ -29282,7 +29537,7 @@ asn1@evs-broadcast/node-asn1: languageName: node linkType: hard -"vary@npm:~1.1.2": +"vary@npm:^1.1.2, vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" checksum: 10/31389debef15a480849b8331b220782230b9815a8e0dbb7b9a8369559aed2e9a7800cd904d4371ea74f4c3527db456dc8e7ac5befce5f0d289014dbdf47b2242