mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-07 00:13:53 +00:00
225 lines
6.0 KiB
TypeScript
225 lines
6.0 KiB
TypeScript
import { Log, RundownCached, RuntimeStore } from 'ontime-types';
|
|
|
|
import { CLIENT_LIST, CUSTOM_FIELDS, isProduction, RUNDOWN, RUNTIME, websocketUrl } from '../api/constants';
|
|
import { ontimeQueryClient } from '../queryClient';
|
|
import {
|
|
getClientId,
|
|
getClientName,
|
|
setClientId,
|
|
setClientName,
|
|
setClientRedirect,
|
|
setClients,
|
|
} from '../stores/clientStore';
|
|
import { addLog } from '../stores/logger';
|
|
import { patchRuntime, runtimeStore } from '../stores/runtime';
|
|
|
|
export let websocket: WebSocket | null = null;
|
|
let reconnectTimeout: NodeJS.Timeout | null = null;
|
|
const reconnectInterval = 1000;
|
|
|
|
export let shouldReconnect = true;
|
|
export let hasConnected = false;
|
|
export let reconnectAttempts = 0;
|
|
|
|
export const connectSocket = () => {
|
|
websocket = new WebSocket(websocketUrl);
|
|
|
|
const preferredClientName = getClientName();
|
|
|
|
websocket.onopen = () => {
|
|
clearTimeout(reconnectTimeout as NodeJS.Timeout);
|
|
hasConnected = true;
|
|
reconnectAttempts = 0;
|
|
|
|
if (preferredClientName) {
|
|
socketSendJson('set-client-name', preferredClientName);
|
|
}
|
|
|
|
socketSendJson('set-client-type', 'ontime');
|
|
|
|
socketSendJson('set-client-path', location.pathname + location.search);
|
|
};
|
|
|
|
websocket.onclose = () => {
|
|
console.warn('WebSocket disconnected');
|
|
if (shouldReconnect) {
|
|
reconnectTimeout = setTimeout(() => {
|
|
console.warn('WebSocket: attempting reconnect');
|
|
if (websocket && websocket.readyState === WebSocket.CLOSED) {
|
|
reconnectAttempts += 1;
|
|
connectSocket();
|
|
}
|
|
}, reconnectInterval);
|
|
}
|
|
};
|
|
|
|
websocket.onerror = (error) => {
|
|
console.error('WebSocket error:', error);
|
|
};
|
|
|
|
websocket.onmessage = (event) => {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
|
|
const { type, payload } = data;
|
|
|
|
if (!type) {
|
|
return;
|
|
}
|
|
|
|
switch (type) {
|
|
case 'client-id': {
|
|
if (typeof payload === 'string') {
|
|
setClientId(payload);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'client-name': {
|
|
if (typeof payload === 'string') {
|
|
setClientName(payload);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'client-rename': {
|
|
if (typeof payload === 'object') {
|
|
const id = getClientId();
|
|
if (payload.target && payload.target === id) {
|
|
setClientName(payload.name);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'client-redirect': {
|
|
if (typeof payload === 'object') {
|
|
const id = getClientId();
|
|
if (payload.target && payload.target === id) {
|
|
setClientRedirect(payload.path);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'client-list': {
|
|
setClients(payload);
|
|
if (!isProduction) {
|
|
ontimeQueryClient.setQueryData(CLIENT_LIST, payload);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'ontime-log': {
|
|
addLog(payload as Log);
|
|
break;
|
|
}
|
|
case 'ontime': {
|
|
runtimeStore.setState(payload as RuntimeStore);
|
|
if (!isProduction) {
|
|
ontimeQueryClient.setQueryData(RUNTIME, data.payload);
|
|
}
|
|
break;
|
|
}
|
|
case 'ontime-clock': {
|
|
patchRuntime('clock', payload);
|
|
updateDevTools({ clock: payload });
|
|
break;
|
|
}
|
|
case 'ontime-timer': {
|
|
patchRuntime('timer', payload);
|
|
updateDevTools({ timer: payload });
|
|
break;
|
|
}
|
|
case 'ontime-onAir': {
|
|
patchRuntime('onAir', payload);
|
|
updateDevTools({ onAir: payload });
|
|
break;
|
|
}
|
|
case 'ontime-message': {
|
|
patchRuntime('message', payload);
|
|
updateDevTools({ message: payload });
|
|
break;
|
|
}
|
|
case 'ontime-runtime': {
|
|
patchRuntime('runtime', payload);
|
|
updateDevTools({ runtime: payload });
|
|
break;
|
|
}
|
|
case 'ontime-eventNow': {
|
|
patchRuntime('eventNow', payload);
|
|
updateDevTools({ eventNow: payload });
|
|
break;
|
|
}
|
|
case 'ontime-currentBlock': {
|
|
patchRuntime('currentBlock', payload);
|
|
updateDevTools({ currentBlock: payload });
|
|
break;
|
|
}
|
|
case 'ontime-publicEventNow': {
|
|
patchRuntime('publicEventNow', payload);
|
|
updateDevTools({ publicEventNow: payload });
|
|
break;
|
|
}
|
|
case 'ontime-eventNext': {
|
|
patchRuntime('eventNext', payload);
|
|
updateDevTools({ eventNext: payload });
|
|
break;
|
|
}
|
|
case 'ontime-publicEventNext': {
|
|
patchRuntime('publicEventNext', payload);
|
|
updateDevTools({ publicEventNext: payload });
|
|
break;
|
|
}
|
|
case 'ontime-auxtimer1': {
|
|
patchRuntime('auxtimer1', payload);
|
|
updateDevTools({ auxtimer1: payload });
|
|
break;
|
|
}
|
|
case 'ontime-refetch': {
|
|
// the refetch message signals that the rundown has changed in the server side
|
|
const { revision } = payload;
|
|
const currentRevision = ontimeQueryClient.getQueryData<RundownCached>(RUNDOWN)?.revision ?? -1;
|
|
|
|
if (revision > currentRevision) {
|
|
ontimeQueryClient.invalidateQueries({ queryKey: RUNDOWN });
|
|
ontimeQueryClient.invalidateQueries({ queryKey: CUSTOM_FIELDS });
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} catch (_) {
|
|
// ignore unhandled
|
|
}
|
|
};
|
|
};
|
|
|
|
export const disconnectSocket = () => {
|
|
shouldReconnect = false;
|
|
websocket?.close();
|
|
};
|
|
|
|
export const socketSend = (message: any) => {
|
|
if (websocket && websocket.readyState === WebSocket.OPEN) {
|
|
websocket.send(message);
|
|
}
|
|
};
|
|
|
|
export const socketSendJson = (type: string, payload?: unknown) => {
|
|
socketSend(
|
|
JSON.stringify({
|
|
type,
|
|
payload,
|
|
}),
|
|
);
|
|
};
|
|
|
|
function updateDevTools(newData: Partial<RuntimeStore>) {
|
|
if (!isProduction) {
|
|
ontimeQueryClient.setQueryData(RUNTIME, (oldData: RuntimeStore) => ({
|
|
...oldData,
|
|
...newData,
|
|
}));
|
|
}
|
|
}
|