From e3246f54e242c2eedb430a297aff4b44e2ab6d2a Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Sat, 11 Jan 2025 16:06:43 +0100 Subject: [PATCH] refactor: remove legacy service --- apps/cli/main.js | 5 +- apps/client/src/common/models/Info.ts | 3 - apps/client/src/common/models/OscSettings.ts | 10 - .../src/features/app-settings/AppSettings.tsx | 2 - .../integrations-panel/HttpIntegrations.tsx | 189 ----------- .../IntegrationsPanel.module.css | 16 - .../integrations-panel/IntegrationsPanel.tsx | 42 --- .../integrations-panel/OscIntegrations.tsx | 310 ------------------ .../integrations-panel/integrationUtils.ts | 19 -- .../app-settings/useAppSettingsMenu.tsx | 8 - apps/electron/src/main.js | 5 +- .../api-data/automation/automation.parser.ts | 25 ++ .../src/api-data/http/http.controller.ts | 31 -- apps/server/src/api-data/http/http.router.ts | 9 - .../src/api-data/http/http.validation.ts | 21 -- apps/server/src/api-data/index.ts | 4 - .../server/src/api-data/osc/osc.controller.ts | 32 -- apps/server/src/api-data/osc/osc.router.ts | 8 - .../server/src/api-data/osc/osc.validation.ts | 25 -- .../src/api-data/session/session.service.ts | 2 - apps/server/src/app.ts | 44 +-- apps/server/src/index.ts | 6 +- apps/server/src/models/dataModel.ts | 12 - .../integration-service/HttpIntegration.ts | 80 ----- .../integration-service/IIntegration.ts | 11 - .../integration-service/IntegrationService.ts | 37 --- .../integration-service/OscIntegration.ts | 147 --------- .../integrationUtils.test.ts | 169 ---------- .../integration-service/integrationUtils.ts | 73 ----- .../project-service/ProjectService.ts | 14 +- .../runtime-service/RuntimeService.ts | 1 - .../server/src/utils/__tests__/parser.test.ts | 4 +- .../utils/__tests__/parserFunctions.test.ts | 143 -------- apps/server/src/utils/parser.ts | 12 +- apps/server/src/utils/parserFunctions.ts | 103 +----- .../ontime-controller/BackendResponse.type.ts | 2 - .../types/src/definitions/DataModel.type.ts | 4 - .../src/definitions/core/HttpSettings.type.ts | 8 - .../src/definitions/core/OscSettings.type.ts | 18 - packages/types/src/index.ts | 4 - 40 files changed, 35 insertions(+), 1623 deletions(-) delete mode 100644 apps/client/src/common/models/OscSettings.ts delete mode 100644 apps/client/src/features/app-settings/panel/integrations-panel/HttpIntegrations.tsx delete mode 100644 apps/client/src/features/app-settings/panel/integrations-panel/IntegrationsPanel.module.css delete mode 100644 apps/client/src/features/app-settings/panel/integrations-panel/IntegrationsPanel.tsx delete mode 100644 apps/client/src/features/app-settings/panel/integrations-panel/OscIntegrations.tsx delete mode 100644 apps/client/src/features/app-settings/panel/integrations-panel/integrationUtils.ts delete mode 100644 apps/server/src/api-data/http/http.controller.ts delete mode 100644 apps/server/src/api-data/http/http.router.ts delete mode 100644 apps/server/src/api-data/http/http.validation.ts delete mode 100644 apps/server/src/api-data/osc/osc.controller.ts delete mode 100644 apps/server/src/api-data/osc/osc.router.ts delete mode 100644 apps/server/src/api-data/osc/osc.validation.ts delete mode 100644 apps/server/src/services/integration-service/HttpIntegration.ts delete mode 100644 apps/server/src/services/integration-service/IIntegration.ts delete mode 100644 apps/server/src/services/integration-service/IntegrationService.ts delete mode 100644 apps/server/src/services/integration-service/OscIntegration.ts delete mode 100644 apps/server/src/services/integration-service/integrationUtils.test.ts delete mode 100644 apps/server/src/services/integration-service/integrationUtils.ts delete mode 100644 packages/types/src/definitions/core/HttpSettings.type.ts delete mode 100644 packages/types/src/definitions/core/OscSettings.type.ts diff --git a/apps/cli/main.js b/apps/cli/main.js index 73f5260ba5..a2354a407d 100644 --- a/apps/cli/main.js +++ b/apps/cli/main.js @@ -3,14 +3,11 @@ // NOTE: for now the following needs to be in place: ./server/index.cjs, ./client, ./external const ontimeServer = require('./server/index.cjs'); -const { initAssets, startServer, startIntegrations, shutdown } = ontimeServer; +const { initAssets, startServer, shutdown } = ontimeServer; async function startOntime() { await initAssets(); - await startServer(); - - await startIntegrations(); } startOntime(); diff --git a/apps/client/src/common/models/Info.ts b/apps/client/src/common/models/Info.ts index 1cec492a80..d05acdd2b0 100644 --- a/apps/client/src/common/models/Info.ts +++ b/apps/client/src/common/models/Info.ts @@ -1,11 +1,8 @@ import { GetInfo } from 'ontime-types'; -import { oscPlaceholderSettings } from './OscSettings'; - export const ontimePlaceholderInfo: GetInfo = { networkInterfaces: [], version: '2.0.0', serverPort: 4001, - osc: oscPlaceholderSettings, publicDir: '', }; diff --git a/apps/client/src/common/models/OscSettings.ts b/apps/client/src/common/models/OscSettings.ts deleted file mode 100644 index 365b9ae8c1..0000000000 --- a/apps/client/src/common/models/OscSettings.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { OSCSettings } from 'ontime-types'; - -export const oscPlaceholderSettings: OSCSettings = { - portIn: 8888, - portOut: 9999, - targetIP: '127.0.0.1', - enabledIn: false, - enabledOut: false, - subscriptions: [], -}; diff --git a/apps/client/src/features/app-settings/AppSettings.tsx b/apps/client/src/features/app-settings/AppSettings.tsx index a4ceb8cc6e..3644c3d068 100644 --- a/apps/client/src/features/app-settings/AppSettings.tsx +++ b/apps/client/src/features/app-settings/AppSettings.tsx @@ -7,7 +7,6 @@ import AutomationPanel from './panel/automations-panel/AutomationPanel'; import ClientControlPanel from './panel/client-control-panel/ClientControlPanel'; import FeatureSettingsPanel from './panel/feature-settings-panel/FeatureSettingsPanel'; import GeneralPanel from './panel/general-panel/GeneralPanel'; -import IntegrationsPanel from './panel/integrations-panel/IntegrationsPanel'; import NetworkLogPanel from './panel/network-panel/NetworkLogPanel'; import ProjectPanel from './panel/project-panel/ProjectPanel'; import ShutdownPanel from './panel/shutdown-panel/ShutdownPanel'; @@ -31,7 +30,6 @@ export default function AppSettings() { {panel === 'general' && } {panel === 'feature_settings' && } {panel === 'sources' && } - {panel === 'integrations' && } {panel === 'automation' && } {panel === 'client_control' && } {panel === 'about' && } diff --git a/apps/client/src/features/app-settings/panel/integrations-panel/HttpIntegrations.tsx b/apps/client/src/features/app-settings/panel/integrations-panel/HttpIntegrations.tsx deleted file mode 100644 index 7e28fe8732..0000000000 --- a/apps/client/src/features/app-settings/panel/integrations-panel/HttpIntegrations.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import { Controller, useFieldArray, useForm } from 'react-hook-form'; -import { Button, IconButton, Input, Select, Switch } from '@chakra-ui/react'; -import { IoAdd } from '@react-icons/all-files/io5/IoAdd'; -import { IoTrash } from '@react-icons/all-files/io5/IoTrash'; -import { HttpSettings } from 'ontime-types'; -import { generateId } from 'ontime-utils'; - -import { maybeAxiosError } from '../../../../common/api/utils'; -import { useHttpSettings, usePostHttpSettings } from '../../../../common/hooks-query/useHttpSettings'; -import { isKeyEscape } from '../../../../common/utils/keyEvent'; -import { startsWithHttp } from '../../../../common/utils/regex'; -import * as Panel from '../../panel-utils/PanelUtils'; - -import { cycles } from './integrationUtils'; - -import style from './IntegrationsPanel.module.css'; - -export default function HttpIntegrations() { - const { data, status } = useHttpSettings(); - const { mutateAsync } = usePostHttpSettings(); - - const { - control, - handleSubmit, - reset, - register, - setError, - formState: { errors, isSubmitting, isDirty, isValid }, - } = useForm({ - mode: 'onChange', - defaultValues: data, - values: data, - resetOptions: { - keepDirtyValues: true, - }, - }); - - const { fields, prepend, remove } = useFieldArray({ - name: 'subscriptions', - control, - }); - - const onSubmit = async (values: HttpSettings) => { - try { - await mutateAsync(values); - } catch (error) { - setError('root', { message: maybeAxiosError(error) }); - } - }; - - const preventEscape = (event: React.KeyboardEvent) => { - if (isKeyEscape(event)) { - event.preventDefault(); - event.stopPropagation(); - } - }; - - const handleAddNewSubscription = () => { - prepend({ - id: generateId(), - cycle: 'onLoad', - message: '', - enabled: true, - }); - }; - - const handleDeleteSubscription = (index: number) => { - remove(index); - }; - - const canSubmit = !isSubmitting && isDirty && isValid; - const isLoading = status === 'pending'; - - return ( - - - - HTTP settings -
- - -
-
- - - - {errors?.root && {errors.root.message}} - - - - ( - - )} - /> - - - - - - - HTTP Integration - - - {fields.length > 0 && ( - - - - Enabled - Cycle - Message - - - - - {fields.map((integration, index) => { - // @ts-expect-error -- not sure why it is not finding the type, it is ok - const maybeError = errors.subscriptions?.[index]?.message?.message; - return ( - - - - - - - - - - {maybeError && {maybeError}} - - - } - aria-label='Delete entry' - onClick={() => handleDeleteSubscription(index)} - /> - - - ); - })} - - - )} - -
-
- ); -} diff --git a/apps/client/src/features/app-settings/panel/integrations-panel/IntegrationsPanel.module.css b/apps/client/src/features/app-settings/panel/integrations-panel/IntegrationsPanel.module.css deleted file mode 100644 index 606f7f934d..0000000000 --- a/apps/client/src/features/app-settings/panel/integrations-panel/IntegrationsPanel.module.css +++ /dev/null @@ -1,16 +0,0 @@ -.fullWidth { - width: 100%; -} - -.halfWidth { - width: 50%; -} - -.fitContents.fitContents { - width: max-content; /* override chakra */ -} - -.flex { - display: flex; - gap: 1rem; -} diff --git a/apps/client/src/features/app-settings/panel/integrations-panel/IntegrationsPanel.tsx b/apps/client/src/features/app-settings/panel/integrations-panel/IntegrationsPanel.tsx deleted file mode 100644 index ac25cd7aae..0000000000 --- a/apps/client/src/features/app-settings/panel/integrations-panel/IntegrationsPanel.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { Alert, AlertDescription, AlertIcon } from '@chakra-ui/react'; - -import ExternalLink from '../../../../common/components/external-link/ExternalLink'; -import useScrollIntoView from '../../../../common/hooks/useScrollIntoView'; -import type { PanelBaseProps } from '../../panel-list/PanelList'; -import * as Panel from '../../panel-utils/PanelUtils'; - -import HttpIntegrations from './HttpIntegrations'; -import OscIntegrations from './OscIntegrations'; - -const integrationDocsUrl = 'https://docs.getontime.no/api/integrations/'; - -export default function IntegrationsPanel({ location }: PanelBaseProps) { - const oscRef = useScrollIntoView('osc', location); - const httpRef = useScrollIntoView('http', location); - - return ( - <> - Integration settings - - - - - Integrations allow Ontime to receive commands or send its data to other systems in your workflow.
-
- Currently supported protocols are OSC (Open Sound Control), HTTP and Websockets.
- WebSockets are used for Ontime and cannot be configured independently.
- See the docs -
-
-
- -
- -
-
- -
-
- - ); -} diff --git a/apps/client/src/features/app-settings/panel/integrations-panel/OscIntegrations.tsx b/apps/client/src/features/app-settings/panel/integrations-panel/OscIntegrations.tsx deleted file mode 100644 index b235836859..0000000000 --- a/apps/client/src/features/app-settings/panel/integrations-panel/OscIntegrations.tsx +++ /dev/null @@ -1,310 +0,0 @@ -import { useEffect } from 'react'; -import { Controller, useFieldArray, useForm } from 'react-hook-form'; -import { Button, IconButton, Input, Select, Switch } from '@chakra-ui/react'; -import { IoAdd } from '@react-icons/all-files/io5/IoAdd'; -import { IoTrash } from '@react-icons/all-files/io5/IoTrash'; -import { OSCSettings } from 'ontime-types'; -import { generateId } from 'ontime-utils'; - -import { maybeAxiosError } from '../../../../common/api/utils'; -import useOscSettings, { useOscSettingsMutation } from '../../../../common/hooks-query/useOscSettings'; -import { preventEscape } from '../../../../common/utils/keyEvent'; -import { isASCII, isASCIIorEmpty, isIPAddress, isOnlyNumbers, startsWithSlash } from '../../../../common/utils/regex'; -import { isOntimeCloud } from '../../../../externals'; -import * as Panel from '../../panel-utils/PanelUtils'; - -import { cycles } from './integrationUtils'; - -import style from './IntegrationsPanel.module.css'; - -export default function OscIntegrations() { - const { data, status } = useOscSettings(); - const { mutateAsync } = useOscSettingsMutation(); - - const { - control, - handleSubmit, - reset, - register, - setError, - formState: { errors, isSubmitting, isDirty, isValid }, - } = useForm({ - mode: 'onChange', - defaultValues: data, - values: data, - resetOptions: { - keepDirtyValues: true, - }, - }); - - const { fields, prepend, remove } = useFieldArray({ - name: 'subscriptions', - control, - }); - - // update form if we get new data from server - useEffect(() => { - if (data) { - reset(data); - } - }, [data, reset]); - - const onSubmit = async (values: OSCSettings) => { - if (values.portIn === values.portOut) { - setError('portIn', { message: 'OSC IN and OUT Ports cant be the same' }); - return; - } - - const parsedValues = { ...values, portIn: Number(values.portIn), portOut: Number(values.portOut) }; - try { - await mutateAsync(parsedValues); - } catch (error) { - setError('root', { message: maybeAxiosError(error) }); - } - }; - - const handleAddNewSubscription = () => { - prepend({ - id: generateId(), - cycle: 'onLoad', - address: '', - payload: '', - enabled: true, - }); - }; - - const handleDeleteSubscription = (index: number) => { - remove(index); - }; - - const canSubmit = !isSubmitting && isDirty && isValid; - const isLoading = status === 'pending'; - - return ( - - - OSC settings -
- - -
-
- {isOntimeCloud && ( - For security reasons OSC integrations are not available in the cloud service. - )} - - - - - - General OSC settings - {errors?.root && {errors.root.message}} - - - - ( - - )} - /> - - - - - - - - - - ( - - )} - /> - - - - - - - - - - - - - - - OSC integrations - - - - {fields.length > 0 && ( - - - - Enabled - Cycle - Address - Arguments - - - - - {fields.map((field, index) => { - const maybeAddressError = errors.subscriptions?.[index]?.address?.message; - const maybePayloadError = errors.subscriptions?.[index]?.payload?.message; - return ( - - - - - - - - - - startsWithSlash.test(value) || 'OSC address should start with a forward slash', - oscStringIsAscii: (value) => - isASCII.test(value) || 'OSC address only allow ASCII characters', - }, - })} - /> - {maybeAddressError && {maybeAddressError}} - - - - isASCIIorEmpty.test(value) || 'OSC arguments only allow ASCII characters', - }, - })} - /> - {maybePayloadError && {maybePayloadError}} - - - } - aria-label='Delete entry' - onClick={() => handleDeleteSubscription(index)} - /> - - - ); - })} - - - )} - -
- ); -} diff --git a/apps/client/src/features/app-settings/panel/integrations-panel/integrationUtils.ts b/apps/client/src/features/app-settings/panel/integrations-panel/integrationUtils.ts deleted file mode 100644 index 1f0456cfa8..0000000000 --- a/apps/client/src/features/app-settings/panel/integrations-panel/integrationUtils.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { TimerLifeCycle } from 'ontime-types'; - -type CycleLabel = { - id: number; - label: string; - value: keyof typeof TimerLifeCycle; -}; - -export const cycles: CycleLabel[] = [ - { id: 1, label: 'On Load', value: 'onLoad' }, - { id: 2, label: 'On Start', value: 'onStart' }, - { id: 3, label: 'On Pause', value: 'onPause' }, - { id: 4, label: 'On Stop', value: 'onStop' }, - { id: 5, label: 'Every second', value: 'onClock' }, - { id: 5, label: 'On Timer Update', value: 'onUpdate' }, - { id: 6, label: 'On Finish', value: 'onFinish' }, - { id: 7, label: 'On Warning', value: 'onWarning' }, - { id: 8, label: 'On Danger', value: 'onDanger' }, -]; diff --git a/apps/client/src/features/app-settings/useAppSettingsMenu.tsx b/apps/client/src/features/app-settings/useAppSettingsMenu.tsx index 5faebdbae9..75350d65ff 100644 --- a/apps/client/src/features/app-settings/useAppSettingsMenu.tsx +++ b/apps/client/src/features/app-settings/useAppSettingsMenu.tsx @@ -46,14 +46,6 @@ const staticOptions = [ ], split: true, }, - { - id: 'integrations', - label: 'Integrations', - secondary: [ - { id: 'integrations__osc', label: 'OSC settings' }, - { id: 'integrations__http', label: 'HTTP settings' }, - ], - }, { id: 'automation', label: 'Automation', diff --git a/apps/electron/src/main.js b/apps/electron/src/main.js index f200ac7e33..ae6a0e1de8 100644 --- a/apps/electron/src/main.js +++ b/apps/electron/src/main.js @@ -47,15 +47,12 @@ async function startBackend() { } const ontimeServer = require(nodePath); - const { initAssets, startServer, startIntegrations } = ontimeServer; + const { initAssets, startServer } = ontimeServer; await initAssets(); const result = await startServer(escalateError); loaded = result.message; - - await startIntegrations(); - return result.serverPort; } diff --git a/apps/server/src/api-data/automation/automation.parser.ts b/apps/server/src/api-data/automation/automation.parser.ts index c3d007da92..54e881f18c 100644 --- a/apps/server/src/api-data/automation/automation.parser.ts +++ b/apps/server/src/api-data/automation/automation.parser.ts @@ -4,10 +4,35 @@ import { dbModel } from '../../models/dataModel.js'; import type { ErrorEmitter } from '../../utils/parser.js'; export function parseAutomationSettings(data: Partial, emitError?: ErrorEmitter): AutomationSettings { + /** + * Leaving a path for migrating users to the new automations + * This should be removed after a few releases + */ + // @ts-expect-error -- these are legacy keys that may exist in a file <= 3.9.5 + if (data.http || data.osc) { + emitError?.('Found legacy integrations'); + console.log('Found legacy integrations...'); + // @ts-expect-error -- these are legacy keys that may exist in a file <= 3.9.5 + if (data.osc) { + return { + enabledAutomations: dbModel.automation.enabledAutomations, + // @ts-expect-error -- these are legacy keys that may exist in a file <= 3.9.5 + enabledOscIn: data.osc?.enabledIn ?? dbModel.automation.enabledOscIn, + // @ts-expect-error -- these are legacy keys that may exist in a file <= 3.9.5 + oscPortIn: data.osc?.portIn ?? dbModel.automation.oscPortIn, + automations: [], + blueprints: {}, + }; + } else { + return { ...dbModel.automation }; + } + } + if (!data.automation) { emitError?.('No data found to import'); return { ...dbModel.automation }; } + console.log('Found Automation settings, importing...'); return { enabledAutomations: data.automation.enabledAutomations ?? dbModel.automation.enabledAutomations, diff --git a/apps/server/src/api-data/http/http.controller.ts b/apps/server/src/api-data/http/http.controller.ts deleted file mode 100644 index 5812743122..0000000000 --- a/apps/server/src/api-data/http/http.controller.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { ErrorResponse, HttpSettings } from 'ontime-types'; -import { getErrorMessage } from 'ontime-utils'; - -import type { Request, Response } from 'express'; - -import { failEmptyObjects } from '../../utils/routerUtils.js'; -import { httpIntegration } from '../../services/integration-service/HttpIntegration.js'; -import { getDataProvider } from '../../classes/data-provider/DataProvider.js'; - -export async function getHTTP(_req: Request, res: Response) { - const http = getDataProvider().getHttp(); - res.status(200).send(http); -} - -export async function postHTTP(req: Request, res: Response) { - if (failEmptyObjects(req.body, res)) { - return; - } - - try { - const httpSettings = req.body; - - httpIntegration.init(httpSettings); - // we persist the data after init to avoid persisting invalid data - const result = await getDataProvider().setHttp(httpSettings); - res.send(result).status(200); - } catch (error) { - const message = getErrorMessage(error); - res.status(400).send({ message }); - } -} diff --git a/apps/server/src/api-data/http/http.router.ts b/apps/server/src/api-data/http/http.router.ts deleted file mode 100644 index 8e9fb645ad..0000000000 --- a/apps/server/src/api-data/http/http.router.ts +++ /dev/null @@ -1,9 +0,0 @@ -import express from 'express'; - -import { validateHTTP } from './http.validation.js'; -import { getHTTP, postHTTP } from './http.controller.js'; - -export const router = express.Router(); - -router.get('/', getHTTP); -router.post('/', validateHTTP, postHTTP); diff --git a/apps/server/src/api-data/http/http.validation.ts b/apps/server/src/api-data/http/http.validation.ts deleted file mode 100644 index 0acd9b4340..0000000000 --- a/apps/server/src/api-data/http/http.validation.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { body, validationResult } from 'express-validator'; -import { Request, Response, NextFunction } from 'express'; - -import { sanitiseHttpSubscriptions } from '../../utils/parserFunctions.js'; - -/** - * @description Validates object for POST /ontime/http - */ -export const validateHTTP = [ - body('enabledOut').exists().isBoolean(), - body('subscriptions') - .exists() - .isArray() - .custom((value) => sanitiseHttpSubscriptions(value)), - - (req: Request, res: Response, next: NextFunction) => { - const errors = validationResult(req); - if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() }); - next(); - }, -]; diff --git a/apps/server/src/api-data/index.ts b/apps/server/src/api-data/index.ts index 9024aaff5e..d7211b9096 100644 --- a/apps/server/src/api-data/index.ts +++ b/apps/server/src/api-data/index.ts @@ -4,8 +4,6 @@ import { router as automationsRouter } from './automation/automation.router.js'; import { router as urlPresetsRouter } from './url-presets/urlPresets.router.js'; import { router as customFieldsRouter } from './custom-fields/customFields.router.js'; import { router as dbRouter } from './db/db.router.js'; -import { router as httpRouter } from './http/http.router.js'; -import { router as oscRouter } from './osc/osc.router.js'; import { router as projectRouter } from './project/project.router.js'; import { router as rundownRouter } from './rundown/rundown.router.js'; import { router as settingsRouter } from './settings/settings.router.js'; @@ -19,8 +17,6 @@ export const appRouter = express.Router(); appRouter.use('/automations', automationsRouter); appRouter.use('/custom-fields', customFieldsRouter); appRouter.use('/db', dbRouter); -appRouter.use('/http', httpRouter); -appRouter.use('/osc', oscRouter); appRouter.use('/project', projectRouter); appRouter.use('/rundown', rundownRouter); appRouter.use('/settings', settingsRouter); diff --git a/apps/server/src/api-data/osc/osc.controller.ts b/apps/server/src/api-data/osc/osc.controller.ts deleted file mode 100644 index 9c1a23139d..0000000000 --- a/apps/server/src/api-data/osc/osc.controller.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { ErrorResponse, OSCSettings } from 'ontime-types'; -import { getErrorMessage } from 'ontime-utils'; - -import type { Request, Response } from 'express'; - -import { failEmptyObjects } from '../../utils/routerUtils.js'; -import { oscIntegration } from '../../services/integration-service/OscIntegration.js'; -import { getDataProvider } from '../../classes/data-provider/DataProvider.js'; - -export async function getOSC(_req: Request, res: Response) { - const osc = getDataProvider().getOsc(); - res.status(200).send(osc); -} - -export async function postOSC(req: Request, res: Response) { - if (failEmptyObjects(req.body, res)) { - return; - } - - try { - const oscSettings = req.body; - - oscIntegration.init(oscSettings); - // we persist the data after init to avoid persisting invalid data - const result = await getDataProvider().setOsc(oscSettings); - - res.send(result).status(200); - } catch (error) { - const message = getErrorMessage(error); - res.status(400).send({ message }); - } -} diff --git a/apps/server/src/api-data/osc/osc.router.ts b/apps/server/src/api-data/osc/osc.router.ts deleted file mode 100644 index 7ff46413b6..0000000000 --- a/apps/server/src/api-data/osc/osc.router.ts +++ /dev/null @@ -1,8 +0,0 @@ -import express from 'express'; -import { getOSC, postOSC } from './osc.controller.js'; -import { validateOSC } from './osc.validation.js'; - -export const router = express.Router(); - -router.get('/', getOSC); -router.post('/', validateOSC, postOSC); diff --git a/apps/server/src/api-data/osc/osc.validation.ts b/apps/server/src/api-data/osc/osc.validation.ts deleted file mode 100644 index 72ebdc973d..0000000000 --- a/apps/server/src/api-data/osc/osc.validation.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { body, validationResult } from 'express-validator'; -import { Request, Response, NextFunction } from 'express'; - -import { sanitiseOscSubscriptions } from '../../utils/parserFunctions.js'; - -/** - * @description Validates object for POST /ontime/osc - */ -export const validateOSC = [ - body('portIn').exists().isPort(), - body('portOut').exists().isPort(), - body('targetIP').exists().isIP(), - body('enabledIn').exists().isBoolean(), - body('enabledOut').exists().isBoolean(), - body('subscriptions') - .exists() - .isArray() - .custom((value) => sanitiseOscSubscriptions(value)), - - (req: Request, res: Response, next: NextFunction) => { - const errors = validationResult(req); - if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() }); - next(); - }, -]; diff --git a/apps/server/src/api-data/session/session.service.ts b/apps/server/src/api-data/session/session.service.ts index 7fbee53c35..75af229a67 100644 --- a/apps/server/src/api-data/session/session.service.ts +++ b/apps/server/src/api-data/session/session.service.ts @@ -33,7 +33,6 @@ export async function getSessionStats(): Promise { */ export async function getInfo(): Promise { const { version, serverPort } = getDataProvider().getSettings(); - const osc = getDataProvider().getOsc(); // get nif and inject localhost const ni = getNetworkInterfaces(); @@ -43,7 +42,6 @@ export async function getInfo(): Promise { networkInterfaces: ni, version, serverPort, - osc, publicDir: publicDir.root, }; } diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index 233e2f5471..957f1488b8 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -10,7 +10,7 @@ import { extname } from 'node:path'; // import utils import { publicDir, srcDir, srcFiles } from './setup/index.js'; -import { environment, isOntimeCloud, isProduction, updateRouterPrefix } from './externals.js'; +import { environment, isProduction, updateRouterPrefix } from './externals.js'; import { ONTIME_VERSION } from './ONTIME_VERSION.js'; import { consoleSuccess, consoleHighlight, consoleError } from './utils/console.js'; @@ -23,10 +23,7 @@ import { socket } from './adapters/WebsocketAdapter.js'; import { getDataProvider } from './classes/data-provider/DataProvider.js'; // Services -import { integrationService } from './services/integration-service/IntegrationService.js'; import { logger } from './classes/Logger.js'; -import { oscIntegration } from './services/integration-service/OscIntegration.js'; -import { httpIntegration } from './services/integration-service/HttpIntegration.js'; import { populateStyles } from './setup/loadStyles.js'; import { eventStore } from './stores/EventStore.js'; import { runtimeService } from './services/runtime-service/RuntimeService.js'; @@ -146,7 +143,6 @@ enum OntimeStartOrder { Error, InitAssets, InitServer, - InitIO, } let step = OntimeStartOrder.InitAssets; @@ -155,7 +151,7 @@ let expressServer: Server | null = null; const checkStart = (currentState: OntimeStartOrder) => { if (step !== currentState) { step = OntimeStartOrder.Error; - throw new Error('Init order error: initAssets > startServer > startOsc > startIntegrations'); + throw new Error('Init order error: initAssets > startServer'); } else { if (step === 1 || step === 2) { step = step + 1; @@ -249,41 +245,6 @@ export const startServer = async ( }; }; -/** - * starts integrations - */ -export const startIntegrations = async () => { - checkStart(OntimeStartOrder.InitIO); - - // if a config is not provided, we use the persisted one - const { osc, http } = getDataProvider().getData(); - - if (http) { - logger.info(LogOrigin.Tx, 'Initialising HTTP Integration...'); - try { - httpIntegration.init(http); - integrationService.register(httpIntegration); - } catch (error) { - logger.error(LogOrigin.Tx, `HTTP Integration initialisation failed: ${error}`); - } - } - - if (isOntimeCloud) { - logger.info(LogOrigin.Tx, 'Skipping OSC in Cloud environment...'); - return; - } - - if (osc) { - logger.info(LogOrigin.Tx, 'Initialising OSC Integration...'); - try { - oscIntegration.init(osc); - integrationService.register(oscIntegration); - } catch (error) { - logger.error(LogOrigin.Tx, 'OSC Integration initialisation failed'); - } - } -}; - /** * @description clean shutdown app services * @param {number} exitCode @@ -303,7 +264,6 @@ export const shutdown = async (exitCode = 0) => { expressServer?.close(); runtimeService.shutdown(); - integrationService.shutdown(); logger.shutdown(); socket.shutdown(); process.exit(exitCode); diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index fcb9eb8a25..b4170864ae 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ import { consoleHighlight, consoleError } from './utils/console.js'; -import { initAssets, startIntegrations, startServer } from './app.js'; +import { initAssets, startServer } from './app.js'; async function startOntime() { try { @@ -11,10 +11,6 @@ async function startOntime() { console.log('\n'); consoleHighlight('Request: Start server...'); await startServer(); - - console.log('\n'); - consoleHighlight('Request: Start integrations...'); - await startIntegrations(); } catch (error) { consoleError(`Request failed: ${error}`); } diff --git a/apps/server/src/models/dataModel.ts b/apps/server/src/models/dataModel.ts index cdf19ada3b..ccd6fb03e6 100644 --- a/apps/server/src/models/dataModel.ts +++ b/apps/server/src/models/dataModel.ts @@ -31,18 +31,6 @@ export const dbModel: DatabaseModel = { }, urlPresets: [], customFields: {}, - osc: { - portIn: 8888, - portOut: 9999, - targetIP: '127.0.0.1', - enabledIn: false, - enabledOut: false, - subscriptions: [], - }, - http: { - enabledOut: false, - subscriptions: [], - }, automation: { enabledAutomations: true, enabledOscIn: true, diff --git a/apps/server/src/services/integration-service/HttpIntegration.ts b/apps/server/src/services/integration-service/HttpIntegration.ts deleted file mode 100644 index 5b7c83a313..0000000000 --- a/apps/server/src/services/integration-service/HttpIntegration.ts +++ /dev/null @@ -1,80 +0,0 @@ -import got from 'got'; - -import { HttpSettings, HttpSubscription, LogOrigin } from 'ontime-types'; - -import IIntegration, { TimerLifeCycleKey } from './IIntegration.js'; -import { parseTemplateNested } from './integrationUtils.js'; -import { logger } from '../../classes/Logger.js'; - -/** - * @description Class contains logic towards outgoing HTTP communications - * @class - */ -export class HttpIntegration implements IIntegration { - subscriptions: HttpSubscription[]; - enabled: boolean; - - constructor() { - this.subscriptions = []; - this.enabled = false; - } - - /** - * Initializes httpClient - */ - init(config: HttpSettings) { - const { subscriptions, enabledOut } = config; - this.initSubscriptions(subscriptions); - this.enabled = enabledOut; - } - - initSubscriptions(subscriptions: HttpSubscription[]) { - this.subscriptions = subscriptions; - } - - dispatch(action: TimerLifeCycleKey, state?: object) { - // noop - if (!this.enabled) { - return; - } - - for (let i = 0; i < this.subscriptions.length; i++) { - const { cycle, message, enabled } = this.subscriptions[i]; - if (cycle !== action || !enabled || !message) { - continue; - } - - const parsedMessage = parseTemplateNested(message, state || {}); - this.emit(parsedMessage); - } - } - - emit(path: string) { - got.get(path, { retry: { limit: 0 } }).catch((err) => { - logger.warning(LogOrigin.Tx, `HTTP Integration: ${err.message}`); - - if (err.code === 'ECONNREFUSED') { - logger.warning(LogOrigin.Tx, `HTTP Integration: '${err.code}' The server refused the connection`); - return; - } - - if (err.code === 'ENOTFOUND') { - logger.warning(LogOrigin.Tx, `HTTP Integration: '${err.code}' DNS lookup failed`); - return; - } - - if (err.code === 'ETIMEDOUT') { - logger.warning(LogOrigin.Tx, `HTTP Integration: '${err.code}' The connection timed out`); - return; - } - - logger.warning(LogOrigin.Tx, `HTTP Integration: ${err.code}`); - }); - } - - shutdown() { - /** shutdown is a no-op here*/ - } -} - -export const httpIntegration = new HttpIntegration(); diff --git a/apps/server/src/services/integration-service/IIntegration.ts b/apps/server/src/services/integration-service/IIntegration.ts deleted file mode 100644 index 9a5ea24da3..0000000000 --- a/apps/server/src/services/integration-service/IIntegration.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { TimerLifeCycle } from 'ontime-types'; - -export type TimerLifeCycleKey = keyof typeof TimerLifeCycle; - -export default interface IIntegration { - subscriptions: T[]; - init: (config: C) => void; - dispatch: (action: TimerLifeCycleKey, state?: object) => void; - emit: (...args: never[]) => unknown; - shutdown: () => void; -} diff --git a/apps/server/src/services/integration-service/IntegrationService.ts b/apps/server/src/services/integration-service/IntegrationService.ts deleted file mode 100644 index 4c8e59086b..0000000000 --- a/apps/server/src/services/integration-service/IntegrationService.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { LogOrigin } from 'ontime-types'; - -import IIntegration, { TimerLifeCycleKey } from './IIntegration.js'; -import { eventStore } from '../../stores/EventStore.js'; -import { logger } from '../../classes/Logger.js'; - -class IntegrationService { - private integrations: IIntegration[]; - - constructor() { - this.integrations = []; - } - - register(integrationService: IIntegration) { - this.integrations.push(integrationService); - } - - unregister(integrationService: IIntegration) { - this.integrations = this.integrations.filter((int) => int !== integrationService); - } - - dispatch(action: TimerLifeCycleKey) { - const state = eventStore.poll(); - this.integrations.forEach((integration) => { - integration.dispatch(action, state); - }); - } - - shutdown() { - logger.info(LogOrigin.Tx, 'Shutdown Integrations'); - this.integrations.forEach((integration) => { - integration.shutdown(); - }); - } -} - -export const integrationService = new IntegrationService(); diff --git a/apps/server/src/services/integration-service/OscIntegration.ts b/apps/server/src/services/integration-service/OscIntegration.ts deleted file mode 100644 index 8e5ae5c674..0000000000 --- a/apps/server/src/services/integration-service/OscIntegration.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { ArgumentType, Client, Message } from 'node-osc'; -import { LogOrigin, MaybeNumber, MaybeString, OSCSettings, OscSubscription } from 'ontime-types'; - -import IIntegration, { TimerLifeCycleKey } from './IIntegration.js'; -import { parseTemplateNested } from './integrationUtils.js'; -import { logger } from '../../classes/Logger.js'; -import { OscServer } from '../../adapters/OscAdapter.js'; -import { stringToOSCArgs } from '../../utils/oscArgParser.js'; - -/** - * @description Class contains logic towards outgoing OSC communications - * @class - */ -export class OscIntegration implements IIntegration { - protected oscClient: null | Client; - protected oscServer: OscServer | null = null; - - subscriptions: OscSubscription[]; - targetIP: MaybeString; - portOut: MaybeNumber; - portIn: MaybeNumber; - enabledOut: boolean; - enabledIn: boolean; - - constructor() { - this.oscClient = null; - this.subscriptions = []; - this.targetIP = null; - this.portOut = null; - this.portIn = null; - this.enabledOut = false; - this.enabledIn = false; - } - - /** - * Initializes oscClient - */ - init(config: OSCSettings) { - const { targetIP, portOut, subscriptions, enabledOut, enabledIn, portIn } = config; - - this.initTX(enabledOut, targetIP, portOut, subscriptions); - this.initRX(enabledIn, portIn); - // return `OSC integration client connected to ${targetIP}:${portOut}`; - } - - private initSubscriptions(subscriptions: OscSubscription[]) { - this.subscriptions = subscriptions; - } - - dispatch(action: TimerLifeCycleKey, state?: object) { - // noop - if (!this.oscClient || !this.enabledOut) { - return; - } - - for (let i = 0; i < this.subscriptions.length; i++) { - const { cycle, address, payload, enabled } = this.subscriptions[i]; - if (cycle !== action || !enabled || !address) { - continue; - } - const parsedAddress = parseTemplateNested(address, state || {}); - const parsedPayload = payload ? parseTemplateNested(payload, state || {}) : undefined; - const parsedArguments = stringToOSCArgs(parsedPayload); - - try { - this.emit(parsedAddress, parsedArguments); - } catch (error) { - logger.error(LogOrigin.Tx, `OSC Integration: ${error}`); - } - } - } - - emit(address: string, args: ArgumentType[]) { - if (!this.oscClient) { - return; - } - - //TODO: Look into using bundles - const message = new Message(address); - message.append(args); - - this.oscClient.send(message); - } - - private initTX(enabledOut: boolean, targetIP: string, portOut: number, subscriptions: OscSubscription[]) { - this.initSubscriptions(subscriptions); - - if (!enabledOut) { - this.targetIP = targetIP; - this.portOut = portOut; - this.enabledOut = enabledOut; - this.shutdownTX(); - return; - } - - if (this.oscClient && targetIP === this.targetIP && portOut === this.portOut) { - // nothing changed that would mean we need a new client - return; - } - - this.targetIP = targetIP; - this.portOut = portOut; - this.enabledOut = enabledOut; - - try { - this.oscClient = new Client(targetIP, portOut); - logger.info(LogOrigin.Tx, `Starting OSC Clint on port: ${portOut}`); - } catch (error) { - this.oscClient = null; - throw new Error(`Failed initialising OSC client: ${error}`); - } - } - - private initRX(enabledIn: boolean, portIn: number) { - if (!enabledIn) { - this.shutdownRX(); - return; - } - - // Start OSC Server - logger.info(LogOrigin.Rx, `Starting OSC Server on port: ${portIn}`); - this.oscServer = new OscServer(portIn); - } - - shutdown() { - this.shutdownTX(); - this.shutdownRX(); - } - - private shutdownTX() { - if (this.oscClient) { - logger.info(LogOrigin.Tx, 'Shutting down OSC integration'); - this.oscClient?.close(); - this.oscClient = null; - } - } - - private shutdownRX() { - if (this.oscServer) { - logger.info(LogOrigin.Rx, 'Shutting down OSC integration'); - this.oscServer?.shutdown(); - this.oscServer = null; - } - } -} - -export const oscIntegration = new OscIntegration(); diff --git a/apps/server/src/services/integration-service/integrationUtils.test.ts b/apps/server/src/services/integration-service/integrationUtils.test.ts deleted file mode 100644 index 3203a53490..0000000000 --- a/apps/server/src/services/integration-service/integrationUtils.test.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { stringToOSCArgs } from '../../utils/oscArgParser.js'; -import { parseTemplateNested } from './integrationUtils.js'; - -describe('parseTemplateNested()', () => { - it('parses string with a single-level variable name', () => { - const store = { timer: 10 }; - const templateString = '/test/{{timer}}'; - const result = parseTemplateNested(templateString, store); - expect(result).toEqual('/test/10'); - }); - - it('parses string with a nested variable name', () => { - const store = { timer: { clock: 10 } }; - const templateString = '/timer/{{timer.clock}}'; - const result = parseTemplateNested(templateString, store); - expect(result).toEqual('/timer/10'); - }); - - it('parses string with multiple variables', () => { - const mockState = { test1: 'that', test2: 'this' }; - const testString = '{{test1}} should replace {{test2}}'; - const expected = `${mockState.test1} should replace ${mockState.test2}`; - - const result = parseTemplateNested(testString, mockState); - expect(result).toStrictEqual(expected); - }); - - it('correctly parses a string without templates', () => { - const testString = 'That should replace {test}'; - - const result = parseTemplateNested(testString, {}); - expect(result).toStrictEqual(testString); - }); - - it('handles scenarios with missing variables', () => { - // by failing to provide a value, we give visibility to - // potential issues in the given string - const mockState = { test1: 'that', test2: 'this' }; - const testString = '{{test1}} should replace {{test2}}, but not {{test3}}'; - const expected = `${mockState.test1} should replace ${mockState.test2}, but not {{test3}}`; - - const result = parseTemplateNested(testString, mockState); - expect(result).toStrictEqual(expected); - }); -}); - -describe('parseNestedTemplate() -> resolveAliasData()', () => { - it('resolves data through callback', () => { - const data = { - not: { - so: { - easy: '3', - }, - }, - }; - const aliases = { - easy: { key: 'not.so.easy', cb: (value: string) => `testing-${value}` }, - }; - - const easyParse = parseTemplateNested('{{human.easy}}', data, aliases); - expect(easyParse).toBe('testing-3'); - }); - it('handles a mixed operation', () => { - const data = { - not: { - so: { - easy: '3', - }, - }, - other: { - value: 42, - }, - }; - const aliases = { - easy: { key: 'not.so.easy', cb: (value: string) => `testing-${value}` }, - }; - - const easyParse = parseTemplateNested('{{other.value}} to {{human.easy}}', data, aliases); - expect(easyParse).toBe('42 to testing-3'); - }); - it('returns given key when not found', () => { - const data = { - not: { - so: { - easy: '3', - }, - }, - other: { - value: 5, - }, - }; - const aliases = { - easy: { key: 'not.so.easy', cb: (value: string) => `testing-${value}` }, - }; - - const easyParse = parseTemplateNested('{{other.value}} to {{human.easy}} {{human.not.found}}', data, aliases); - expect(easyParse).toBe('5 to testing-3 {{human.not.found}}'); - }); -}); - -describe('parseNestedTemplate() -> stringToOSCArgs()', () => { - it('specific osc requirements', () => { - const data = { - not: { - so: { - easy: 'data with space', - empty: '', - number: 1234, - stringNumber: '1234', - }, - }, - }; - - const payloads = [ - { - test: '"string with space and {{not.so.easy}}"', - expect: [{ type: 'string', value: 'string with space and data with space' }], - }, - { - test: '', - expect: [], - }, - { - test: ' ', - expect: [], - }, - { - test: '""', - expect: [{ type: 'string', value: '' }], - }, - { - test: '"string with space and {{not.so.empty}}"', - expect: [{ type: 'string', value: 'string with space and ' }], - }, - { - test: '"string with space and {{not.so.number}}"', - expect: [{ type: 'string', value: 'string with space and 1234' }], - }, - { - test: '"string with space and {{not.so.stringNumber}}"', - expect: [{ type: 'string', value: 'string with space and 1234' }], - }, - { - test: '"{{not.so.easy}}" 1', - expect: [ - { type: 'string', value: 'data with space' }, - { type: 'integer', value: 1 }, - ], - }, - { - test: '"{{not.so.empty}}" 1', - expect: [ - { type: 'string', value: '' }, - { type: 'integer', value: 1 }, - ], - }, - { - test: '', - expect: [], - }, - ]; - - payloads.forEach((payload) => { - const parsedPayload = parseTemplateNested(payload.test, data); - const parsedArguments = stringToOSCArgs(parsedPayload); - expect(parsedArguments).toStrictEqual(payload.expect); - }); - }); -}); diff --git a/apps/server/src/services/integration-service/integrationUtils.ts b/apps/server/src/services/integration-service/integrationUtils.ts deleted file mode 100644 index eee5c44ba9..0000000000 --- a/apps/server/src/services/integration-service/integrationUtils.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { MaybeNumber } from 'ontime-types'; -import { millisToString, removeLeadingZero } from 'ontime-utils'; - -// any value inside double curly braces {{val}} -const placeholderRegex = /{{(.*?)}}/g; - -function formatDisplayFromString(value: string, hideZero = false): string { - let valueInNumber: MaybeNumber = null; - - if (value !== 'null') { - const parsedValue = Number(value); - if (!Number.isNaN(parsedValue)) { - valueInNumber = parsedValue; - } - } - let formatted = millisToString(valueInNumber, { fallback: hideZero ? '00:00' : '00:00:00' }); - if (hideZero) { - formatted = removeLeadingZero(formatted); - } - return formatted; -} - -type AliasesDefinition = Record string }>; -const quickAliases: AliasesDefinition = { - clock: { key: 'clock', cb: (value: string) => formatDisplayFromString(value) }, - duration: { key: 'timer.duration', cb: (value: string) => formatDisplayFromString(value, true) }, - expectedEnd: { - key: 'timer.expectedFinish', - cb: (value: string) => formatDisplayFromString(value), - }, - runningTimer: { - key: 'timer.current', - cb: (value: string) => formatDisplayFromString(value, true), - }, - elapsedTime: { - key: 'timer.elapsed', - cb: (value: string) => formatDisplayFromString(value, true), - }, - startedAt: { key: 'timer.startedAt', cb: (value: string) => formatDisplayFromString(value) }, -}; - -/** - * Parses a templated string to values in a nested object - */ -export function parseTemplateNested(template: string, state: object, humanReadable = quickAliases): string { - let parsedTemplate = template; - const matches = Array.from(parsedTemplate.matchAll(placeholderRegex)); - - for (const match of matches) { - const variableName = match[1]; - const variableParts = variableName.split('.'); - let value: string | undefined = undefined; - - if (variableParts[0] === 'human') { - const lookupKey = variableParts[1]; - if (lookupKey in humanReadable) { - const newTemplate = `{{${humanReadable[lookupKey].key}}}`; - const parsed = parseTemplateNested(newTemplate, state, humanReadable); - value = humanReadable[lookupKey].cb(parsed); - } else { - value = undefined; - } - } else { - // iterate through variable parts, and look for the property in the state object - value = variableParts.reduce((obj, key) => obj?.[key], state); - } - if (value !== undefined) { - parsedTemplate = parsedTemplate.replace(match[0], value); - } - } - - return parsedTemplate; -} diff --git a/apps/server/src/services/project-service/ProjectService.ts b/apps/server/src/services/project-service/ProjectService.ts index 6cc13d9474..6df369401d 100644 --- a/apps/server/src/services/project-service/ProjectService.ts +++ b/apps/server/src/services/project-service/ProjectService.ts @@ -28,8 +28,6 @@ import { setLastLoadedProject, } from '../app-state-service/AppStateService.js'; import { runtimeService } from '../runtime-service/RuntimeService.js'; -import { oscIntegration } from '../integration-service/OscIntegration.js'; -import { httpIntegration } from '../integration-service/HttpIntegration.js'; import { copyCorruptFile, @@ -176,14 +174,10 @@ export async function loadProjectFile(name: string) { // apply data model runtimeService.stop(); - const { rundown, customFields, osc, http } = result.data; + const { rundown, customFields } = result.data; // apply the rundown await initRundown(rundown, customFields); - - // apply integrations - oscIntegration.init(osc); - httpIntegration.init(http); } /** @@ -250,14 +244,10 @@ export async function renameProjectFile(originalFile: string, newFilename: strin // apply data model runtimeService.stop(); - const { rundown, customFields, osc, http } = result.data; + const { rundown, customFields } = result.data; // apply the rundown await initRundown(rundown, customFields); - - // apply integrations - oscIntegration.init(osc); - httpIntegration.init(http); } } diff --git a/apps/server/src/services/runtime-service/RuntimeService.ts b/apps/server/src/services/runtime-service/RuntimeService.ts index 7674e82d31..c4422b54e4 100644 --- a/apps/server/src/services/runtime-service/RuntimeService.ts +++ b/apps/server/src/services/runtime-service/RuntimeService.ts @@ -32,7 +32,6 @@ import { getRundown, getTimedEvents, } from '../rundown-service/rundownUtils.js'; -import { integrationService } from '../integration-service/IntegrationService.js'; import { getForceUpdate, getShouldClockUpdate, getShouldTimerUpdate } from './rundownService.utils.js'; import { skippedOutOfEvent } from '../timerUtils.js'; diff --git a/apps/server/src/utils/__tests__/parser.test.ts b/apps/server/src/utils/__tests__/parser.test.ts index 779d9e400d..5a13b0646c 100644 --- a/apps/server/src/utils/__tests__/parser.test.ts +++ b/apps/server/src/utils/__tests__/parser.test.ts @@ -314,7 +314,7 @@ describe('test parser edge cases', () => { //@ts-expect-error -- we know this is wrong, testing imports outside domain const { data, errors } = parseDatabaseModel(testData); expect(data.rundown.length).toBe(1); - expect(errors.length).toBe(7); + expect(errors.length).toBe(5); }); it('handles incomplete datasets', () => { @@ -724,8 +724,6 @@ describe('test import of v2 datamodel', () => { ); // @ts-expect-error -- checking if the field is removed expect(parsed?.userFields).toBeUndefined(); - expect(parsed.osc).toMatchObject({ subscriptions: [] }); - expect(parsed.http).toMatchObject({ enabledOut: false, subscriptions: [] }); }); }); diff --git a/apps/server/src/utils/__tests__/parserFunctions.test.ts b/apps/server/src/utils/__tests__/parserFunctions.test.ts index 12d69454e5..eface0ea1a 100644 --- a/apps/server/src/utils/__tests__/parserFunctions.test.ts +++ b/apps/server/src/utils/__tests__/parserFunctions.test.ts @@ -2,12 +2,8 @@ import { CustomFields, DatabaseModel, EndAction, - HttpSettings, - HttpSubscription, - OSCSettings, OntimeEvent, OntimeRundown, - OscSubscription, Settings, SupportedEvent, TimeStrategy, @@ -17,16 +13,12 @@ import { import { parseCustomFields, - parseHttp, - parseOsc, parseProject, parseRundown, parseSettings, parseUrlPresets, parseViewSettings, sanitiseCustomFields, - sanitiseHttpSubscriptions, - sanitiseOscSubscriptions, } from '../parserFunctions.js'; describe('parseRundown()', () => { @@ -111,66 +103,6 @@ describe('parseViewSettings()', () => { }); }); -describe('parseOsc()', () => { - it('returns an a base model if nothing is given', () => { - const errorEmitter = vi.fn(); - const result = parseOsc({}, errorEmitter); - expect(result).toBeTypeOf('object'); - expect(errorEmitter).toHaveBeenCalledOnce(); - }); - - it('parses data, skipping invalid results', () => { - const errorEmitter = vi.fn(); - const osc = { - subscriptions: [ - { id: '1', cycle: 'onLoad', address: '/test', payload: 'test', enabled: true }, // OK - {}, // no data - { id: '2', cycle: 'onStart', payload: 'test', enabled: true }, // no address - ], - } as OSCSettings; - const result = parseOsc({ osc }, errorEmitter); - expect(result.subscriptions.length).toEqual(1); - expect(result.subscriptions.at(0)).toMatchObject({ - id: '1', - cycle: 'onLoad', - address: '/test', - payload: 'test', - enabled: true, - }); - expect(errorEmitter).toHaveBeenCalled(); - }); -}); - -describe('parseHttp()', () => { - it('returns an a base model if nothing is given', () => { - const errorEmitter = vi.fn(); - const result = parseHttp({}, errorEmitter); - expect(result).toBeTypeOf('object'); - expect(errorEmitter).toHaveBeenCalledOnce(); - }); - - it('parses data, skipping invalid results', () => { - const errorEmitter = vi.fn(); - const http = { - subscriptions: [ - { id: '1', cycle: 'onLoad', message: 'http://', enabled: true }, // OK - {}, // no data - { id: '2', cycle: 'onStart', enabled: true }, // no message - { id: '3', cycle: 'onLoad', message: '/test', enabled: true }, // doesnt start with http - ], - } as HttpSettings; - const result = parseHttp({ http }, errorEmitter); - expect(result.subscriptions.length).toEqual(1); - expect(result.subscriptions.at(0)).toMatchObject({ - id: '1', - cycle: 'onLoad', - message: 'http://', - enabled: true, - }); - expect(errorEmitter).toHaveBeenCalled(); - }); -}); - describe('parseUrlPresets()', () => { it('returns an a base model if nothing is given', () => { const errorEmitter = vi.fn(); @@ -223,81 +155,6 @@ describe('parseCustomFields()', () => { }); }); -describe('sanitiseOscSubscriptions()', () => { - it('throws if not an array an empty array if not an array', () => { - expect(() => sanitiseOscSubscriptions(undefined)).toThrow(); - // @ts-expect-error -- data is external, we check bad types - expect(() => sanitiseOscSubscriptions({})).toThrow(); - expect(() => sanitiseOscSubscriptions(null)).toThrow(); - }); - - it('returns an array of valid entries', () => { - const oscSubscriptions: OscSubscription[] = [ - { id: '1', cycle: 'onLoad', address: '/test', payload: 'test', enabled: true }, - { id: '2', cycle: 'onStart', address: '/test', payload: 'test', enabled: false }, - { id: '3', cycle: 'onPause', address: '/test', payload: 'test', enabled: true }, - { id: '4', cycle: 'onStop', address: '/test', payload: 'test', enabled: false }, - { id: '5', cycle: 'onUpdate', address: '/test', payload: 'test', enabled: true }, - { id: '6', cycle: 'onFinish', address: '/test', payload: 'test', enabled: false }, - { id: '7', cycle: 'onWarning', address: '/test', payload: 'test', enabled: false }, - { id: '8', cycle: 'onDanger', address: '/test', payload: 'test', enabled: false }, - ]; - const sanitationResult = sanitiseOscSubscriptions(oscSubscriptions); - expect(sanitationResult).toStrictEqual(oscSubscriptions); - }); - - it('filters invalid entries', () => { - const oscSubscriptions = [ - { id: '1', cycle: 'onLoad', address: 4, payload: 'test', enabled: true }, - { cycle: 'onLoad', payload: 'test', enabled: true }, - { id: '2', cycle: 'unknown', payload: 'test', enabled: false }, - { id: '3', payload: 'test', enabled: true }, - { id: '4', cycle: 'onStop', enabled: false }, - { id: '5', cycle: 'onUpdate', payload: 'test' }, - { id: '6', cycle: 'onFinish', payload: 'test', enabled: 'true' }, - ] as OscSubscription[]; - const sanitationResult = sanitiseOscSubscriptions(oscSubscriptions); - expect(sanitationResult.length).toBe(0); - }); -}); - -describe('sanitiseHttpSubscriptions()', () => { - it('throws if the data is unexpected', () => { - expect(() => sanitiseHttpSubscriptions(undefined)).toThrow(); - // @ts-expect-error -- data is external, we check bad types - expect(() => sanitiseHttpSubscriptions({})).toThrow(); - expect(() => sanitiseHttpSubscriptions(null)).toThrow(); - }); - - it('returns an array of valid entries', () => { - const httpSubscription: HttpSubscription[] = [ - { id: '1', cycle: 'onLoad', message: 'http://test', enabled: true }, - { id: '2', cycle: 'onStart', message: 'http://test', enabled: false }, - { id: '3', cycle: 'onPause', message: 'http://test', enabled: true }, - { id: '4', cycle: 'onStop', message: 'http://test', enabled: false }, - { id: '5', cycle: 'onUpdate', message: 'http://test', enabled: true }, - { id: '6', cycle: 'onFinish', message: 'http://test', enabled: false }, - { id: '7', cycle: 'onWarning', message: 'http://test', enabled: false }, - { id: '8', cycle: 'onDanger', message: 'http://test', enabled: false }, - ]; - const sanitationResult = sanitiseHttpSubscriptions(httpSubscription); - expect(sanitationResult).toStrictEqual(httpSubscription); - }); - - it('filters invalid entries', () => { - const httpSubscription = [ - { cycle: 'onLoad', message: 'http://test', enabled: true }, - { id: '2', cycle: 'unknown', message: 'http://test', enabled: false }, - { id: '3', message: 'http://test', enabled: true }, - { id: '4', cycle: 'onStop', enabled: false }, - { id: '5', cycle: 'onUpdate', message: 'http://test' }, - { id: '6', cycle: 'onFinish', message: 'ftp://test', enabled: 'true' }, - ]; - const sanitationResult = sanitiseHttpSubscriptions(httpSubscription as HttpSubscription[]); - expect(sanitationResult.length).toBe(0); - }); -}); - describe('sanitiseCustomFields()', () => { it('returns an empty array if not an array', () => { expect(sanitiseCustomFields({})).toEqual({}); diff --git a/apps/server/src/utils/parser.ts b/apps/server/src/utils/parser.ts index 53a4729fb3..55f97aeb49 100644 --- a/apps/server/src/utils/parser.ts +++ b/apps/server/src/utils/parser.ts @@ -27,15 +27,7 @@ import { logger } from '../classes/Logger.js'; import { event as eventDef } from '../models/eventsDefinition.js'; import { makeString } from './parserUtils.js'; -import { - parseHttp, - parseOsc, - parseProject, - parseRundown, - parseSettings, - parseUrlPresets, - parseViewSettings, -} from './parserFunctions.js'; +import { parseProject, parseRundown, parseSettings, parseUrlPresets, parseViewSettings } from './parserFunctions.js'; import { parseExcelDate } from './time.js'; export type ErrorEmitter = (message: string) => void; @@ -334,8 +326,6 @@ export function parseDatabaseModel(jsonData: Partial): { data: Da viewSettings: parseViewSettings(jsonData, makeEmitError('View Settings')), urlPresets: parseUrlPresets(jsonData, makeEmitError('URL Presets')), customFields, - osc: parseOsc(jsonData, makeEmitError('OSC')), - http: parseHttp(jsonData, makeEmitError('HTTP')), automation: parseAutomationSettings(jsonData), }; diff --git a/apps/server/src/utils/parserFunctions.ts b/apps/server/src/utils/parserFunctions.ts index 0dcbab54da..34253e9b4f 100644 --- a/apps/server/src/utils/parserFunctions.ts +++ b/apps/server/src/utils/parserFunctions.ts @@ -2,25 +2,20 @@ import { CustomField, CustomFields, DatabaseModel, - HttpSettings, - HttpSubscription, - OSCSettings, OntimeBlock, OntimeDelay, OntimeEvent, OntimeRundown, - OscSubscription, ProjectData, Settings, TimerType, URLPreset, ViewSettings, isOntimeBlock, - isOntimeCycle, isOntimeDelay, isOntimeEvent, } from 'ontime-types'; -import { customFieldLabelToKey, generateId, getErrorMessage, isAlphanumericWithSpace } from 'ontime-utils'; +import { customFieldLabelToKey, generateId, isAlphanumericWithSpace } from 'ontime-utils'; import { dbModel } from '../models/dataModel.js'; import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js'; @@ -165,102 +160,6 @@ export function parseViewSettings(data: Partial, emitError?: Erro }; } -/** - * Sanitises an OSC Subscriptions array - */ -export function sanitiseOscSubscriptions(subscriptions?: OscSubscription[]): OscSubscription[] { - if (!Array.isArray(subscriptions)) { - throw new Error('ERROR: invalid OSC subscriptions'); - } - - return subscriptions.filter( - ({ id, cycle, address, payload, enabled }) => - typeof id === 'string' && - isOntimeCycle(cycle) && - typeof address === 'string' && - typeof payload === 'string' && - typeof enabled === 'boolean', - ); -} - -/** - * Parse osc portion of an entry - */ -export function parseOsc(data: Partial, emitError?: ErrorEmitter): OSCSettings { - if (!data.osc) { - emitError?.('No data found to import'); - return { ...dbModel.osc }; - } - - console.log('Found OSC settings, importing...'); - - let newSubscriptions: OscSubscription[] = []; - try { - newSubscriptions = sanitiseOscSubscriptions(data.osc.subscriptions); - } catch (error) { - emitError?.(getErrorMessage(error)); - } - - if (newSubscriptions.length !== data.osc.subscriptions.length) { - emitError?.('Skipped invalid subscriptions'); - } - - return { - portIn: data.osc.portIn ?? dbModel.osc.portIn, - portOut: data.osc.portOut ?? dbModel.osc.portOut, - targetIP: data.osc.targetIP ?? dbModel.osc.targetIP, - enabledIn: data.osc.enabledIn ?? dbModel.osc.enabledIn, - enabledOut: data.osc.enabledOut ?? dbModel.osc.enabledOut, - subscriptions: newSubscriptions, - }; -} - -/** - * Sanitises an HTTP Subscriptions array - */ -export function sanitiseHttpSubscriptions(subscriptions?: HttpSubscription[]): HttpSubscription[] { - if (!Array.isArray(subscriptions)) { - throw new Error('ERROR: invalid HTTP subscriptions'); - } - - return subscriptions.filter( - ({ id, cycle, message, enabled }) => - typeof id === 'string' && - isOntimeCycle(cycle) && - typeof message === 'string' && - message.startsWith('http://') && - typeof enabled === 'boolean', - ); -} - -/** - * Parse Http portion of an entry - */ -export function parseHttp(data: Partial, emitError?: ErrorEmitter): HttpSettings { - if (!data.http) { - emitError?.('No data found to import'); - return { ...dbModel.http }; - } - - console.log('Found HTTP settings, importing...'); - - let newSubscriptions: HttpSubscription[] = []; - try { - newSubscriptions = sanitiseHttpSubscriptions(data.http.subscriptions); - } catch (error) { - emitError?.(getErrorMessage(error)); - } - - if (newSubscriptions.length !== data.http?.subscriptions.length) { - emitError?.('Skipped invalid subscriptions'); - } - - return { - enabledOut: data.http.enabledOut ?? dbModel.http.enabledOut, - subscriptions: newSubscriptions, - }; -} - /** * Parse URL preset portion of an entry */ diff --git a/packages/types/src/api/ontime-controller/BackendResponse.type.ts b/packages/types/src/api/ontime-controller/BackendResponse.type.ts index d81dd8827d..961a09b06e 100644 --- a/packages/types/src/api/ontime-controller/BackendResponse.type.ts +++ b/packages/types/src/api/ontime-controller/BackendResponse.type.ts @@ -1,4 +1,3 @@ -import type { OSCSettings } from '../../definitions/core/OscSettings.type.js'; import type { OntimeRundown } from '../../definitions/core/Rundown.type.js'; import type { Playback } from '../../definitions/runtime/Playback.type.js'; import type { MaybeString } from '../../utils/utils.type.js'; @@ -22,7 +21,6 @@ export interface GetInfo { networkInterfaces: NetworkInterface[]; version: string; serverPort: number; - osc: OSCSettings; publicDir: string; } diff --git a/packages/types/src/definitions/DataModel.type.ts b/packages/types/src/definitions/DataModel.type.ts index 63ecbc98e8..0f8d318789 100644 --- a/packages/types/src/definitions/DataModel.type.ts +++ b/packages/types/src/definitions/DataModel.type.ts @@ -1,9 +1,7 @@ import type { AutomationSettings, CustomFields, - HttpSettings, OntimeRundown, - OSCSettings, ProjectData, Settings, URLPreset, @@ -17,7 +15,5 @@ export type DatabaseModel = { viewSettings: ViewSettings; urlPresets: URLPreset[]; customFields: CustomFields; - osc: OSCSettings; - http: HttpSettings; automation: AutomationSettings; }; diff --git a/packages/types/src/definitions/core/HttpSettings.type.ts b/packages/types/src/definitions/core/HttpSettings.type.ts deleted file mode 100644 index 0c3dfc92db..0000000000 --- a/packages/types/src/definitions/core/HttpSettings.type.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { TimerLifeCycleKey } from './TimerLifecycle.type.js'; - -export type HttpSubscription = { id: string; cycle: TimerLifeCycleKey; message: string; enabled: boolean }; - -export interface HttpSettings { - enabledOut: boolean; - subscriptions: HttpSubscription[]; -} diff --git a/packages/types/src/definitions/core/OscSettings.type.ts b/packages/types/src/definitions/core/OscSettings.type.ts deleted file mode 100644 index c006d65f1e..0000000000 --- a/packages/types/src/definitions/core/OscSettings.type.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { TimerLifeCycleKey } from './TimerLifecycle.type.js'; - -export type OscSubscription = { - id: string; - cycle: TimerLifeCycleKey; - address: string; - payload: string; // TODO: we should be using arguments to keep in line with protocol language - enabled: boolean; -}; - -export interface OSCSettings { - portIn: number; - portOut: number; - targetIP: string; - enabledIn: boolean; - enabledOut: boolean; - subscriptions: OscSubscription[]; -} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 0c1237114c..b84273b9e5 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -52,10 +52,6 @@ export type { EventCustomFields, } from './definitions/core/CustomFields.type.js'; -// ---> Integration, Subscription -export type { OSCSettings, OscSubscription } from './definitions/core/OscSettings.type.js'; -export type { HttpSettings, HttpSubscription } from './definitions/core/HttpSettings.type.js'; - // SERVER RESPONSES export type { AuthenticationStatus,