mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-25 17:09:09 +00:00
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:
@@ -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) => {
|
||||||
|
|||||||
+31
-2
@@ -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() });
|
||||||
+2
-3
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user