From e794ca7dee8e6743b51b7ca35ce04591dcc16329 Mon Sep 17 00:00:00 2001 From: ianshade Date: Wed, 20 Sep 2023 15:14:58 +0200 Subject: [PATCH] wip: realtime api using feathers.js --- apps/app/package.json | 6 + .../{IPCClient.ts => ClientEventBus.ts} | 36 +- apps/app/src/electron/HTTPAPI.ts | 118 ---- apps/app/src/electron/IPCServer.ts | 25 +- apps/app/src/electron/SuperConductor.ts | 63 +- apps/app/src/electron/api/ApiServer.ts | 64 ++ apps/app/src/electron/api/LegacyService.ts | 27 + apps/app/src/electron/api/RundownService.ts | 57 ++ apps/app/src/electron/storageHandler.ts | 12 +- apps/app/src/electron/triggersHandler.ts | 4 +- apps/app/src/ipc/IPCAPI.ts | 13 +- apps/app/src/main.ts | 3 +- apps/app/src/react/App.tsx | 12 +- .../react/api/{IPCServer.ts => ApiClient.ts} | 62 +- .../{IPCClient.ts => RealtimeDataProvider.ts} | 23 +- apps/app/src/react/api/clipboard/lib.ts | 4 +- apps/app/src/react/api/logger.ts | 4 +- .../pages/newRundownPage/NewRundownPage.tsx | 4 +- apps/app/src/react/contexts/IPCServer.ts | 4 +- apps/app/src/react/mobx/AnalogStore.ts | 14 +- apps/app/src/react/mobx/AppStore.ts | 13 +- .../src/react/mobx/GDDValidatorStoreStore.ts | 8 +- apps/app/src/react/mobx/GroupPlayDataStore.ts | 14 +- apps/app/src/react/mobx/GuiStore.ts | 6 +- .../react/mobx/ResourcesAndMetadataStore.ts | 14 +- apps/app/src/react/mobx/RundownsStore.ts | 70 ++- apps/app/src/react/mobx/TriggersStore.ts | 13 +- yarn.lock | 553 +++++++++++++++++- 28 files changed, 933 insertions(+), 313 deletions(-) rename apps/app/src/electron/{IPCClient.ts => ClientEventBus.ts} (56%) delete mode 100644 apps/app/src/electron/HTTPAPI.ts create mode 100644 apps/app/src/electron/api/ApiServer.ts create mode 100644 apps/app/src/electron/api/LegacyService.ts create mode 100644 apps/app/src/electron/api/RundownService.ts rename apps/app/src/react/api/{IPCServer.ts => ApiClient.ts} (86%) rename apps/app/src/react/api/{IPCClient.ts => RealtimeDataProvider.ts} (87%) diff --git a/apps/app/package.json b/apps/app/package.json index e0fbc3b6..3ff616b3 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -67,6 +67,11 @@ "@babel/core": "^7.22.1", "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", + "@feathersjs/errors": "^5.0.8", + "@feathersjs/feathers": "^5.0.8", + "@feathersjs/koa": "^5.0.8", + "@feathersjs/socketio": "^5.0.8", + "@feathersjs/socketio-client": "^5.0.8", "@fontsource/barlow": "^4.5.9", "@fontsource/barlow-condensed": "^4.5.9", "@fontsource/barlow-semi-condensed": "^4.5.10", @@ -110,6 +115,7 @@ "react-visibility-sensor": "^5.1.1", "semver": "^7.5.0", "short-uuid": "^4.2.2", + "socket.io-client": "^4.7.2", "superfly-timeline": "^8.3.1", "timeline-state-resolver-types": "7.5.0-nightly-release47-20221116-134940-9a43f95c5.0", "tiny-warning": "^1.0.3", diff --git a/apps/app/src/electron/IPCClient.ts b/apps/app/src/electron/ClientEventBus.ts similarity index 56% rename from apps/app/src/electron/IPCClient.ts rename to apps/app/src/electron/ClientEventBus.ts index ada535fc..386b332a 100644 --- a/apps/app/src/electron/IPCClient.ts +++ b/apps/app/src/electron/ClientEventBus.ts @@ -3,7 +3,6 @@ import { Project } from '../models/project/Project' import { MetadataAny, ResourceAny, ResourceId, SerializedProtectedMap, TSRDeviceId } from '@shared/models' import { Rundown } from '../models/rundown/Rundown' import { PeripheralStatus } from '../models/project/Peripheral' -import { BrowserWindow } from 'electron' import { IPCClientMethods, SystemMessageOptions } from '../ipc/IPCAPI' import { AppData } from '../models/App/AppData' import { ActiveTriggers } from '../models/rundown/Trigger' @@ -12,58 +11,57 @@ import { ActiveAnalog } from '../models/rundown/Analog' import { AnalogInput } from '../models/project/AnalogInput' import { BridgeId } from '@shared/api' import { BridgePeripheralId } from '@shared/lib' +import { EventEmitter } from 'stream' -/** This class is used server-side, to send messages to the client */ -export class IPCClient implements IPCClientMethods { - constructor(private mainWindow: BrowserWindow) {} - +// --- some of it might be needed, most of it hopefully not +export class ClientEventBus extends EventEmitter implements IPCClientMethods { close(): void { // Nothing here } systemMessage(message: string, options: SystemMessageOptions): void { - this.mainWindow?.webContents.send('callMethod', 'systemMessage', message, options) + this.emit('callMethod', 'systemMessage', message, options) } updateAppData(appData: AppData): void { - this.mainWindow?.webContents.send('callMethod', 'updateAppData', appData) + this.emit('callMethod', 'updateAppData', appData) } updateProject(project: Project): void { - this.mainWindow?.webContents.send('callMethod', 'updateProject', project) + this.emit('callMethod', 'updateProject', project) } updateRundown(fileName: string, rundown: Rundown): void { - this.mainWindow?.webContents.send('callMethod', 'updateRundown', fileName, rundown) + this.emit('callMethod', 'updateRundown', fileName, rundown) } updateResourcesAndMetadata( resources: Array<{ id: ResourceId; resource: ResourceAny | null }>, metadata: SerializedProtectedMap ): void { - this.mainWindow?.webContents.send('callMethod', 'updateResourcesAndMetadata', resources, metadata) + this.emit('callMethod', 'updateResourcesAndMetadata', resources, metadata) } updateBridgeStatus(id: BridgeId, status: BridgeStatus | null): void { - this.mainWindow?.webContents.send('callMethod', 'updateBridgeStatus', id, status) + this.emit('callMethod', 'updateBridgeStatus', id, status) } updatePeripheral(peripheralId: BridgePeripheralId, peripheral: PeripheralStatus | null): void { - this.mainWindow?.webContents.send('callMethod', 'updatePeripheral', peripheralId, peripheral) + this.emit('callMethod', 'updatePeripheral', peripheralId, peripheral) } updatePeripheralTriggers(peripheralTriggers: ActiveTriggers): void { - this.mainWindow?.webContents.send('callMethod', 'updatePeripheralTriggers', peripheralTriggers) + this.emit('callMethod', 'updatePeripheralTriggers', peripheralTriggers) } updatePeripheralAnalog(fullIdentifier: string, analog: ActiveAnalog | null): void { - this.mainWindow?.webContents.send('callMethod', 'updatePeripheralAnalog', fullIdentifier, analog) + this.emit('callMethod', 'updatePeripheralAnalog', fullIdentifier, analog) } updateDeviceRefreshStatus(deviceId: TSRDeviceId, refreshing: boolean): void { - this.mainWindow?.webContents.send('callMethod', 'updateDeviceRefreshStatus', deviceId, refreshing) + this.emit('callMethod', 'updateDeviceRefreshStatus', deviceId, refreshing) } displayAboutDialog(): void { - this.mainWindow?.webContents.send('callMethod', 'displayAboutDialog') + this.emit('callMethod', 'displayAboutDialog') } updateDefiningArea(definingArea: DefiningArea | null): void { - this.mainWindow?.webContents.send('callMethod', 'updateDefiningArea', definingArea) + this.emit('callMethod', 'updateDefiningArea', definingArea) } updateFailedGlobalTriggers(identifiers: string[]): void { - this.mainWindow?.webContents.send('callMethod', 'updateFailedGlobalTriggers', identifiers) + this.emit('callMethod', 'updateFailedGlobalTriggers', identifiers) } updateAnalogInput(fullIdentifier: string, analogInput: AnalogInput | null): void { - this.mainWindow?.webContents.send('callMethod', 'updateAnalogInput', fullIdentifier, analogInput) + this.emit('callMethod', 'updateAnalogInput', fullIdentifier, analogInput) } } diff --git a/apps/app/src/electron/HTTPAPI.ts b/apps/app/src/electron/HTTPAPI.ts deleted file mode 100644 index 59a4e5fc..00000000 --- a/apps/app/src/electron/HTTPAPI.ts +++ /dev/null @@ -1,118 +0,0 @@ -import Koa from 'koa' -import Router from '@koa/router' -import bodyParser from 'koa-bodyparser' -import { IPCServer, isUndoable } from './IPCServer' -import { stringifyError } from '@shared/lib' -import { LoggerLike } from '@shared/api' - -export class HTTPAPI { - private app = new Koa() - private router = new Router() - - private methodSignatures: { - [fullEndpoint: string]: { - endpoint: string - type: string - } - } = {} - - constructor(port: number, ipcServer: IPCServer, log: LoggerLike) { - this.app.use(bodyParser()) - this.router.get(`/`, async (ctx) => { - ctx.response.body = ` - Internal API (unstable) - ` - ctx.response.status = 200 - }) - this.router.get(`/api/internal`, async (ctx) => { - const methods = Object.entries<{ endpoint: string; type: string }>(this.methodSignatures) - .map(([_fullEndpoint, e]) => { - const url = `/api/internal/${e.endpoint}` - return `${e.type} ${url}` - }) - .join('
\n') - - ctx.response.body = ` -

Send request parameters as a JSON body.

- ${methods} - ` - ctx.response.status = 200 - }) - - for (const methodName of Object.getOwnPropertyNames(IPCServer.prototype)) { - // Ignore "private" methods. - if (methodName[0] === '_') continue - if (methodName === 'constructor') continue - - const originalMethod = (ipcServer as any)[methodName] - // Ignore methods that don't exist. - if (!originalMethod) continue - - const fcn = originalMethod.bind(ipcServer) - - let endpoint: string - let endpointType: 'GET' | 'POST' | 'DELETE' - - if (methodName.startsWith('delete')) { - // Handle DELETE requests - - endpoint = methodName.charAt(6).toLocaleLowerCase() + methodName.substring(7) - endpointType = 'DELETE' - this.router.delete(`/api/internal/${endpoint}`, async (ctx) => { - try { - const result = await fcn(ctx.request.body) - if (isUndoable(result)) { - ctx.response.body = result.result - } else { - ctx.response.body = result - } - ctx.response.status = 200 - } catch (error) { - const stringifiedError = stringifyError(error) - if (stringifiedError.match(/not found/i)) { - ctx.response.status = 404 - } else { - ctx.response.status = 500 - } - } - }) - } else { - // Handle POST requests - - endpoint = methodName - endpointType = 'POST' - this.router.post(`/api/internal/${endpoint}`, async (ctx) => { - try { - const result = await fcn(ctx.request.body) - if (isUndoable(result)) { - ctx.response.body = result.result - } else { - ctx.response.body = result - } - ctx.response.status = 200 - } catch (error) { - const stringifiedError = stringifyError(error) - if (stringifiedError.match(/not found/i)) { - ctx.response.status = 404 - } else { - ctx.response.status = 500 - } - } - }) - } - - const fullEndpoint = `${endpointType} ${endpoint}` - if (this.methodSignatures[fullEndpoint]) { - throw new Error(`Duplicate API endpoints "${fullEndpoint}"!`) - } - this.methodSignatures[fullEndpoint] = { - type: endpointType, - endpoint, - } - } - - this.app.use(this.router.routes()).use(this.router.allowedMethods()) - this.app.listen(port) - log.info(`Internal HTTP API available at http://localhost:${port}/api/internal`) - } -} diff --git a/apps/app/src/electron/IPCServer.ts b/apps/app/src/electron/IPCServer.ts index a7cd37bc..4c8c6cc4 100644 --- a/apps/app/src/electron/IPCServer.ts +++ b/apps/app/src/electron/IPCServer.ts @@ -123,7 +123,7 @@ type ConvertToServerSide = { } /** This class is used server-side, to handle requests from the client */ -export class IPCServer +export class EverythingService extends (EventEmitter as new () => TypedEmitter) implements ConvertToServerSide { @@ -153,7 +153,7 @@ export class IPCServer } ) { super() - for (const methodName of Object.getOwnPropertyNames(IPCServer.prototype)) { + for (const methodName of Object.getOwnPropertyNames(EverythingService.prototype)) { if (methodName[0] !== '_') { const fcn = (this as any)[methodName].bind(this) if (fcn) { @@ -324,9 +324,6 @@ export class IPCServer this.callbacks.onClientConnected() } - async triggerSendRundown(arg: { rundownId: string }): Promise { - this.storage.triggerEmitRundown(arg.rundownId) - } async setKeyboardKeys(arg: { activeKeys: ActiveTrigger[] }): Promise { this.callbacks.setKeyboardKeys(arg.activeKeys) } @@ -415,23 +412,26 @@ export class IPCServer return this.storage.openProject(arg.projectId) } - async playPart(arg: { rundownId: string; groupId: string; partId: string }): Promise { + async playPart(arg: { rundownId: string; groupId: string; partId: string }): Promise { const now = Date.now() const { rundown, group, part } = this.getPart(arg) RundownActions.playPart(group, part, now) this._saveUpdates({ rundownId: arg.rundownId, rundown, group }) + return rundown // this should return something more granular (group or part) } - async pausePart(arg: { rundownId: string; groupId: string; partId: string; time?: number }): Promise { + async pausePart(arg: { rundownId: string; groupId: string; partId: string; time?: number }): Promise { const now = Date.now() const { rundown, group, part } = this.getPart(arg) updateGroupPlayingParts(group) RundownActions.pausePart(group, part, arg.time, now) this._saveUpdates({ rundownId: arg.rundownId, rundown, group }) + return rundown } + async pauseParts(arg: { rundownId: string; groupId: string; partIds: string[]; time?: number }): Promise { const now = Date.now() const { rundown, group } = this.getGroup({ @@ -451,14 +451,16 @@ export class IPCServer this._saveUpdates({ rundownId: arg.rundownId, rundown, group }) } - async stopPart(arg: { rundownId: string; groupId: string; partId: string }): Promise { + async stopPart(arg: { rundownId: string; groupId: string; partId: string }): Promise { const now = Date.now() const { rundown, group } = this.getGroup(arg) RundownActions.stopPart(group, arg.partId, now) this._saveUpdates({ rundownId: arg.rundownId, rundown, group }) + return rundown } + async setPartTrigger(arg: { rundownId: string groupId: string @@ -2200,8 +2202,9 @@ export class IPCServer async updateProject(arg: { id: string; project: Project }): Promise { this._saveUpdates({ project: arg.project }) } - async newRundown(arg: { name: string }): Promise> { - const fileName = this.storage.newRundown(arg.name) + async newRundown(arg: { name: string }): Promise> { + const rundown = this.storage.newRundown(arg.name) + const fileName = rundown.name this._saveUpdates({}) return { @@ -2210,7 +2213,7 @@ export class IPCServer this._saveUpdates({}) }, description: ActionDescription.NewRundown, - result: fileName, + result: rundown, } } async deleteRundown(arg: { rundownId: string }): Promise { diff --git a/apps/app/src/electron/SuperConductor.ts b/apps/app/src/electron/SuperConductor.ts index ac8d8aff..a41cec7a 100644 --- a/apps/app/src/electron/SuperConductor.ts +++ b/apps/app/src/electron/SuperConductor.ts @@ -1,8 +1,8 @@ import { BrowserWindow, dialog, ipcMain } from 'electron' import { autoUpdater } from 'electron-updater' import { AutoFillMode, Group } from '../models/rundown/Group' -import { IPCServer } from './IPCServer' -import { IPCClient } from './IPCClient' +import { EverythingService } from './IPCServer' +import { ClientEventBus } from './ClientEventBus' import { updateTimeline } from './timeline' import { GroupPreparedPlayData } from '../models/GUI/PreparedPlayhead' import { StorageHandler } from './storageHandler' @@ -36,16 +36,17 @@ import { postProcessPart } from './rundown' import { BridgePeripheralId, assertNever } from '@shared/lib' import { TelemetryHandler } from './telemetry' import { USER_AGREEMENT_VERSION } from '../lib/userAgreement' -import { HTTPAPI } from './HTTPAPI' +import { ApiServer } from './api/ApiServer' import { ActiveAnalog } from '../models/rundown/Analog' import { AnalogHandler } from './analogHandler' import { AnalogInput } from '../models/project/AnalogInput' import { SystemMessageOptions } from '../ipc/IPCAPI' export class SuperConductor { - ipcServer: IPCServer - clients: { ipcClient: IPCClient; window: BrowserWindow }[] = [] - httpAPI?: HTTPAPI + ipcServer: EverythingService + windows: { window: BrowserWindow }[] = [] + clientEventBus: ClientEventBus + httpAPI?: ApiServer session: SessionHandler storage: StorageHandler @@ -71,25 +72,26 @@ export class SuperConductor { constructor(private log: LoggerLike, private renderLog: LoggerLike) { this.session = new SessionHandler() + this.clientEventBus = new ClientEventBus() this.session.on('bridgeStatus', (id: BridgeId, status: BridgeStatus | null) => { - this.clients.forEach((clients) => clients.ipcClient.updateBridgeStatus(id, status)) + this.clientEventBus.updateBridgeStatus(id, status) }) this.session.on('peripheral', (peripheralId: BridgePeripheralId, peripheral: PeripheralStatus | null) => { this.triggers.onPeripheralStatus(peripheralId, peripheral) - this.clients.forEach((clients) => clients.ipcClient.updatePeripheral(peripheralId, peripheral)) + this.clientEventBus.updatePeripheral(peripheralId, peripheral) }) this.session.on('activeTriggers', (activeTriggers: ActiveTriggers) => { this.triggers?.updateActiveTriggers(activeTriggers) - this.clients.forEach((clients) => clients.ipcClient.updatePeripheralTriggers(activeTriggers)) + this.clientEventBus.updatePeripheralTriggers(activeTriggers) }) this.session.on('activeAnalog', (fullIdentifier: string, analog: ActiveAnalog | null) => { this.analogHandler?.updateActiveAnalog(fullIdentifier, analog) - this.clients.forEach((clients) => clients.ipcClient.updatePeripheralAnalog(fullIdentifier, analog)) + this.clientEventBus.updatePeripheralAnalog(fullIdentifier, analog) }) this.session.on('definingArea', (definingArea: DefiningArea | null) => { this.triggers?.updateDefiningArea(definingArea) - this.clients.forEach((clients) => clients.ipcClient.updateDefiningArea(definingArea)) + this.clientEventBus.updateDefiningArea(definingArea) }) this.session.on('allTrigger', (fullIdentifier: string, trigger: ActiveTrigger | null) => { this.triggers?.registerTrigger(fullIdentifier, trigger) @@ -100,15 +102,15 @@ export class SuperConductor { this.storage = new StorageHandler(log, CURRENT_VERSION) this.storage.on('appData', (appData: AppData) => { - this.clients.forEach((clients) => clients.ipcClient.updateAppData(appData)) + this.clientEventBus.updateAppData(appData) this.triggers.registerGlobalKeyboardTriggers() }) this.storage.on('project', (project: Project) => { - this.clients.forEach((clients) => clients.ipcClient.updateProject(project)) + this.clientEventBus.updateProject(project) this.handleAutoRefresh() }) this.storage.on('rundown', (fileName: string, rundown: Rundown) => { - this.clients.forEach((clients) => clients.ipcClient.updateRundown(fileName, rundown)) + this.clientEventBus.updateRundown(fileName, rundown) this.triggers.registerGlobalKeyboardTriggers() }) this.storage.on('resource', (id: ResourceId, resource: ResourceAny | null) => { @@ -124,7 +126,7 @@ export class SuperConductor { this.triggerHandleAutoFill() }) this.storage.on('analogInput', (fullIdentifier: string, analogInput: AnalogInput | null) => { - this.clients.forEach((clients) => clients.ipcClient.updateAnalogInput(fullIdentifier, analogInput)) + this.clientEventBus.updateAnalogInput(fullIdentifier, analogInput) this.bridgeHandler.updateAnalogInput(analogInput) }) @@ -199,7 +201,7 @@ export class SuperConductor { } }, onVersionMismatch: (bridgeId: BridgeId, bridgeVersion: string, ourVersion: string): void => { - this.clients.forEach((client) => { + this.windows.forEach((client) => { dialog .showMessageBox(client.window, { type: 'warning', @@ -219,11 +221,11 @@ export class SuperConductor { } else { this.refreshStatus.delete(deviceId) } - this.clients.forEach((clients) => clients.ipcClient.updateDeviceRefreshStatus(deviceId, refreshing)) + this.clientEventBus.updateDeviceRefreshStatus(deviceId, refreshing) }, }) - this.ipcServer = new IPCServer(ipcMain, this.log, this.renderLog, this.storage, this, this.session, { + this.ipcServer = new EverythingService(ipcMain, this.log, this.renderLog, this.storage, this, this.session, { refreshResources: () => { this.refreshResources() }, @@ -273,9 +275,7 @@ export class SuperConductor { this.triggers = new TriggersHandler(this.log, this.storage, this.ipcServer, this.bridgeHandler, this.session) this.triggers.on('error', (e) => this.log.error(e)) this.triggers.on('failedGlobalTriggers', (failedGlobalTriggers) => { - this.clients.forEach((client) => - client.ipcClient.updateFailedGlobalTriggers(Array.from(failedGlobalTriggers)) - ) + this.clientEventBus.updateFailedGlobalTriggers(Array.from(failedGlobalTriggers)) }) this.ipcServer.triggers = this.triggers @@ -283,13 +283,14 @@ export class SuperConductor { this.analogHandler.on('error', (e) => this.log.error(e)) if (this.disableInternalHttpApi) { + // TODO: now this becomes an API that also serves the contents of the Electron window - it should not be disabled this.log.info(`Internal HTTP API disabled`) } else { - this.httpAPI = new HTTPAPI(this.internalHttpApiPort, this.ipcServer, this.log) + this.httpAPI = new ApiServer(this.internalHttpApiPort, this.ipcServer, this.clientEventBus, this.log) } } sendSystemMessage(message: string, options: SystemMessageOptions): void { - this.clients.forEach((clients) => clients.ipcClient.systemMessage(message, options)) + this.clientEventBus.systemMessage(message, options) } public setAutoUpdateAllowPrerelease(forceCheckUpdates: boolean): void { const appData = this.storage.getAppData() @@ -324,9 +325,7 @@ export class SuperConductor { serializeProtectedMap(this.metadataUpdatesToSend) if (resourceUpdatesToSend.length || Object.keys(metadataUpdatesToSend).length) { - this.clients.forEach((clients) => - clients.ipcClient.updateResourcesAndMetadata(resourceUpdatesToSend, metadataUpdatesToSend) - ) + this.clientEventBus.updateResourcesAndMetadata(resourceUpdatesToSend, metadataUpdatesToSend) } this.resourceUpdatesToSend.clear() }, 100) @@ -505,24 +504,18 @@ export class SuperConductor { } onNewWindow(window: BrowserWindow): { - ipcClient: IPCClient close: () => void } { - const ipcClient = new IPCClient(window) - const client = { window, - ipcClient, } - this.clients.push(client) + this.windows.push(client) return { - ipcClient, close: () => { - ipcClient.close() - const index = this.clients.findIndex((c) => c === client) - if (index >= 0) this.clients.splice(index) + const index = this.windows.findIndex((c) => c === client) + if (index >= 0) this.windows.splice(index) }, } } diff --git a/apps/app/src/electron/api/ApiServer.ts b/apps/app/src/electron/api/ApiServer.ts new file mode 100644 index 00000000..629d95d0 --- /dev/null +++ b/apps/app/src/electron/api/ApiServer.ts @@ -0,0 +1,64 @@ +import { EverythingService } from '../IPCServer' +import { LoggerLike } from '@shared/api' + +import { HookContext, feathers } from '@feathersjs/feathers' +import { koa, rest, bodyParser, errorHandler, serveStatic, cors } from '@feathersjs/koa' +import socketio from '@feathersjs/socketio' +import { Rundown } from '../../models/rundown/Rundown' +import { ClientEventBus } from '../ClientEventBus' +import { RundownService, RUNDOWN_CHANNEL_PREFIX } from './RundownService' +import { LegacyService } from './LegacyService' + +// --- + +export type ServiceTypes = { + rundowns: RundownService + legacy: EverythingService +} + +export class ApiServer { + private app = koa(feathers()) + + constructor(port: number, ipcServer: EverythingService, clientEventBus: ClientEventBus, log: LoggerLike) { + this.app.use(serveStatic('src')) + + this.app.use( + cors({ + origin: '*', // TODO: cors + }) + ) + + this.app.use(errorHandler()) + this.app.use(bodyParser()) + this.app.configure(rest()) + this.app.configure(socketio({ cors: { origin: '*' } })) // TODO: cors + + this.app.use('rundowns', new RundownService(this.app, ipcServer), { + // TODO: what if we made a base class for Services and made those arrays fields so that they live nearvy the implementation? + methods: ['get', 'create', 'unsubscribe', 'playPart', 'pausePart', 'stopPart'], + serviceEvents: ['created', 'updated', 'deleted'], + }) + + this.app.service('rundowns').publish((data: Rundown, _context: HookContext) => { + return this.app.channel(RUNDOWN_CHANNEL_PREFIX + data.id) + }) + + // --- legacy code, only for a rapid prototype + this.app.use('legacy', new LegacyService(this.app, ipcServer, clientEventBus) as unknown as EverythingService, { + methods: Object.getOwnPropertyNames(EverythingService.prototype).filter( + (methodName) => !methodName.startsWith('_') && methodName !== 'constructor' + ) as (keyof EverythingService)[], + events: ['callMethod'], + }) + this.app.on('connection', (connection) => this.app.channel('everybody').join(connection)) + this.app.service('legacy').publish(() => { + return this.app.channel(`everybody`) + }) + // ---- end legacy code + + this.app + .listen(port) + .then(() => log.info('Feathers server listening on localhost:' + port)) + .catch(log.error) + } +} diff --git a/apps/app/src/electron/api/LegacyService.ts b/apps/app/src/electron/api/LegacyService.ts new file mode 100644 index 00000000..c987c9db --- /dev/null +++ b/apps/app/src/electron/api/LegacyService.ts @@ -0,0 +1,27 @@ +import { EverythingService } from '../IPCServer' +import { Application } from '@feathersjs/feathers' +import { ClientEventBus } from '../ClientEventBus' +import EventEmitter from 'node:events' +import { ServiceTypes } from './ApiServer' + +// --- this is only for a rapid prototype, exposing ALL the IPC over WebSocket +export class LegacyService extends EventEmitter { + constructor(private app: Application, ipcServer: EverythingService, ipcClient: ClientEventBus) { + super() + for (const methodName of Object.getOwnPropertyNames(EverythingService.prototype)) { + // Ignore "private" methods. + if (methodName.startsWith('_')) continue + if (methodName === 'constructor') continue + + const originalMethod = (ipcServer as any)[methodName] + // Ignore methods that don't exist. + if (!originalMethod) continue + const fcn = originalMethod.bind(ipcServer) + ;(this as any)[methodName] = (data: any) => { + console.log('Received', methodName, data) + return fcn(...data) + } + } + ipcClient.on('callMethod', (...args) => this.emit('callMethod', args)) + } +} diff --git a/apps/app/src/electron/api/RundownService.ts b/apps/app/src/electron/api/RundownService.ts new file mode 100644 index 00000000..ff802226 --- /dev/null +++ b/apps/app/src/electron/api/RundownService.ts @@ -0,0 +1,57 @@ +import { EverythingService } from '../IPCServer' +import { Application, Params } from '@feathersjs/feathers' +import { Rundown } from '../../models/rundown/Rundown' +import EventEmitter from 'node:events' +import { GeneralError, NotFound } from '@feathersjs/errors' +import { ServiceTypes } from './ApiServer' + +export const RUNDOWN_CHANNEL_PREFIX = 'rundowns/' +export class RundownService extends EventEmitter { + constructor(private app: Application, private rundownRepository: EverythingService) { + super() + } + + async get(id: string, params: Params): Promise { + // TODO: access control + const rundown = this.rundownRepository.getRundown({ rundownId: id }) + if (!rundown) throw new NotFound() + if (rundown && params.connection) { + this.app.channel(RUNDOWN_CHANNEL_PREFIX + id).join(params.connection) // automatically subscribes to updates + } + return rundown.rundown + } + + async unsubscribe(id: string, params: Params): Promise { + if (params.connection) { + this.app.channel(RUNDOWN_CHANNEL_PREFIX + id).leave(params.connection) + } + } + + async create(data: { name: string }): Promise { + // TODO: access control + const result = await this.rundownRepository.newRundown(data) + if (!result.result) throw new GeneralError() + return result.result + } + + // this could potentially be moved to a PartService or GroupService + async playPart(data: { rundownId: string; groupId: string; partId: string }): Promise { + // TODO: access control + const rundown = await this.rundownRepository.playPart(data) + this.emit('updated', rundown) // TODO: make this more granular + } + + // this too could potentially be moved to a PartService or GroupService + async pausePart(data: { rundownId: string; groupId: string; partId: string }): Promise { + // TODO: access control + const rundown = await this.rundownRepository.pausePart(data) + this.emit('updated', rundown) // TODO: make this more granular + } + + // this too could potentially be moved to a PartService or GroupService + async stopPart(data: { rundownId: string; groupId: string; partId: string }): Promise { + // TODO: access control + const rundown = await this.rundownRepository.stopPart(data) + this.emit('updated', rundown) // TODO: make this more granular + } +} diff --git a/apps/app/src/electron/storageHandler.ts b/apps/app/src/electron/storageHandler.ts index 06b9232f..d9e6505c 100644 --- a/apps/app/src/electron/storageHandler.ts +++ b/apps/app/src/electron/storageHandler.ts @@ -324,9 +324,10 @@ export class StorageHandler extends EventEmitter { this.triggerEmitAll() } - newRundown(name: string): string { + newRundown(name: string): Rundown { const fileName = this.getRundownFilename(name) - this.openRundowns[fileName] = this._loadRundown(this._projectId, fileName, name) + const rundown = this._loadRundown(this._projectId, fileName, name) + this.openRundowns[fileName] = rundown this.appData.appData.rundowns[fileName] = { name: name, open: true, @@ -334,7 +335,10 @@ export class StorageHandler extends EventEmitter { this.appDataHasChanged = true this.appDataNeedsWrite = true this.triggerUpdate({ appData: true, rundowns: { [fileName]: true } }) - return fileName + return { + ...rundown.rundown, + id: fileName, + } } openRundown(fileName: string): void { const fileRundown = this._loadRundown(this._projectId, fileName) @@ -597,7 +601,7 @@ export class StorageHandler extends EventEmitter { this.updateProject(devData.project) for (const rundown of devData.rundowns) { - const filename = this.newRundown(rundown.name) + const filename = this.newRundown(rundown.name).id rundown.id = filename this.openRundown(filename) diff --git a/apps/app/src/electron/triggersHandler.ts b/apps/app/src/electron/triggersHandler.ts index 07ac37ad..aef65f3e 100644 --- a/apps/app/src/electron/triggersHandler.ts +++ b/apps/app/src/electron/triggersHandler.ts @@ -4,7 +4,7 @@ import _ from 'lodash' import { getGroupPlayData } from '../lib/playout/groupPlayData' import { ActiveTrigger, ActiveTriggers } from '../models/rundown/Trigger' import { BridgeHandler } from './bridgeHandler' -import { IPCServer } from './IPCServer' +import { EverythingService } from './IPCServer' import { StorageHandler } from './storageHandler' import { ActionAny, @@ -59,7 +59,7 @@ export class TriggersHandler extends EventEmitter { constructor( private log: LoggerLike, private storage: StorageHandler, - private ipcServer: IPCServer, + private ipcServer: EverythingService, private bridgeHandler: BridgeHandler, private session: SessionHandler ) { diff --git a/apps/app/src/ipc/IPCAPI.ts b/apps/app/src/ipc/IPCAPI.ts index 4c526ca1..ab7671f5 100644 --- a/apps/app/src/ipc/IPCAPI.ts +++ b/apps/app/src/ipc/IPCAPI.ts @@ -16,6 +16,7 @@ import { ActiveAnalog } from '../models/rundown/Analog' import { AnalogInput } from '../models/project/AnalogInput' import { ValidatorCache } from 'graphics-data-definition' import { BridgePeripheralId } from '@shared/lib' +import { DefiningArea } from '../lib/triggers/keyDisplay/keyDisplay' export const MAX_UNDO_LEDGER_LENGTH = 100 @@ -74,6 +75,7 @@ export interface Action { undo: UndoFunction } +// --- legacy /** Methods that can be called on the server, by the client */ export interface IPCServerMethods { // Note: All these methods must only accept a single parameter. @@ -84,7 +86,6 @@ export interface IPCServerMethods { debugThrowError: (arg: { type: 'sync' | 'async' | 'setTimeout' }) => void installUpdate: () => void triggerSendAll: () => void - triggerSendRundown: (arg: { rundownId: string }) => void setKeyboardKeys(arg: { activeKeys: ActiveTrigger[] }): void makeDevData(): void @@ -101,9 +102,9 @@ export interface IPCServerMethods { listProjects: () => { name: string; id: string }[] openProject: (arg: { projectId: string }) => void - playPart: (arg: { rundownId: string; groupId: string; partId: string; resume?: boolean }) => void - pausePart: (arg: { rundownId: string; groupId: string; partId: string; pauseTime?: number }) => void - stopPart: (arg: { rundownId: string; groupId: string; partId: string }) => void + playPart: (arg: { rundownId: string; groupId: string; partId: string; resume?: boolean }) => Rundown + pausePart: (arg: { rundownId: string; groupId: string; partId: string; pauseTime?: number }) => Rundown + stopPart: (arg: { rundownId: string; groupId: string; partId: string }) => Rundown setPartTrigger: (arg: { rundownId: string groupId: string @@ -207,7 +208,7 @@ export interface IPCServerMethods { updateAppData: (arg: UpdateAppDataOptions) => void updateProject: (arg: { id: string; project: Project }) => void - newRundown: (arg: { name: string }) => string + newRundown: (arg: { name: string }) => Rundown deleteRundown: (arg: { rundownId: string }) => void openRundown: (arg: { rundownId: string }) => void closeRundown: (arg: { rundownId: string }) => void @@ -255,6 +256,8 @@ export interface IPCClientMethods { updatePeripheralAnalog: (fullIdentifier: string, analog: ActiveAnalog | null) => void updateFailedGlobalTriggers: (identifiers: string[]) => void updateAnalogInput: (fullIdentifier: string, analogInput: AnalogInput | null) => void + updateDeviceRefreshStatus: (deviceId: TSRDeviceId, refreshing: boolean) => void + updateDefiningArea: (area: DefiningArea | null) => void } export interface SystemMessageOptions { diff --git a/apps/app/src/main.ts b/apps/app/src/main.ts index cbae2d92..942576dc 100644 --- a/apps/app/src/main.ts +++ b/apps/app/src/main.ts @@ -71,7 +71,8 @@ function createWindow(log: winston.Logger, superConductor: SuperConductor): void superConductor.ipcServer.redo().catch(log.error) }, onAboutClick: () => { - handler.ipcClient.displayAboutDialog() + // TODO: this should probably become a client-side only action + // handler.ipcClient.displayAboutDialog() }, // eslint-disable-next-line @typescript-eslint/no-misused-promises onUpdateClick: async () => { diff --git a/apps/app/src/react/App.tsx b/apps/app/src/react/App.tsx index 1214c621..1d23b193 100644 --- a/apps/app/src/react/App.tsx +++ b/apps/app/src/react/App.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' -const { ipcRenderer } = window.require('electron') +// const { ipcRenderer } = window.require('electron') import '@fontsource/barlow/300.css' import '@fontsource/barlow/400.css' @@ -13,8 +13,8 @@ import './styles/app.scss' import { RundownView } from './components/rundown/RundownView' import { Sidebar } from './components/sidebar/Sidebar' import sorensen from '@sofie-automation/sorensen' -import { IPCClient } from './api/IPCClient' -import { IPCServer } from './api/IPCServer' +import { RealtimeDataProvider } from './api/RealtimeDataProvider' +import { ApiClient } from './api/ApiClient' import { Project } from '../models/project/Project' import { IPCServerContext } from './contexts/IPCServer' import { ProjectContext } from './contexts/Project' @@ -74,8 +74,8 @@ export const App = observer(function App() { const [sorensenInitialized, setSorensenInitialized] = useState(false) const { enqueueSnackbar, closeSnackbar } = useSnackbar() - const serverAPI = useMemo(() => { - return new IPCServer(ipcRenderer) + const serverAPI = useMemo(() => { + return new ApiClient() }, []) const logger = useMemo(() => { @@ -109,7 +109,7 @@ export const App = observer(function App() { // Handle IPC-messages from server useEffect(() => { - const ipcClient = new IPCClient(logger, ipcRenderer, { + const ipcClient = new RealtimeDataProvider(logger, { systemMessage: (messageStr: string, options: SystemMessageOptions) => { messageStr = messageStr.replace(/\n/g, '
') const message = ( diff --git a/apps/app/src/react/api/IPCServer.ts b/apps/app/src/react/api/ApiClient.ts similarity index 86% rename from apps/app/src/react/api/IPCServer.ts rename to apps/app/src/react/api/ApiClient.ts index 806e1a71..663fdded 100644 --- a/apps/app/src/react/api/IPCServer.ts +++ b/apps/app/src/react/api/ApiClient.ts @@ -1,24 +1,49 @@ import { IPCServerMethods } from '../../ipc/IPCAPI' import { replaceUndefined } from '../../lib/util' -type Promisify = { - [K in keyof T]: T[K] extends (...arg: any[]) => any - ? (...args: Parameters) => Promise> - : T[K] +import { feathers } from '@feathersjs/feathers' +import socketio, { SocketService } from '@feathersjs/socketio-client' +import io from 'socket.io-client' +import { Rundown } from '../../models/rundown/Rundown' +import { ServiceTypes } from '../../electron/api/ApiServer' + +type AddTypeToProperties = { + [K in keyof T]: U & T[K] } +const socket = io('127.0.0.1:5500') // TODO +export const app = feathers>() +const socketClient = socketio(socket) +app.configure(socketClient) + +// TODO this type assertion below should be unnecessary +app.use('rundowns', socketClient.service('rundowns') as SocketService & ServiceTypes['rundowns'], { + methods: ['get', 'create', 'playPart', 'pausePart', 'stopPart', 'unsubscribe'], +}) + type ServerArgs = Parameters type ServerReturn = Promise> /** This class is used client-side, to send requests to the server */ -export class IPCServer implements Promisify { - constructor(private ipcRenderer: Electron.IpcRenderer) {} +export class ApiClient { + private readonly rundownService = app.service('rundowns') // TODO: DI? + // --- legacy code private async invokeServerMethod(methodname: T, ...args: any[]): ServerReturn { // Stringifying and parsing data will convert Mobx observable objects into object literals. // Otherwise, Electron won't be able to clone it. - return this.ipcRenderer.invoke(methodname, replaceUndefined(args)) - } + return new Promise((resolve, reject) => { + socket.emit(methodname, 'legacy', [...replaceUndefined(args)], {}, (error: any, data: any) => { + if (error) { + reject(error) + } else { + console.log('Called ', methodname, 'received', data) + resolve(data) + } + }) + }) as ServerReturn + } + // --- legacy code end async log(...args: ServerArgs<'log'>): ServerReturn<'log'> { return this.invokeServerMethod('log', ...args) @@ -35,8 +60,13 @@ export class IPCServer implements Promisify { async triggerSendAll(...args: ServerArgs<'triggerSendAll'>): ServerReturn<'triggerSendAll'> { return this.invokeServerMethod('triggerSendAll', ...args) } - async triggerSendRundown(...args: ServerArgs<'triggerSendRundown'>): ServerReturn<'triggerSendRundown'> { - return this.invokeServerMethod('triggerSendRundown', ...args) + + async getRundown(rundownId: string): Promise { + return await this.rundownService.get(rundownId) + } + + async unsubscribe(rundownId: string): Promise { + return await this.rundownService.unsubscribe(rundownId, {}) } async setKeyboardKeys(...args: ServerArgs<'setKeyboardKeys'>): ServerReturn<'setKeyboardKeys'> { @@ -80,14 +110,14 @@ export class IPCServer implements Promisify { async openProject(...args: ServerArgs<'openProject'>): ServerReturn<'openProject'> { return this.invokeServerMethod('openProject', ...args) } - async playPart(...args: ServerArgs<'playPart'>): ServerReturn<'playPart'> { - return this.invokeServerMethod('playPart', ...args) + async playPart(...args: Parameters): Promise { + return await this.rundownService.playPart(...args) } - async pausePart(...args: ServerArgs<'pausePart'>): ServerReturn<'pausePart'> { - return this.invokeServerMethod('pausePart', ...args) + async pausePart(...args: Parameters): Promise { + return await this.rundownService.pausePart(...args) } - async stopPart(...args: ServerArgs<'stopPart'>): ServerReturn<'stopPart'> { - return this.invokeServerMethod('stopPart', ...args) + async stopPart(...args: Parameters): Promise { + return await this.rundownService.stopPart(...args) } async setPartTrigger(...args: ServerArgs<'setPartTrigger'>): ServerReturn<'setPartTrigger'> { return this.invokeServerMethod('setPartTrigger', ...args) diff --git a/apps/app/src/react/api/IPCClient.ts b/apps/app/src/react/api/RealtimeDataProvider.ts similarity index 87% rename from apps/app/src/react/api/IPCClient.ts rename to apps/app/src/react/api/RealtimeDataProvider.ts index 75ada86e..b2a89554 100644 --- a/apps/app/src/react/api/IPCClient.ts +++ b/apps/app/src/react/api/RealtimeDataProvider.ts @@ -1,4 +1,4 @@ -import { IPCClientMethods, SystemMessageOptions } from '../../ipc/IPCAPI' +import { SystemMessageOptions } from '../../ipc/IPCAPI' import { BridgeStatus } from '../../models/project/Bridge' import { Project } from '../../models/project/Project' import { PeripheralStatus } from '../../models/project/Peripheral' @@ -12,12 +12,12 @@ import { ActiveAnalog } from '../../models/rundown/Analog' import { AnalogInput } from '../../models/project/AnalogInput' import { BridgeId } from '@shared/api' import { BridgePeripheralId } from '@shared/lib' +import { app } from './ApiClient' /** This class is used client-side, to handle messages from the server */ -export class IPCClient implements IPCClientMethods { +export class RealtimeDataProvider { constructor( private logger: ClientSideLogger, - private ipcRenderer: Electron.IpcRenderer, private callbacks: { systemMessage?: (message: string, options: SystemMessageOptions) => void @@ -39,12 +39,17 @@ export class IPCClient implements IPCClientMethods { updateAnalogInput?: (fullIdentifier: string, analogInput: AnalogInput | null) => void } ) { - this.handleCallMethod = this.handleCallMethod.bind(this) - // eslint-disable-next-line @typescript-eslint/unbound-method - this.ipcRenderer.on('callMethod', this.handleCallMethod) + // this is new: + app.service('rundowns').on('updated', (rundown) => this.updateRundown(rundown.id, rundown)) + // app.service('project').on(...) etc. + + // this is temporary: + app.service('legacy').on('callMethod', (args) => this.handleCallMethod(args[0], args.slice(1))) } - private handleCallMethod(_event: Electron.IpcRendererEvent, methodname: string, ...args: any[]): void { + // --- legacy, remove + + private handleCallMethod(methodname: string, args: any[]): void { const fcn = (this as any)[methodname] if (!fcn) { this.logger.error(`IPCClient: method ${methodname} not found`) @@ -100,6 +105,8 @@ export class IPCClient implements IPCClientMethods { } destroy(): void { // eslint-disable-next-line @typescript-eslint/unbound-method - this.ipcRenderer.off('callMethod', this.handleCallMethod) + // this.ipcRenderer.off('callMethod', this.handleCallMethod) } + + // --- legacy end } diff --git a/apps/app/src/react/api/clipboard/lib.ts b/apps/app/src/react/api/clipboard/lib.ts index 49d815cd..43fc2cd1 100644 --- a/apps/app/src/react/api/clipboard/lib.ts +++ b/apps/app/src/react/api/clipboard/lib.ts @@ -4,11 +4,11 @@ import { Project } from '../../../models/project/Project' import { Group } from '../../../models/rundown/Group' import { Part } from '../../../models/rundown/Part' import { store } from '../../mobx/store' -import { IPCServer } from '../IPCServer' +import { ApiClient } from '../ApiClient' export interface ClipBoardContext { project?: Project - serverAPI: IPCServer + serverAPI: ApiClient handleError: (error: unknown) => void } export async function insertGroups( diff --git a/apps/app/src/react/api/logger.ts b/apps/app/src/react/api/logger.ts index d305f21d..ddb2cecb 100644 --- a/apps/app/src/react/api/logger.ts +++ b/apps/app/src/react/api/logger.ts @@ -1,9 +1,9 @@ import { LogLevel } from '@shared/api' import { stringifyError } from '@shared/lib' -import { IPCServer } from './IPCServer' +import { ApiClient } from './ApiClient' export class ClientSideLogger { - constructor(private serverAPI: IPCServer) { + constructor(private serverAPI: ApiClient) { this.error = this.error.bind(this) this.warn = this.warn.bind(this) this.info = this.info.bind(this) diff --git a/apps/app/src/react/components/pages/newRundownPage/NewRundownPage.tsx b/apps/app/src/react/components/pages/newRundownPage/NewRundownPage.tsx index 5534bff4..9907d078 100644 --- a/apps/app/src/react/components/pages/newRundownPage/NewRundownPage.tsx +++ b/apps/app/src/react/components/pages/newRundownPage/NewRundownPage.tsx @@ -22,9 +22,9 @@ export const NewRundownPage = (): JSX.Element => { const handleCreateNewRundown = (rundownName: string) => { serverAPI .newRundown({ name: rundownName }) - .then((rundownId) => { + .then((rundown) => { setTimeout(() => { - store.rundownsStore.setCurrentRundown(rundownId) + store.rundownsStore.setCurrentRundown(rundown.id) }, 100) }) .catch(handleError) diff --git a/apps/app/src/react/contexts/IPCServer.ts b/apps/app/src/react/contexts/IPCServer.ts index 98edb289..cec885c8 100644 --- a/apps/app/src/react/contexts/IPCServer.ts +++ b/apps/app/src/react/contexts/IPCServer.ts @@ -1,4 +1,4 @@ -import { IPCServer } from '../api/IPCServer' +import { ApiClient } from '../api/ApiClient' import React from 'react' /** Used to communicate with the backend */ -export const IPCServerContext = React.createContext({} as IPCServer) +export const IPCServerContext = React.createContext({} as ApiClient) diff --git a/apps/app/src/react/mobx/AnalogStore.ts b/apps/app/src/react/mobx/AnalogStore.ts index 5413020a..7e300d3e 100644 --- a/apps/app/src/react/mobx/AnalogStore.ts +++ b/apps/app/src/react/mobx/AnalogStore.ts @@ -1,27 +1,27 @@ import { makeAutoObservable, runInAction } from 'mobx' import { AnalogInput } from '../../models/project/AnalogInput' import { ActiveAnalog } from '../../models/rundown/Analog' -import { IPCClient } from '../api/IPCClient' -import { IPCServer } from '../api/IPCServer' +import { RealtimeDataProvider } from '../api/RealtimeDataProvider' +import { ApiClient } from '../api/ApiClient' import { ClientSideLogger } from '../api/logger' -const { ipcRenderer } = window.require('electron') +// const { ipcRenderer } = window.require('electron') export class AnalogStore { private analogInputs = new Map() private activeAnalogListeners: ((activeAnalog: ActiveAnalog) => void)[] // Not an observable - serverAPI: IPCServer + serverAPI: ApiClient logger: ClientSideLogger - ipcClient: IPCClient + ipcClient: RealtimeDataProvider constructor() { makeAutoObservable(this) this.activeAnalogListeners = [] - this.serverAPI = new IPCServer(ipcRenderer) + this.serverAPI = new ApiClient() this.logger = new ClientSideLogger(this.serverAPI) - this.ipcClient = new IPCClient(this.logger, ipcRenderer, { + this.ipcClient = new RealtimeDataProvider(this.logger, { updateAnalogInput: (fullIdentifier: string, analogInput: AnalogInput | null) => this.updateAnalogInput(fullIdentifier, analogInput), }) diff --git a/apps/app/src/react/mobx/AppStore.ts b/apps/app/src/react/mobx/AppStore.ts index 11303fa2..1f492138 100644 --- a/apps/app/src/react/mobx/AppStore.ts +++ b/apps/app/src/react/mobx/AppStore.ts @@ -1,32 +1,31 @@ import { makeAutoObservable } from 'mobx' import { BridgeDevice, BridgeStatus } from '../../models/project/Bridge' import { AppData } from '../../models/App/AppData' -import { IPCServer } from '../api/IPCServer' -import { IPCClient } from '../api/IPCClient' +import { ApiClient } from '../api/ApiClient' +import { RealtimeDataProvider } from '../api/RealtimeDataProvider' import { PeripheralStatus } from '../../models/project/Peripheral' import { ClientSideLogger } from '../api/logger' import { setConstants } from '../constants' import { BridgeId } from '@shared/api' import { TSRDeviceId, protectString } from '@shared/models' import { BridgePeripheralId } from '@shared/lib' -const { ipcRenderer } = window.require('electron') export class AppStore { bridgeStatuses = new Map() peripherals = new Map() - serverAPI: IPCServer + serverAPI: ApiClient logger: ClientSideLogger - ipcClient: IPCClient + ipcClient: RealtimeDataProvider allDeviceStatuses = new Map() private _data?: AppData = undefined constructor(init?: AppData) { - this.serverAPI = new IPCServer(ipcRenderer) + this.serverAPI = new ApiClient() this.logger = new ClientSideLogger(this.serverAPI) - this.ipcClient = new IPCClient(this.logger, ipcRenderer, { + this.ipcClient = new RealtimeDataProvider(this.logger, { updateBridgeStatus: (bridgeId: BridgeId, status: BridgeStatus | null) => this.updateBridgeStatus(bridgeId, status), updatePeripheral: (peripheralId: BridgePeripheralId, peripheral: PeripheralStatus | null) => diff --git a/apps/app/src/react/mobx/GDDValidatorStoreStore.ts b/apps/app/src/react/mobx/GDDValidatorStoreStore.ts index 981a602c..2c95d336 100644 --- a/apps/app/src/react/mobx/GDDValidatorStoreStore.ts +++ b/apps/app/src/react/mobx/GDDValidatorStoreStore.ts @@ -1,14 +1,14 @@ import { makeAutoObservable, runInAction } from 'mobx' import { SchemaValidator, setupSchemaValidator, ValidatorCache } from 'graphics-data-definition' -import { IPCServer } from '../api/IPCServer' -const { ipcRenderer } = window.require('electron') +import { ApiClient } from '../api/ApiClient' +// const { ipcRenderer } = window.require('electron') export class GDDValidatorStore { private isInitialized = false // private gddCache: ValidatorCache | null | undefined = undefined public gddValidator: SchemaValidator | null = null - private serverAPI: IPCServer | null = null + private serverAPI: ApiClient | null = null constructor() { makeAutoObservable(this) @@ -18,7 +18,7 @@ export class GDDValidatorStore { if (this.isInitialized) return this.isInitialized = true - if (!this.serverAPI) this.serverAPI = new IPCServer(ipcRenderer) + if (!this.serverAPI) this.serverAPI = new ApiClient() // First, retrieve a cache from server, if possible: let gddCache = await this.serverAPI.fetchGDDCache() diff --git a/apps/app/src/react/mobx/GroupPlayDataStore.ts b/apps/app/src/react/mobx/GroupPlayDataStore.ts index a5663e0f..4399deab 100644 --- a/apps/app/src/react/mobx/GroupPlayDataStore.ts +++ b/apps/app/src/react/mobx/GroupPlayDataStore.ts @@ -1,27 +1,27 @@ import { getGroupPlayData, GroupPlayData } from '../../lib/playout/groupPlayData' import { makeAutoObservable } from 'mobx' import _ from 'lodash' -import { IPCClient } from '../api/IPCClient' +import { RealtimeDataProvider } from '../api/RealtimeDataProvider' import { Rundown } from '../../models/rundown/Rundown' -import { IPCServer } from '../api/IPCServer' +import { ApiClient } from '../api/ApiClient' import { ClientSideLogger } from '../api/logger' -const { ipcRenderer } = window.require('electron') +// const { ipcRenderer } = window.require('electron') export class GroupPlayDataStore { groups: Map = new Map() - serverAPI: IPCServer + serverAPI: ApiClient logger: ClientSideLogger - ipcClient: IPCClient + ipcClient: RealtimeDataProvider private updateGroupPlayDataLowLatency: boolean private updateGroupPlayDataFrameCount: number private rundown: Rundown | undefined = undefined constructor() { - this.serverAPI = new IPCServer(ipcRenderer) + this.serverAPI = new ApiClient() this.logger = new ClientSideLogger(this.serverAPI) - this.ipcClient = new IPCClient(this.logger, ipcRenderer, { + this.ipcClient = new RealtimeDataProvider(this.logger, { updateRundown: (_rundownId: string, rundown: Rundown) => { this.rundown = rundown this.updateGroupPlayDataLowLatency = true diff --git a/apps/app/src/react/mobx/GuiStore.ts b/apps/app/src/react/mobx/GuiStore.ts index e44de971..b9610caf 100644 --- a/apps/app/src/react/mobx/GuiStore.ts +++ b/apps/app/src/react/mobx/GuiStore.ts @@ -9,8 +9,8 @@ import { CurrentSelectionTimelineObj, } from '../../lib/GUI' import { DefiningArea } from '../../lib/triggers/keyDisplay/keyDisplay' -import { IPCServer } from '../api/IPCServer' -const { ipcRenderer } = window.require('electron') +import { ApiClient } from '../api/ApiClient' +// const { ipcRenderer } = window.require('electron') /** * Store contains only information about user interface @@ -64,7 +64,7 @@ interface ResourceLibrarySettings { detailedFiltersExpanded: boolean } export class GuiStore { - serverAPI = new IPCServer(ipcRenderer) + serverAPI = new ApiClient() private _selected: CurrentSelectionAny[] = [] diff --git a/apps/app/src/react/mobx/ResourcesAndMetadataStore.ts b/apps/app/src/react/mobx/ResourcesAndMetadataStore.ts index 63922339..4102380f 100644 --- a/apps/app/src/react/mobx/ResourcesAndMetadataStore.ts +++ b/apps/app/src/react/mobx/ResourcesAndMetadataStore.ts @@ -1,7 +1,7 @@ import { makeAutoObservable } from 'mobx' -import { IPCServer } from '../api/IPCServer' -const { ipcRenderer } = window.require('electron') -import { IPCClient } from '../api/IPCClient' +import { ApiClient } from '../api/ApiClient' +// const { ipcRenderer } = window.require('electron') +import { RealtimeDataProvider } from '../api/RealtimeDataProvider' import { MetadataAny, ResourceAny, @@ -22,17 +22,17 @@ export class ResourcesAndMetadataStore { metadata: Metadata = new Map() refreshStatuses = new Set() - serverAPI: IPCServer + serverAPI: ApiClient logger: ClientSideLogger - ipcClient: IPCClient + ipcClient: RealtimeDataProvider private resourceHashes: Map // Not defined here, this should not be an observable private metadataHashes: Map // Not defined here, this should not be an observable constructor(init?: Resources) { - this.serverAPI = new IPCServer(ipcRenderer) + this.serverAPI = new ApiClient() this.logger = new ClientSideLogger(this.serverAPI) - this.ipcClient = new IPCClient(this.logger, ipcRenderer, { + this.ipcClient = new RealtimeDataProvider(this.logger, { updateResourcesAndMetadata: (resources, metadata) => this.updateResourcesAndMetadata(resources, metadata), updateDeviceRefreshStatus: (deviceId, refreshing) => this._updateDeviceRefreshStatus(deviceId, refreshing), }) diff --git a/apps/app/src/react/mobx/RundownsStore.ts b/apps/app/src/react/mobx/RundownsStore.ts index 1aebdf0b..aba3e1ab 100644 --- a/apps/app/src/react/mobx/RundownsStore.ts +++ b/apps/app/src/react/mobx/RundownsStore.ts @@ -2,8 +2,8 @@ import { makeAutoObservable, IObservableArray, runInAction } from 'mobx' import { getDefaultGroup } from '../../lib/defaults' import { Rundown, RundownGUI } from '../../models/rundown/Rundown' import { Group, GroupGUI } from '../../models/rundown/Group' -import { IPCClient } from '../api/IPCClient' -import { IPCServer } from '../api/IPCServer' +import { RealtimeDataProvider } from '../api/RealtimeDataProvider' +import { ApiClient } from '../api/ApiClient' import { store } from './store' import { allowMovingPartIntoGroup, @@ -22,7 +22,6 @@ import { ActionAny, RundownActionLight } from '../../lib/triggers/action' import _ from 'lodash' import { TimelineObj } from '../../models/rundown/TimelineObj' import { assertNever } from '@shared/lib' -const { ipcRenderer } = window.require('electron') interface IRundownsItems { [fileName: string]: IRundownsItem @@ -61,34 +60,15 @@ export class RundownsStore { private _uiParts = new Map() private _uiTimeline = new Map() - serverAPI: IPCServer + serverAPI: ApiClient logger: ClientSideLogger - ipcClient: IPCClient + ipcClient: RealtimeDataProvider constructor() { - this.serverAPI = new IPCServer(ipcRenderer) + this.serverAPI = new ApiClient() this.logger = new ClientSideLogger(this.serverAPI) - this.ipcClient = new IPCClient(this.logger, ipcRenderer, { - updateRundown: (rundownId: string, rundown: Rundown) => { - runInAction(() => { - this._rundownsClean.set(rundownId, rundown) - this._updateRundown(rundownId, rundown) - - // Update selection to remove any selections of non-existent things: - store.guiStore.updateSelection((selected) => { - if (selected.type === 'group') { - return store.rundownsStore.hasGroup(selected.groupId) - } else if (selected.type === 'part') { - return store.rundownsStore.hasPart(selected.partId) - } else if (selected.type === 'timelineObj') { - return store.rundownsStore.hasTimelineObj(selected.timelineObjId) - } else { - assertNever(selected) - return false - } - }) - }) - }, + this.ipcClient = new RealtimeDataProvider(this.logger, { + updateRundown: this.updateRundown, }) makeAutoObservable(this) } @@ -111,6 +91,27 @@ export class RundownsStore { } } + updateRundown = (rundownId: string, rundown: Rundown): void => { + runInAction(() => { + this._rundownsClean.set(rundownId, rundown) + this._updateRundown(rundownId, rundown) + + // Update selection to remove any selections of non-existent things: + store.guiStore.updateSelection((selected) => { + if (selected.type === 'group') { + return store.rundownsStore.hasGroup(selected.groupId) + } else if (selected.type === 'part') { + return store.rundownsStore.hasPart(selected.partId) + } else if (selected.type === 'timelineObj') { + return store.rundownsStore.hasTimelineObj(selected.timelineObjId) + } else { + assertNever(selected) + return false + } + }) + }) + } + hasRundown(rundownId: string): boolean { return this._uiRundowns.has(rundownId) } @@ -450,12 +451,21 @@ export class RundownsStore { * @param rundownId ID of the new current rundown */ setCurrentRundown(rundownId: string | undefined): void { + if (this._currentRundownId) { + // TODO: is there a better place to unsubscribe, in order not to leave a hanging subscription? + this.serverAPI.unsubscribe(this._currentRundownId).catch(() => { + // TODO + }) + } if (rundownId) { this._currentRundownId = rundownId store.guiStore.activeTabId = rundownId - this.serverAPI.triggerSendRundown({ rundownId }).catch(() => { - //TODO - }) + this.serverAPI + .getRundown(rundownId) + .then((rundown) => this.updateRundown(rundown.id, rundown)) + .catch(() => { + //TODO + }) } else { this._currentRundownId = undefined } diff --git a/apps/app/src/react/mobx/TriggersStore.ts b/apps/app/src/react/mobx/TriggersStore.ts index 178d7b0c..701213dd 100644 --- a/apps/app/src/react/mobx/TriggersStore.ts +++ b/apps/app/src/react/mobx/TriggersStore.ts @@ -1,20 +1,19 @@ import { makeAutoObservable } from 'mobx' -import { IPCServer } from '../api/IPCServer' -import { IPCClient } from '../api/IPCClient' +import { ApiClient } from '../api/ApiClient' +import { RealtimeDataProvider } from '../api/RealtimeDataProvider' import { ClientSideLogger } from '../api/logger' -const { ipcRenderer } = window.require('electron') export class TriggersStore { failedGlobalTriggers: Set = new Set() - serverAPI: IPCServer + serverAPI: ApiClient logger: ClientSideLogger - ipcClient: IPCClient + ipcClient: RealtimeDataProvider constructor() { - this.serverAPI = new IPCServer(ipcRenderer) + this.serverAPI = new ApiClient() this.logger = new ClientSideLogger(this.serverAPI) - this.ipcClient = new IPCClient(this.logger, ipcRenderer, { + this.ipcClient = new RealtimeDataProvider(this.logger, { updateFailedGlobalTriggers: (identifiers: string[]) => this.updateFailedGlobalTriggers(identifiers), }) diff --git a/yarn.lock b/yarn.lock index c2af48e7..5681295c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -823,6 +823,121 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.42.0.tgz#484a1d638de2911e6f5a30c12f49c7e4a3270fb6" integrity sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw== +"@feathersjs/adapter-commons@^5.0.8": + version "5.0.8" + resolved "https://registry.yarnpkg.com/@feathersjs/adapter-commons/-/adapter-commons-5.0.8.tgz#df45557b0c00e1d78a22fa3ffc6f2692e9647665" + integrity sha512-DEGthTM/Yg+vxDSI2Myy2V14Nig+Gq73fOGVxR4piLzVgXPtf7q6XkdGwWUzgIQ8C47UrpIn9dBAfEKCRdZm2Q== + dependencies: + "@feathersjs/commons" "^5.0.8" + "@feathersjs/errors" "^5.0.8" + "@feathersjs/feathers" "^5.0.8" + +"@feathersjs/authentication@^5.0.8": + version "5.0.8" + resolved "https://registry.yarnpkg.com/@feathersjs/authentication/-/authentication-5.0.8.tgz#0cefdf02e413f4e1495e2d16dd9bbda70067b9cb" + integrity sha512-RV9P61DuxKkrLbZDvg1s31TnSteXqmCjBokUqFQsh582E97hO89Qj8vbHhm2C5ryBZ1yFWJsCN1wKogkh2oiNQ== + dependencies: + "@feathersjs/commons" "^5.0.8" + "@feathersjs/errors" "^5.0.8" + "@feathersjs/feathers" "^5.0.8" + "@feathersjs/hooks" "^0.8.1" + "@feathersjs/schema" "^5.0.8" + "@feathersjs/transport-commons" "^5.0.8" + "@types/jsonwebtoken" "^9.0.2" + jsonwebtoken "^9.0.1" + lodash "^4.17.21" + long-timeout "^0.1.1" + uuid "^9.0.0" + +"@feathersjs/commons@^5.0.8": + version "5.0.8" + resolved "https://registry.yarnpkg.com/@feathersjs/commons/-/commons-5.0.8.tgz#f4dd3b3ce474f8c79ac376672bade38bef4c934a" + integrity sha512-Xq8R6xD2rIWBCP32+VFX9caldveXqSNDLDNZS5OaKaqyYeIAeXtcjEz4no7jtuUqzxqFOTn/Ub2FqHhncI15Vg== + +"@feathersjs/errors@^5.0.8": + version "5.0.8" + resolved "https://registry.yarnpkg.com/@feathersjs/errors/-/errors-5.0.8.tgz#4e5f62ce5aa22fdd82cbd6844c346c925de99ceb" + integrity sha512-EvYaIbRbp+KK407lrKzDvfVi/EKRA4GlkfxVdT9L7yw/gNSwP1a+Cz3YO1cuhrnjHbaxVklAMRKyA5GboAfyWQ== + +"@feathersjs/feathers@^5.0.8": + version "5.0.8" + resolved "https://registry.yarnpkg.com/@feathersjs/feathers/-/feathers-5.0.8.tgz#bdcae13df4714a013618522f91e1473ae2267a03" + integrity sha512-dhG/Tj1rRiN/ofB7CNuhSQMwfOC5njXXQe00Zf02fkbGXxzi9vg9TKGr2qlsggAMEW/dPdbzpQ8+MiWAEcWh/g== + dependencies: + "@feathersjs/commons" "^5.0.8" + "@feathersjs/hooks" "^0.8.1" + events "^3.3.0" + +"@feathersjs/hooks@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@feathersjs/hooks/-/hooks-0.8.1.tgz#de7e8e9632d9bd9027eaffc7f5292fc577bc769f" + integrity sha512-q/OGjm2BEhT9cHYYcMZR4YKX4lHyufBJmi5Dz+XRM5YqUuEg9MYtR45CWgDiC1klrd2srNSsdmGNVU1otL4+0Q== + +"@feathersjs/koa@^5.0.8": + version "5.0.8" + resolved "https://registry.yarnpkg.com/@feathersjs/koa/-/koa-5.0.8.tgz#82a76763c8d564e8e4666ce66a5ea74e66004db0" + integrity sha512-tKq26u4svDMWz9P7ddE9lge9W1Qt/9HjV3YNRxtcnh6JKs6rQLq0S8RmYi3F3Xa2RHHFSmKvxhoIfkW3uzRiEQ== + dependencies: + "@feathersjs/authentication" "^5.0.8" + "@feathersjs/commons" "^5.0.8" + "@feathersjs/errors" "^5.0.8" + "@feathersjs/feathers" "^5.0.8" + "@feathersjs/transport-commons" "^5.0.8" + "@koa/cors" "^4.0.0" + "@types/koa" "^2.13.6" + "@types/koa-qs" "^2.0.0" + "@types/koa-static" "^4.0.2" + "@types/koa__cors" "^4.0.0" + koa "^2.14.2" + koa-body "^6.0.1" + koa-compose "^4.1.0" + koa-qs "^3.0.0" + koa-static "^5.0.0" + +"@feathersjs/schema@^5.0.8": + version "5.0.8" + resolved "https://registry.yarnpkg.com/@feathersjs/schema/-/schema-5.0.8.tgz#d5341983d9863fc9ea4a2cc312689c142e0a80a5" + integrity sha512-eCwiIz96JtwkIFN03rWGxQY91IQRWlkfRyFfL7UJ9kB+EpKVRfLQayrk+ZKbJcs1Duq3eUBEa+xAgKwCwEyUKQ== + dependencies: + "@feathersjs/adapter-commons" "^5.0.8" + "@feathersjs/commons" "^5.0.8" + "@feathersjs/errors" "^5.0.8" + "@feathersjs/feathers" "^5.0.8" + "@feathersjs/hooks" "^0.8.1" + "@types/json-schema" "^7.0.12" + ajv "^8.12.0" + ajv-formats "^2.1.1" + json-schema-to-ts "^2.9.1" + +"@feathersjs/socketio-client@^5.0.8": + version "5.0.8" + resolved "https://registry.yarnpkg.com/@feathersjs/socketio-client/-/socketio-client-5.0.8.tgz#e15e27ea13c385bf543c168df47d531a6767344e" + integrity sha512-rZU56ZFbNJxxAoQLimxNhi39BwLQMdDkiVgdKYML3RV50wliYSCHeKBgE9jsoqZVOucBWFqDvrGpgYbzcFFCTw== + dependencies: + "@feathersjs/feathers" "^5.0.8" + "@feathersjs/transport-commons" "^5.0.8" + +"@feathersjs/socketio@^5.0.8": + version "5.0.8" + resolved "https://registry.yarnpkg.com/@feathersjs/socketio/-/socketio-5.0.8.tgz#f3414d88b1ac4b6cf983d0a5e64243c0b8fac5c7" + integrity sha512-HBeBpAOyevs7sNm586ry5/v6WO/FBxUXgMHPmwvsD+KFEbZmsML3FldlzmYjdXiY+wUVNTe7/bQMHCCNIxGP6A== + dependencies: + "@feathersjs/commons" "^5.0.8" + "@feathersjs/feathers" "^5.0.8" + "@feathersjs/transport-commons" "^5.0.8" + socket.io "^4.7.1" + +"@feathersjs/transport-commons@^5.0.8": + version "5.0.8" + resolved "https://registry.yarnpkg.com/@feathersjs/transport-commons/-/transport-commons-5.0.8.tgz#bfc6bdd1235c43b6b57443b04245b3f820189594" + integrity sha512-vR0eeF7MExhTQZEFpZTwDEn+4FwNyUT+Aj1ssHkQvsZZp88JecQzQegZscepWcdKRvh64HGXptC2hhh5TvFt0w== + dependencies: + "@feathersjs/commons" "^5.0.8" + "@feathersjs/errors" "^5.0.8" + "@feathersjs/feathers" "^5.0.8" + encodeurl "^1.0.2" + lodash "^4.17.21" + "@fontsource/barlow-condensed@^4.5.9": version "4.5.9" resolved "https://registry.yarnpkg.com/@fontsource/barlow-condensed/-/barlow-condensed-4.5.9.tgz#5088998a644f04cedeecb65a633877dbfadca7b6" @@ -1137,6 +1252,13 @@ node-addon-api "^5.0.0" pkg-prebuilds "^0.1.0" +"@koa/cors@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-4.0.0.tgz#b2d300d7368d2e0ad6faa1d918eff6d0cde0859a" + integrity sha512-Y4RrbvGTlAaa04DBoPBWJqDR5gPj32OOz827ULXfgB1F7piD1MB/zwn8JR2LAnvdILhxUbXbkXGWuNVsFuVFCQ== + dependencies: + vary "^1.1.2" + "@koa/router@^12.0.0": version "12.0.0" resolved "https://registry.yarnpkg.com/@koa/router/-/router-12.0.0.tgz#2ae7937093fd392761c0e5833c368379d4a35737" @@ -2301,6 +2423,11 @@ dependencies: "@sinonjs/commons" "^2.0.0" +"@socket.io/component-emitter@~3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" + integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== + "@sofie-automation/code-standard-preset@^2.4.7": version "2.4.7" resolved "https://registry.yarnpkg.com/@sofie-automation/code-standard-preset/-/code-standard-preset-2.4.7.tgz#2e740ba3de55d7fb627ba76ea9d3e6aa65bb6773" @@ -2430,6 +2557,14 @@ "@types/node" "*" "@types/responselike" "*" +"@types/co-body@^6.1.0": + version "6.1.1" + resolved "https://registry.yarnpkg.com/@types/co-body/-/co-body-6.1.1.tgz#28d253c95cfbe30c8e8c5d69d4c0dbbcffc101c2" + integrity sha512-I9A1k7o4m8m6YPYJIGb1JyNTLqRWtSPg1JOZPWlE19w8Su2VRgRVp/SkKftQSwoxWHGUxGbON4jltONMumC8bQ== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/connect-history-api-fallback@^1.3.5": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" @@ -2450,6 +2585,11 @@ resolved "https://registry.yarnpkg.com/@types/content-disposition/-/content-disposition-0.5.5.tgz#650820e95de346e1f84e30667d168c8fd25aa6e3" integrity sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA== +"@types/cookie@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" + integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== + "@types/cookies@*": version "0.7.7" resolved "https://registry.yarnpkg.com/@types/cookies/-/cookies-0.7.7.tgz#7a92453d1d16389c05a5301eef566f34946cfd81" @@ -2460,6 +2600,13 @@ "@types/keygrip" "*" "@types/node" "*" +"@types/cors@^2.8.12": + version "2.8.14" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.14.tgz#94eeb1c95eda6a8ab54870a3bf88854512f43a92" + integrity sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ== + dependencies: + "@types/node" "*" + "@types/debug@^4.1.6": version "4.1.7" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82" @@ -2527,6 +2674,13 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/formidable@^2.0.5": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/formidable/-/formidable-2.0.6.tgz#811ed3cd8a8a7675e02420b3f861c317e055376a" + integrity sha512-L4HcrA05IgQyNYJj6kItuIkXrInJvsXTPC5B1i64FggWKKqSL+4hgt7asiSNva75AoLQjq29oPxFfU4GAQ6Z2w== + dependencies: + "@types/node" "*" + "@types/fs-extra@^9.0.11": version "9.0.13" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45" @@ -2608,11 +2762,23 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== +"@types/json-schema@^7.0.12": + version "7.0.13" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" + integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/jsonwebtoken@^9.0.2": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz#1f22283b8e1f933af9e195d720798b64b399d84c" + integrity sha512-b0jGiOgHtZ2jqdPgPnP6WLCXZk1T8p06A/vPGzUvxpFGgKMbjXJDjC5m52ErqBnIuWZFgGoIJyRdeG5AyreJjA== + dependencies: + "@types/node" "*" + "@types/keygrip@*": version "1.0.2" resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72" @@ -2639,6 +2805,28 @@ dependencies: "@types/koa" "*" +"@types/koa-qs@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/koa-qs/-/koa-qs-2.0.1.tgz#620262edd195ac3b15928faeef6dd5ef2f623bd9" + integrity sha512-cYNuD72Iks+N7NUyvO/r1Na/lP2xPEEeSJTuBjRicCWB/chkoJQqfgD2k/TaGlfm1Kn87MEzYfAlbHaKskRP3A== + dependencies: + "@types/koa" "*" + +"@types/koa-send@*": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@types/koa-send/-/koa-send-4.1.4.tgz#c11fd792bbcf55d2c0117f975316c3f47ef2546e" + integrity sha512-+ttyO5T1T1cLRUtk9etg/4E7ZIplJJUANkuzYptCPysWX5LRfGHsv9YOCiB7+gkAuedjEgZrl4K02RWJ2gaJ6Q== + dependencies: + "@types/koa" "*" + +"@types/koa-static@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/koa-static/-/koa-static-4.0.2.tgz#a199d2d64d2930755eb3ea370aeaf2cb6f501d67" + integrity sha512-ns/zHg+K6XVPMuohjpOlpkR1WLa4VJ9czgUP9bxkCDn0JZBtUWbD/wKDZzPGDclkQK1bpAEScufCHOy8cbfL0w== + dependencies: + "@types/koa" "*" + "@types/koa-send" "*" + "@types/koa@*", "@types/koa@^2.13.5": version "2.13.5" resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.13.5.tgz#64b3ca4d54e08c0062e89ec666c9f45443b21a61" @@ -2653,6 +2841,27 @@ "@types/koa-compose" "*" "@types/node" "*" +"@types/koa@^2.13.6": + version "2.13.9" + resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.13.9.tgz#8d989ac17d7f033475fbe34c4f906c9287c2041a" + integrity sha512-tPX3cN1dGrMn+sjCDEiQqXH2AqlPoPd594S/8zxwUm/ZbPsQXKqHPUypr2gjCPhHUc+nDJLduhh5lXI/1olnGQ== + dependencies: + "@types/accepts" "*" + "@types/content-disposition" "*" + "@types/cookies" "*" + "@types/http-assert" "*" + "@types/http-errors" "*" + "@types/keygrip" "*" + "@types/koa-compose" "*" + "@types/node" "*" + +"@types/koa__cors@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/koa__cors/-/koa__cors-4.0.1.tgz#72fc6e1d4f6044acfe938b87a100840993d611a1" + integrity sha512-1AigjNVkUTZyYbgCtEEy8OM1aTKtHIDYS+yTrGtOPu/9jYSM7gjGbhgxA1FrJpsuigtd8u+AWOnjwRitQ7/nnw== + dependencies: + "@types/koa" "*" + "@types/koa__router@^12.0.0": version "12.0.0" resolved "https://registry.yarnpkg.com/@types/koa__router/-/koa__router-12.0.0.tgz#19b0f287f9708267dfd8842feb5f2de407d93af2" @@ -2714,6 +2923,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.10.tgz#616f16e9d3a2a3d618136b1be244315d95bd7cab" integrity sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog== +"@types/node@>=10.0.0": + version "20.6.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.2.tgz#a065925409f59657022e9063275cd0b9bd7e1b12" + integrity sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw== + "@types/node@^16.11.26": version "16.18.3" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc" @@ -3373,6 +3587,16 @@ ajv@^8.0.0, ajv@^8.8.0: require-from-string "^2.0.2" uri-js "^4.2.2" +ajv@^8.12.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + alawmulaw@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/alawmulaw/-/alawmulaw-5.0.2.tgz#885a914933e3f3bc0bdd67534af669a20f17d4aa" @@ -3834,6 +4058,11 @@ base64-js@^1.3.1, base64-js@^1.5.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +base64id@2.0.0, base64id@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" + integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== + batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" @@ -4032,6 +4261,11 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + buffer-equal@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" @@ -4552,7 +4786,7 @@ cmd-shim@^5.0.0: dependencies: mkdirp-infer-owner "^2.0.0" -co-body@^6.0.0: +co-body@^6.0.0, co-body@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/co-body/-/co-body-6.1.0.tgz#d87a8efc3564f9bfe3aced8ef5cd04c7a8766547" integrity sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ== @@ -4917,6 +5151,11 @@ cookie@0.5.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== +cookie@~0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + cookies@~0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90" @@ -4950,6 +5189,14 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cors@~2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + cosmiconfig@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" @@ -5092,7 +5339,7 @@ debug@^3.1.0, debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.3.4: +debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -5341,6 +5588,14 @@ dezalgo@^1.0.0: asap "^2.0.0" wrappy "1" +dezalgo@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" + integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== + dependencies: + asap "^2.0.0" + wrappy "1" + diff-sequences@^29.4.3: version "29.4.3" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" @@ -5578,6 +5833,13 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -5741,6 +6003,38 @@ endianness@^8.0.2: resolved "https://registry.yarnpkg.com/endianness/-/endianness-8.0.2.tgz#e35d16bbe80b6ff94fbc199168dd234d9f78168e" integrity sha512-IU+77+jJ7lpw2qZ3NUuqBZFy3GuioNgXUdsL1L9tooDNTaw0TgOnwNuc+8Ns+haDaTifK97QLzmOANJtI/rGvw== +engine.io-client@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.5.2.tgz#8709e22c291d4297ae80318d3c8baeae71f0e002" + integrity sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.11.0" + xmlhttprequest-ssl "~2.0.0" + +engine.io-parser@~5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.1.tgz#9f213c77512ff1a6cc0c7a86108a7ffceb16fcfb" + integrity sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ== + +engine.io@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.5.2.tgz#769348ced9d56bd47bd83d308ec1c3375e85937c" + integrity sha512-IXsMcGpw/xRfjra46sVZVHiSWo/nJ/3g1337q9KNXtS6YRzbW5yIzTCb9DjhrBe7r3GZQR0I4+nq+4ODk5g/cA== + dependencies: + "@types/cookie" "^0.4.1" + "@types/cors" "^2.8.12" + "@types/node" ">=10.0.0" + accepts "~1.3.4" + base64id "2.0.0" + cookie "~0.4.1" + cors "~2.8.5" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.11.0" + enhanced-resolve@^5.0.0, enhanced-resolve@^5.8.2: version "5.8.3" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz#6d552d465cce0423f5b3d718511ea53826a7b2f0" @@ -6130,7 +6424,7 @@ eventemitter3@^4.0.0, eventemitter3@^4.0.4, eventemitter3@^4.0.7: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.2.0: +events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -6559,6 +6853,16 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +formidable@^2.0.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-2.1.2.tgz#fa973a2bec150e4ce7cac15589d7a25fc30ebd89" + integrity sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g== + dependencies: + dezalgo "^1.0.4" + hexoid "^1.0.0" + once "^1.4.0" + qs "^6.11.0" + formik-mui@^5.0.0-alpha.0: version "5.0.0-alpha.0" resolved "https://registry.yarnpkg.com/formik-mui/-/formik-mui-5.0.0-alpha.0.tgz#567f2f3edf4a2edd9cb20cb7cab4dbd9ba24d05a" @@ -7195,6 +7499,11 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +hexoid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" + integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== + highland@^3.0.0-beta.6: version "3.0.0-beta.10" resolved "https://registry.yarnpkg.com/highland/-/highland-3.0.0-beta.10.tgz#e07869611de2109134c523e2479ef5af5334c112" @@ -7331,7 +7640,7 @@ http-errors@2.0.0, http-errors@^2.0.0: statuses "2.0.1" toidentifier "1.0.1" -http-errors@^1.6.3, http-errors@~1.8.0: +http-errors@^1.6.3, http-errors@^1.7.3, http-errors@~1.8.0: version "1.8.1" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== @@ -8465,6 +8774,15 @@ json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-schema-to-ts@^2.9.1: + version "2.9.2" + resolved "https://registry.yarnpkg.com/json-schema-to-ts/-/json-schema-to-ts-2.9.2.tgz#a054bc6410f13c7a2fc51aeabac52292e885b98d" + integrity sha512-h9WqLkTVpBbiaPb5OmeUpz/FBLS/kvIJw4oRCPiEisIu2WjMh+aai0QIY2LoOhRFx5r92taGLcerIrzxKBAP6g== + dependencies: + "@babel/runtime" "^7.18.3" + "@types/json-schema" "^7.0.9" + ts-algebra "^1.2.0" + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -8545,6 +8863,22 @@ jsonschema@^1.4.1: resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab" integrity sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ== +jsonwebtoken@^9.0.1: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + jsprim@^1.2.2: version "1.4.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" @@ -8573,6 +8907,23 @@ just-diff@^5.0.1: resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-5.1.1.tgz#8da6414342a5ed6d02ccd64f5586cbbed3146202" integrity sha512-u8HXJ3HlNrTzY7zrYYKjNEfBlyjqhdBkoyTVdjtn7p02RJD5NvR8rIClzeGA7t+UYP1/7eAkWNLU0+P3QrEqKQ== +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + keygrip@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226" @@ -8616,6 +8967,18 @@ klona@^2.0.4: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== +koa-body@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/koa-body/-/koa-body-6.0.1.tgz#46c490033cceebb2874c53cfbb04c45562cf3c84" + integrity sha512-M8ZvMD8r+kPHy28aWP9VxL7kY8oPWA+C7ZgCljrCMeaU7uX6wsIQgDHskyrAr9sw+jqnIXyv4Mlxri5R4InIJg== + dependencies: + "@types/co-body" "^6.1.0" + "@types/formidable" "^2.0.5" + "@types/koa" "^2.13.5" + co-body "^6.1.0" + formidable "^2.0.1" + zod "^3.19.1" + koa-bodyparser@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/koa-bodyparser/-/koa-bodyparser-4.4.0.tgz#2271cd7d603a08c47a47e82d2c00630379672e6c" @@ -8637,6 +9000,31 @@ koa-convert@^2.0.0: co "^4.6.0" koa-compose "^4.1.0" +koa-qs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/koa-qs/-/koa-qs-3.0.0.tgz#390b0b5e9be0658b0f863ceb121da9a414b78d78" + integrity sha512-05IB5KirwMs3heWW26iTz46HuMAtrlrRMus/aNH1BRDocLyF/099EtCB0MIfQpRuT0TISvaTsWwSy2gctIWiGA== + dependencies: + merge-descriptors "^1.0.1" + qs "^6.9.4" + +koa-send@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/koa-send/-/koa-send-5.0.1.tgz#39dceebfafb395d0d60beaffba3a70b4f543fe79" + integrity sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ== + dependencies: + debug "^4.1.1" + http-errors "^1.7.3" + resolve-path "^1.4.0" + +koa-static@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/koa-static/-/koa-static-5.0.0.tgz#5e92fc96b537ad5219f425319c95b64772776943" + integrity sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ== + dependencies: + debug "^3.1.0" + koa-send "^5.0.0" + koa@^2.13.4: version "2.13.4" resolved "https://registry.yarnpkg.com/koa/-/koa-2.13.4.tgz#ee5b0cb39e0b8069c38d115139c774833d32462e" @@ -8666,6 +9054,35 @@ koa@^2.13.4: type-is "^1.6.16" vary "^1.1.2" +koa@^2.14.2: + version "2.14.2" + resolved "https://registry.yarnpkg.com/koa/-/koa-2.14.2.tgz#a57f925c03931c2b4d94b19d2ebf76d3244863fc" + integrity sha512-VFI2bpJaodz6P7x2uyLiX6RLYpZmOJqNmoCst/Yyd7hQlszyPwG/I9CQJ63nOtKSxpt5M7NH67V6nJL2BwCl7g== + dependencies: + accepts "^1.3.5" + cache-content-type "^1.0.0" + content-disposition "~0.5.2" + content-type "^1.0.4" + cookies "~0.8.0" + debug "^4.3.2" + delegates "^1.0.0" + depd "^2.0.0" + destroy "^1.0.4" + encodeurl "^1.0.2" + escape-html "^1.0.3" + fresh "~0.5.2" + http-assert "^1.3.0" + http-errors "^1.6.3" + is-generator-function "^1.0.7" + koa-compose "^4.1.0" + koa-convert "^2.0.0" + on-finished "^2.3.0" + only "~0.0.2" + parseurl "^1.3.2" + statuses "^1.5.0" + type-is "^1.6.16" + vary "^1.1.2" + kuler@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" @@ -8871,21 +9288,46 @@ lodash.escaperegexp@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc= +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + lodash.memoize@4.x: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -8896,6 +9338,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash@^4, lodash@^4.17.10, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -8937,6 +9384,11 @@ logform@^2.3.2, logform@^2.4.0: safe-stable-stringify "^2.3.1" triple-beam "^1.3.0" +long-timeout@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514" + integrity sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w== + long@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" @@ -9127,7 +9579,7 @@ meow@^8.0.0: type-fest "^0.18.0" yargs-parser "^20.2.3" -merge-descriptors@1.0.1: +merge-descriptors@1.0.1, merge-descriptors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= @@ -9919,7 +10371,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -10439,7 +10891,7 @@ path-exists@^5.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-5.0.0.tgz#a6aad9489200b21fab31e49cf09277e5116fb9e7" integrity sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ== -path-is-absolute@^1.0.0: +path-is-absolute@1.0.1, path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= @@ -10817,6 +11269,13 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" +qs@^6.11.0, qs@^6.9.4: + version "6.11.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" + integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== + dependencies: + side-channel "^1.0.4" + qs@^6.5.2: version "6.11.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.1.tgz#6c29dff97f0c0060765911ba65cbc9764186109f" @@ -11341,6 +11800,14 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== +resolve-path@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/resolve-path/-/resolve-path-1.4.0.tgz#c4bda9f5efb2fce65247873ab36bb4d834fe16f7" + integrity sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w== + dependencies: + http-errors "~1.6.2" + path-is-absolute "1.0.1" + resolve.exports@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.1.tgz#cee884cd4e3f355660e501fa3276b27d7ffe5a20" @@ -11621,6 +12088,13 @@ semver@^7.5.0: dependencies: lru-cache "^6.0.0" +semver@^7.5.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + semver@~7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" @@ -11883,6 +12357,44 @@ smart-buffer@^4.0.2, smart-buffer@^4.2.0: resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== +socket.io-adapter@~2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12" + integrity sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA== + dependencies: + ws "~8.11.0" + +socket.io-client@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.7.2.tgz#f2f13f68058bd4e40f94f2a1541f275157ff2c08" + integrity sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.5.2" + socket.io-parser "~4.2.4" + +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + +socket.io@^4.7.1: + version "4.7.2" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.7.2.tgz#22557d76c3f3ca48f82e73d68b7add36a22df002" + integrity sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw== + dependencies: + accepts "~1.3.4" + base64id "~2.0.0" + cors "~2.8.5" + debug "~4.3.2" + engine.io "~6.5.2" + socket.io-adapter "~2.5.2" + socket.io-parser "~4.2.4" + sockjs@^0.3.24: version "0.3.24" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" @@ -12727,6 +13239,11 @@ truncate-utf8-bytes@^1.0.0: dependencies: utf8-byte-length "^1.0.1" +ts-algebra@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/ts-algebra/-/ts-algebra-1.2.2.tgz#b75d301c28cd4126cd344760a47b43e48e2872e0" + integrity sha512-kloPhf1hq3JbCPOTYoOWDKxebWjNb2o/LKnNfkWhxVVisFFmMJPPdJeGoGmM+iRLyoXAR61e08Pb+vUXINg8aA== + ts-jest@^29.0.5: version "29.0.5" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.0.5.tgz#c5557dcec8fe434fcb8b70c3e21c6b143bfce066" @@ -13133,6 +13650,11 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + v8-compile-cache@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" @@ -13169,7 +13691,7 @@ validate-npm-package-name@^4.0.0: dependencies: builtins "^5.0.0" -vary@^1.1.2, vary@~1.1.2: +vary@^1, vary@^1.1.2, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= @@ -13571,6 +14093,11 @@ ws@^8.4.2: resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b" integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA== +ws@~8.11.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" + integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== + xkeys@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xkeys/-/xkeys-3.0.0.tgz#71e61441594e4fe4778ec6ce092a026b61ee0c92" @@ -13613,6 +14140,11 @@ xmlbuilder@~11.0.0: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== +xmlhttprequest-ssl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67" + integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== + xtend@^4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -13728,3 +14260,8 @@ yup@1.0.0-beta.2: property-expr "^2.0.4" tiny-case "^1.0.2" toposort "^2.0.2" + +zod@^3.19.1: + version "3.22.2" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.2.tgz#3add8c682b7077c05ac6f979fea6998b573e157b" + integrity sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==