fix; osc subscriptions (#392)

* style: fix typo

* refactor: convert to typescript

* refactor: convert to typescript

* refactor: create patch for osc subscriptions

* fix: issue with subscription invalidation
This commit is contained in:
Carlos Valente
2023-05-19 22:20:00 +02:00
committed by GitHub
parent 8bf223dee6
commit 6617eb1f0f
15 changed files with 227 additions and 106 deletions
+17 -1
View File
@@ -1,5 +1,13 @@
import axios from 'axios'; import axios from 'axios';
import { Alias, EventData, OSCSettings, Settings, UserFields, ViewSettings } from 'ontime-types'; import {
Alias,
EventData,
OSCSettings,
OscSubscription,
Settings,
UserFields,
ViewSettings,
} from 'ontime-types';
import { apiRepoLatest } from '../../externals'; import { apiRepoLatest } from '../../externals';
import { InfoType } from '../models/Info'; import { InfoType } from '../models/Info';
@@ -100,6 +108,14 @@ export async function postOSC(data: OSCSettings) {
return axios.post(`${ontimeURL}/osc`, data); return axios.post(`${ontimeURL}/osc`, data);
} }
/**
* @description HTTP request to mutate osc subscriptions
* @return {Promise}
*/
export async function postOscSubscriptions(data: OscSubscription) {
return axios.post(`${ontimeURL}/osc-subscriptions`, data);
}
/** /**
* @description HTTP request to download db * @description HTTP request to download db
* @return {Promise} * @return {Promise}
@@ -3,7 +3,7 @@ import { OSCSettings } from 'ontime-types';
import { queryRefetchIntervalSlow } from '../../ontimeConfig'; import { queryRefetchIntervalSlow } from '../../ontimeConfig';
import { OSC_SETTINGS } from '../api/apiConstants'; import { OSC_SETTINGS } from '../api/apiConstants';
import { getOSC, postOSC } from '../api/ontimeApi'; import { getOSC, postOSC, postOscSubscriptions } from '../api/ontimeApi';
import { oscPlaceholderSettings } from '../models/OscSettings'; import { oscPlaceholderSettings } from '../models/OscSettings';
import { ontimeQueryClient } from '../queryClient'; import { ontimeQueryClient } from '../queryClient';
@@ -18,6 +18,7 @@ export default function useOscSettings() {
networkMode: 'always', networkMode: 'always',
}); });
// we need to jump through some hoops because of the type op port
return { data: data! as unknown as OSCSettings, status, isError, refetch }; return { data: data! as unknown as OSCSettings, status, isError, refetch };
} }
@@ -29,3 +30,11 @@ export function useOscSettingsMutation() {
}); });
return { isLoading, mutateAsync }; return { isLoading, mutateAsync };
} }
export function usePostOscSubscriptions() {
const { isLoading, mutateAsync } = useMutation({
mutationFn: postOscSubscriptions,
onSettled: () => ontimeQueryClient.invalidateQueries({ queryKey: OSC_SETTINGS }),
});
return { isLoading, mutateAsync };
}
@@ -1,11 +1,9 @@
import { useState } from 'react'; import { useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import type { OSCSettings, OscSubscription } from 'ontime-types'; import type { OscSubscription } from 'ontime-types';
import { TimerLifeCycle } from 'ontime-types'; import { TimerLifeCycle } from 'ontime-types';
import { generateId } from 'ontime-utils';
import useOscSettings, { useOscSettingsMutation } from '../../../common/hooks-query/useOscSettings'; import useOscSettings, { usePostOscSubscriptions } from '../../../common/hooks-query/useOscSettings';
import { oscPlaceholderSettings, PlaceholderSettings } from '../../../common/models/OscSettings';
import { useEmitLog } from '../../../common/stores/logger'; import { useEmitLog } from '../../../common/stores/logger';
import OntimeModalFooter from '../OntimeModalFooter'; import OntimeModalFooter from '../OntimeModalFooter';
@@ -44,87 +42,102 @@ const sectionText: { [key in TimerLifeCycle]: { title: string; subtitle: string
export default function OscIntegration() { export default function OscIntegration() {
const { data } = useOscSettings(); const { data } = useOscSettings();
const { mutateAsync } = useOscSettingsMutation(); const { mutateAsync } = usePostOscSubscriptions();
const { emitError } = useEmitLog(); const { emitError } = useEmitLog();
const { const {
control,
handleSubmit, handleSubmit,
register, register,
reset, reset,
formState: { isSubmitting, isDirty, isValid }, formState: { isSubmitting, isDirty, isValid },
} = useForm<PlaceholderSettings>({ } = useForm<OscSubscription>({
defaultValues: data, defaultValues: data.subscriptions,
values: data, values: data.subscriptions,
}); });
const [subscriptionState, setSubscription] = useState<OscSubscription>(
data?.subscriptions || oscPlaceholderSettings.subscriptions,
);
const [hasManualChange, setHasManualChange] = useState(false);
const [showSection, setShowSection] = useState<OntimeCycle>(TimerLifeCycle.onLoad); const [showSection, setShowSection] = useState<OntimeCycle>(TimerLifeCycle.onLoad);
const resetForm = () => { const resetForm = () => {
const originalData = data || oscPlaceholderSettings; reset(data.subscriptions);
setSubscription(originalData.subscriptions);
// @ts-expect-error -- we know the data here is safe
reset(originalData);
}; };
const deleteSubscriptionEntry = (cycle: OntimeCycle, id: string) => { const onSubmit = async (values: OscSubscription) => {
setSubscription((prev) => {
const newData = { ...prev };
newData[cycle] = [...prev[cycle].filter((el) => el.id !== id)];
return newData;
});
setHasManualChange(true);
};
const addNewSubscriptionEntry = async (cycle: OntimeCycle) => {
setSubscription((prev) => {
const newData = structuredClone(prev);
newData[cycle] = [...prev[cycle], { id: generateId(), message: '', enabled: false }];
return newData;
});
setHasManualChange(true);
};
const onSubmit = async (values: OSCSettings | PlaceholderSettings) => {
try { try {
// @ts-expect-error -- we know of the type mismatch, not pertinent here const subscriptions = {
await mutateAsync(values); onLoad: values.onLoad ?? [],
setHasManualChange(false); onStart: values.onStart ?? [],
onPause: values.onPause ?? [],
onStop: values.onStop ?? [],
onUpdate: values.onUpdate ?? [],
onFinish: values.onFinish ?? [],
};
await mutateAsync(subscriptions);
} catch (error) { } catch (error) {
emitError(`Error setting OSC: ${error}`); emitError(`Error setting OSC: ${error}`);
} }
}; };
const subscriptionKeys = Object.keys(subscriptionState);
return ( return (
<form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer} id='oscSubscriptions'> <form onSubmit={handleSubmit(onSubmit)} className={styles.sectionContainer} id='osc-subscriptions'>
{subscriptionKeys.map((cycle, idx) => { <OscSubscriptionRow
return ( cycle={TimerLifeCycle.onLoad}
<div key={`${cycle}-${idx}`}> title={sectionText.onLoad.title}
<OscSubscriptionRow subtitle={sectionText.onLoad.subtitle}
key={cycle} visible={showSection === TimerLifeCycle.onLoad}
cycle={cycle as TimerLifeCycle} setShowSection={setShowSection}
title={sectionText[cycle as TimerLifeCycle].title} register={register}
subtitle={sectionText[cycle as TimerLifeCycle].subtitle} control={control}
visible={showSection === cycle} />
setShowSection={setShowSection} <OscSubscriptionRow
subscriptionOptions={subscriptionState[cycle as TimerLifeCycle]} cycle={TimerLifeCycle.onStart}
handleDelete={deleteSubscriptionEntry} title={sectionText.onStart.title}
handleAddNew={addNewSubscriptionEntry} subtitle={sectionText.onStart.subtitle}
register={register} visible={showSection === TimerLifeCycle.onStart}
/> setShowSection={setShowSection}
{idx < subscriptionKeys.length - 1 && <hr className={styles.divider} />} register={register}
</div> control={control}
); />
})} <OscSubscriptionRow
cycle={TimerLifeCycle.onPause}
title={sectionText.onPause.title}
subtitle={sectionText.onPause.subtitle}
visible={showSection === TimerLifeCycle.onPause}
setShowSection={setShowSection}
register={register}
control={control}
/>
<OscSubscriptionRow
cycle={TimerLifeCycle.onStop}
title={sectionText.onStop.title}
subtitle={sectionText.onStop.subtitle}
visible={showSection === TimerLifeCycle.onStop}
setShowSection={setShowSection}
register={register}
control={control}
/>
<OscSubscriptionRow
cycle={TimerLifeCycle.onUpdate}
title={sectionText.onUpdate.title}
subtitle={sectionText.onUpdate.subtitle}
visible={showSection === TimerLifeCycle.onUpdate}
setShowSection={setShowSection}
register={register}
control={control}
/>
<OscSubscriptionRow
cycle={TimerLifeCycle.onFinish}
title={sectionText.onFinish.title}
subtitle={sectionText.onFinish.subtitle}
visible={showSection === TimerLifeCycle.onFinish}
setShowSection={setShowSection}
register={register}
control={control}
/>
<OntimeModalFooter <OntimeModalFooter
formId='oscSubscriptions' formId='osc-subscriptions'
handleRevert={resetForm} handleRevert={resetForm}
isDirty={isDirty || hasManualChange} isDirty={isDirty}
isValid={isValid} isValid={isValid}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
/> />
@@ -1,8 +1,10 @@
import { UseFormRegister } from 'react-hook-form'; import { Control, useFieldArray, UseFormRegister } from 'react-hook-form';
import { Button, IconButton, Input, Switch } from '@chakra-ui/react'; import { Button, IconButton, Input, Switch } from '@chakra-ui/react';
import { FiChevronUp } from '@react-icons/all-files/fi/FiChevronUp'; import { FiChevronUp } from '@react-icons/all-files/fi/FiChevronUp';
import { IoRemove } from '@react-icons/all-files/io5/IoRemove'; import { IoRemove } from '@react-icons/all-files/io5/IoRemove';
import { OscSubscriptionOptions, TimerLifeCycle } from 'ontime-types'; import { OscSubscription, TimerLifeCycle } from 'ontime-types';
import { useEmitLog } from '../../../common/stores/logger';
import collapseStyles from '../../../common/components/collapse-bar/CollapseBar.module.scss'; import collapseStyles from '../../../common/components/collapse-bar/CollapseBar.module.scss';
import styles from '../Modal.module.scss'; import styles from '../Modal.module.scss';
@@ -13,37 +15,50 @@ interface OscSubscriptionRowProps {
subtitle: string; subtitle: string;
visible: boolean; visible: boolean;
setShowSection: (cycle: TimerLifeCycle) => void; setShowSection: (cycle: TimerLifeCycle) => void;
subscriptionOptions: OscSubscriptionOptions[]; register: UseFormRegister<OscSubscription>;
handleDelete: (cycle: TimerLifeCycle, id: string) => void; control: Control<OscSubscription>;
handleAddNew: (cycle: TimerLifeCycle) => void;
register: UseFormRegister<any>;
} }
export default function OscSubscriptionRow(props: OscSubscriptionRowProps) { export default function OscSubscriptionRow(props: OscSubscriptionRowProps) {
const { cycle, title, subtitle, visible, setShowSection, subscriptionOptions, handleDelete, handleAddNew, register } = const { cycle, title, subtitle, visible, setShowSection, register, control } = props;
props; const { emitError } = useEmitLog();
const { fields, append, remove } = useFieldArray({
name: cycle,
control,
});
const hasTooManyOptions = subscriptionOptions.length >= 3; const hasTooManyOptions = fields.length >= 3;
const headerStyle = `${styles.splitSection} ${visible ? '' : styles.showPointer}`; const headerStyle = `${styles.splitSection} ${visible ? '' : styles.showPointer}`;
const registerPrefix = `subscriptions.${cycle}`;
const sectionTitle = `${title} ${fields.length ? fields.length : '-'} / 3`;
const handleAddNew = () => {
if (hasTooManyOptions) {
emitError('Maximum amount of onLoad subscriptions reached (3)');
return;
}
append({
message: '',
enabled: false,
});
};
return ( return (
<> <>
<div className={headerStyle} onClick={() => setShowSection(cycle)}> <div className={headerStyle} onClick={() => setShowSection(cycle)}>
<div> <div>
<span className={`${styles.sectionTitle} ${styles.main}`}>{title}</span> <span className={`${styles.sectionTitle} ${styles.main}`}>{sectionTitle}</span>
{visible && <span className={styles.sectionSubtitle}>{subtitle}</span>} {visible && <span className={styles.sectionSubtitle}>{subtitle}</span>}
</div> </div>
<FiChevronUp className={visible ? collapseStyles.moreCollapsed : collapseStyles.moreExpanded} /> <FiChevronUp className={visible ? collapseStyles.moreCollapsed : collapseStyles.moreExpanded} />
</div> </div>
{visible && ( {visible && (
<> <>
{subscriptionOptions.map((option, idx) => ( {fields.map((subscription, index) => (
<div key={option.id} className={styles.entryRow}> <div key={subscription.id} className={styles.entryRow}>
<input type='hidden' {...register(`${registerPrefix}[${idx}].id`)} value={option.id} />
<IconButton <IconButton
icon={<IoRemove />} icon={<IoRemove />}
onClick={() => handleDelete(cycle, option.id)} onClick={() => remove(index)}
aria-label='delete' aria-label='delete'
size='xs' size='xs'
colorScheme='red' colorScheme='red'
@@ -52,13 +67,14 @@ export default function OscSubscriptionRow(props: OscSubscriptionRowProps) {
placeholder='OSC Message' placeholder='OSC Message'
size='xs' size='xs'
variant='ontime-filled-on-light' variant='ontime-filled-on-light'
{...register(`${registerPrefix}[${idx}].message`)} autoComplete='off'
{...register(`${cycle}.${index}.message`)}
/> />
<Switch variant='ontime-on-light' {...register(`${registerPrefix}[${idx}].enabled`)} /> <Switch variant='ontime-on-light' {...register(`${cycle}.${index}.enabled`)} />
</div> </div>
))} ))}
<Button <Button
onClick={() => handleAddNew(cycle)} onClick={handleAddNew}
className={styles.shiftRight} className={styles.shiftRight}
isDisabled={hasTooManyOptions} isDisabled={hasTooManyOptions}
size='xs' size='xs'
@@ -54,7 +54,7 @@ export default function AliasesForm() {
const addNew = () => { const addNew = () => {
if (fields.length > 20) { if (fields.length > 20) {
emitError('Maximum amount of aliases reacted (20)'); emitError('Maximum amount of aliases reached (20)');
return; return;
} }
append({ append({
@@ -269,6 +269,27 @@ export const getOSC = async (req, res) => {
res.status(200).send(osc); res.status(200).send(osc);
}; };
export const postOscSubscriptions = async (req, res) => {
if (failEmptyObjects(req.body, res)) {
return;
}
try {
const oscSubscriptions = req.body;
const oscSettings = DataProvider.getOsc();
oscSettings.subscriptions = oscSubscriptions;
await DataProvider.setOsc(oscSettings);
// TODO: this update could be more granular, checking that relevant data was changed
const { message } = oscIntegration.init(oscSettings);
logger.info('RX', message);
res.send(oscSettings).status(200);
} catch (error) {
res.status(400).send(error);
}
};
// Create controller for POST request to '/ontime/osc' // Create controller for POST request to '/ontime/osc'
// Returns ACK message // Returns ACK message
export const postOSC = async (req, res) => { export const postOSC = async (req, res) => {
@@ -1,5 +1,5 @@
import { body, check, validationResult } from 'express-validator'; import { body, check, validationResult } from 'express-validator';
import { validateOscSubscription } from '../utils/parserFunctions.js'; import { validateOscObject, validateOscSubscriptionEntry } from '../utils/parserFunctions.js';
/** /**
* @description Validates object for POST /ontime/views * @description Validates object for POST /ontime/views
@@ -81,7 +81,36 @@ export const validateOSC = [
body('enabledOut').exists().isBoolean(), body('enabledOut').exists().isBoolean(),
body('subscriptions') body('subscriptions')
.isObject() .isObject()
.custom((value) => validateOscSubscription(value)), .custom((value) => validateOscObject(value)),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
];
/**
* @description Validates object for POST /ontime/osc-subscriptions
*/
export const validateOscSubscription = [
body('onLoad')
.isArray()
.custom((value) => validateOscSubscriptionEntry(value)),
body('onStart')
.isArray()
.custom((value) => validateOscSubscriptionEntry(value)),
body('onPause')
.isArray()
.custom((value) => validateOscSubscriptionEntry(value)),
body('onStop')
.isArray()
.custom((value) => validateOscSubscriptionEntry(value)),
body('onUpdate')
.isArray()
.custom((value) => validateOscSubscriptionEntry(value)),
body('onFinish')
.isArray()
.custom((value) => validateOscSubscriptionEntry(value)),
(req, res, next) => { (req, res, next) => {
const errors = validationResult(req); const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() }); if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
@@ -1,7 +1,6 @@
import express from 'express'; import express from 'express';
// import event controller import { getEventData, postEventData } from '../controllers/eventDataController.js';
import { getEventData, postEventData } from '../controllers/eventDataController.ts'; import { eventDataSanitizer } from '../controllers/eventDataController.validate.js';
import { eventDataSanitizer } from '../controllers/eventDataController.validate.ts';
export const router = express.Router(); export const router = express.Router();
@@ -13,6 +13,7 @@ import {
postAliases, postAliases,
postNew, postNew,
postOSC, postOSC,
postOscSubscriptions,
postSettings, postSettings,
postUserFields, postUserFields,
postViewSettings, postViewSettings,
@@ -21,6 +22,7 @@ import {
import { import {
validateAliases, validateAliases,
validateOSC, validateOSC,
validateOscSubscription,
validateSettings, validateSettings,
validateUserFields, validateUserFields,
viewValidator, viewValidator,
@@ -71,5 +73,8 @@ router.get('/osc', getOSC);
// create route between controller and '/ontime/osc' endpoint // create route between controller and '/ontime/osc' endpoint
router.post('/osc', validateOSC, postOSC); router.post('/osc', validateOSC, postOSC);
// create route between controller and '/ontime/osc-subscriptions' endpoint
router.post('/osc-subscriptions', validateOscSubscription, postOscSubscriptions);
// create route between controller and '/ontime/new' endpoint // create route between controller and '/ontime/new' endpoint
router.post('/new', eventDataSanitizer, postNew); router.post('/new', eventDataSanitizer, postNew);
@@ -1,5 +1,4 @@
import express from 'express'; import express from 'express';
// import playback controllers
import { import {
pbGet, pbGet,
pbLoad, pbLoad,
@@ -5,7 +5,7 @@ import IIntegration, { TimerLifeCycleKey } from './IIntegration.js';
import { parseTemplateNested } from './integrationUtils.js'; import { parseTemplateNested } from './integrationUtils.js';
import { isObject } from '../../utils/varUtils.js'; import { isObject } from '../../utils/varUtils.js';
import { dbModel } from '../../models/dataModel.js'; import { dbModel } from '../../models/dataModel.js';
import { validateOscSubscription } from '../../utils/parserFunctions.js'; import { validateOscObject } from '../../utils/parserFunctions.js';
type Action = TimerLifeCycleKey | string; type Action = TimerLifeCycleKey | string;
@@ -58,7 +58,7 @@ export class OscIntegration implements IIntegration {
} }
initSubscriptions(subscriptionOptions: OscSubscription) { initSubscriptions(subscriptionOptions: OscSubscription) {
if (validateOscSubscription(subscriptionOptions)) { if (validateOscObject(subscriptionOptions)) {
this.subscriptions = { ...subscriptionOptions }; this.subscriptions = { ...subscriptionOptions };
} }
} }
@@ -1,4 +1,4 @@
import { validateOscSubscription } from '../parserFunctions.ts'; import { validateOscObject } from '../parserFunctions.ts';
test('validateOscSubscription()', () => { test('validateOscSubscription()', () => {
it('should return true when given a valid OscSubscription', () => { it('should return true when given a valid OscSubscription', () => {
@@ -11,35 +11,35 @@ test('validateOscSubscription()', () => {
onFinish: [{ id: '6', message: 'test', enabled: false }], onFinish: [{ id: '6', message: 'test', enabled: false }],
}; };
const result = validateOscSubscription(validSubscription); const result = validateOscObject(validSubscription);
expect(result).toBe(true); expect(result).toBe(true);
}); });
it('should return false when given undefined', () => { it('should return false when given undefined', () => {
const result = validateOscSubscription(undefined); const result = validateOscObject(undefined);
expect(result).toBe(false); expect(result).toBe(false);
}); });
it('should return false when given null', () => { it('should return false when given null', () => {
const result = validateOscSubscription(null); const result = validateOscObject(null);
expect(result).toBe(false); expect(result).toBe(false);
}); });
it('should return false when given an empty object', () => { it('should return false when given an empty object', () => {
const result = validateOscSubscription({}); const result = validateOscObject({});
expect(result).toBe(false); expect(result).toBe(false);
}); });
it('should return false when given an empty array', () => { it('should return false when given an empty array', () => {
const result = validateOscSubscription([]); const result = validateOscObject([]);
expect(result).toBe(false); expect(result).toBe(false);
}); });
it('should return false when given an object that is not an OscSubscription', () => { it('should return false when given an object that is not an OscSubscription', () => {
const invalidObject = { foo: 'bar' }; const invalidObject = { foo: 'bar' };
const result = validateOscSubscription(invalidObject); const result = validateOscObject(invalidObject);
expect(result).toBe(false); expect(result).toBe(false);
}); });
@@ -54,7 +54,7 @@ test('validateOscSubscription()', () => {
onFinish: [{ id: '6', message: 'test', enabled: false }], onFinish: [{ id: '6', message: 'test', enabled: false }],
}; };
const result = validateOscSubscription(invalidSubscription); const result = validateOscObject(invalidSubscription);
expect(result).toBe(false); expect(result).toBe(false);
}); });
@@ -69,7 +69,7 @@ test('validateOscSubscription()', () => {
onFinish: [{ id: '6', message: 'test', enabled: 'not a boolean' }], onFinish: [{ id: '6', message: 'test', enabled: 'not a boolean' }],
}; };
const result = validateOscSubscription(invalidSubscription); const result = validateOscObject(invalidSubscription);
expect(result).toBe(false); expect(result).toBe(false);
}); });
@@ -84,7 +84,7 @@ test('validateOscSubscription()', () => {
onFinish: [{ id: '6', message: 'test', enabled: 'not a boolean' }], onFinish: [{ id: '6', message: 'test', enabled: 'not a boolean' }],
}; };
const result = validateOscSubscription(invalidSubscription); const result = validateOscObject(invalidSubscription);
expect(result).toBe(true); expect(result).toBe(true);
}); });
+17 -3
View File
@@ -5,6 +5,7 @@ import {
OntimeRundown, OntimeRundown,
OSCSettings, OSCSettings,
OscSubscription, OscSubscription,
OscSubscriptionOptions,
Settings, Settings,
TimerLifeCycle, TimerLifeCycle,
UserFields, UserFields,
@@ -164,11 +165,24 @@ export const parseViewSettings = (data, enforce): ViewSettings => {
return newViews as ViewSettings; return newViews as ViewSettings;
}; };
/**
* Parses and validates subscription entry
* @param data
*/
export const validateOscSubscriptionEntry = (data: OscSubscriptionOptions): boolean => {
for (const subscription in data) {
if (typeof data[subscription].message !== 'string' || typeof data[subscription].enabled !== 'boolean') {
return false;
}
}
return true;
};
/** /**
* Parses and validates subscription object * Parses and validates subscription object
* @param data * @param data
*/ */
export const validateOscSubscription = (data: OscSubscription): boolean => { export const validateOscObject = (data: OscSubscription): boolean => {
if (!data) { if (!data) {
return false; return false;
} }
@@ -178,7 +192,7 @@ export const validateOscSubscription = (data: OscSubscription): boolean => {
return false; return false;
} }
for (const subscription of data[key]) { for (const subscription of data[key]) {
if (!subscription.id || typeof subscription.message !== 'string' || typeof subscription.enabled !== 'boolean') { if (typeof subscription.message !== 'string' || typeof subscription.enabled !== 'boolean') {
return false; return false;
} }
} }
@@ -194,7 +208,7 @@ export const parseOsc = (data: { osc?: Partial<OSCSettings> }, enforce: boolean)
console.log('Found OSC definition, importing...'); console.log('Found OSC definition, importing...');
const loadedConfig = data?.osc || {}; const loadedConfig = data?.osc || {};
const validatedSubscriptions = validateOscSubscription(loadedConfig.subscriptions) const validatedSubscriptions = validateOscObject(loadedConfig.subscriptions)
? loadedConfig.subscriptions ? loadedConfig.subscriptions
: dbModel.osc.subscriptions; : dbModel.osc.subscriptions;
@@ -1,6 +1,6 @@
import { TimerLifeCycleKey } from './TimerLifecycle.type.js'; import { TimerLifeCycleKey } from './TimerLifecycle.type.js';
export type OscSubscriptionOptions = { id: string; message: string; enabled: boolean }; export type OscSubscriptionOptions = { message: string; enabled: boolean };
export type OscSubscription = { [key in TimerLifeCycleKey]: OscSubscriptionOptions[] }; export type OscSubscription = { [key in TimerLifeCycleKey]: OscSubscriptionOptions[] };
export interface OSCSettings { export interface OSCSettings {