From 9f7e1cf7baba8b46e3d5c0cc88182968082cb14a Mon Sep 17 00:00:00 2001 From: Alexandre Tuleu Date: Tue, 26 Sep 2023 10:50:54 +0200 Subject: [PATCH] Adds an api to get asynchronous update events. --- src/lib/server/dataPolling.ts | 73 +++++++++++++++++++++++++++++++ src/routes/api/updates/+server.ts | 27 ++++++++++++ vite.config.ts | 3 +- 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 src/lib/server/dataPolling.ts create mode 100644 src/routes/api/updates/+server.ts diff --git a/src/lib/server/dataPolling.ts b/src/lib/server/dataPolling.ts new file mode 100644 index 0000000..4d1f68c --- /dev/null +++ b/src/lib/server/dataPolling.ts @@ -0,0 +1,73 @@ +import type { ServiceData, ServicePoller } from '$lib/services/service'; +import { getServiceRecord } from '$lib/services/services'; +import { readable, type Readable } from 'svelte/store'; +import { serverConfig } from './config'; + +type MayAsyncServiceData = ServiceData | Promise; + +let serviceData: MayAsyncServiceData[][] | undefined = undefined; + +function forEachService( + fn: (group: number, item: number, pollResult: Promise) => void +): MayAsyncServiceData[][] { + const config = serverConfig(); + + if (serviceData == undefined) { + serviceData = []; + } + + for (const [idxGroup, group] of config.services.entries()) { + if (serviceData[group] == undefined) { + serviceData[group] = []; + } + + for (const [idxItem, service] of group.items.entries()) { + const poller: ServicePoller = getServiceRecord(service.type || '').poll; + fn(idxGroup, idxItem, poller(service)); + } + } + return serviceData; +} + +export function getCurrentServiceData(): MayAsyncServiceData[][] { + if (serviceData != undefined) { + return serviceData; + } + + return forEachService((group: number, item: number, pollResult: Promise) => { + (serviceData as MayAsyncServiceData[][])[group][item] = pollResult.then( + (data: ServiceData) => { + (serviceData as MayAsyncServiceData[][])[group][item] = data; + return data; + } + ); + }); +} + +export interface ServiceDataEvent { + group: number; + item: number; + data: ServiceData; +} + +const pollInterval = 30000; + +export const serviceDataEvents = readable(undefined, (set) => { + console.debug('starting ServiceData polling loop every ' + pollInterval / 1000 + 's'); + const timer = setInterval(() => { + forEachService((group: number, item: number, result: Promise) => { + (serviceData as MayAsyncServiceData[][])[group][item] = result.then( + (data: ServiceData) => { + (serviceData as MayAsyncServiceData[][])[group][item] = data; + set({ group, item, data }); + return data; + } + ); + }); + }, pollInterval); + + return () => { + console.debug('stopping ServiceData polling loop'); + clearInterval(timer); + }; +}); diff --git a/src/routes/api/updates/+server.ts b/src/routes/api/updates/+server.ts new file mode 100644 index 0000000..66c9079 --- /dev/null +++ b/src/routes/api/updates/+server.ts @@ -0,0 +1,27 @@ +import { serviceDataEvents } from '$lib/server/dataPolling'; +import type { RequestHandler } from '@sveltejs/kit'; +import type { Unsubscriber } from 'svelte/store'; + +export const GET: RequestHandler = () => { + let unsubscribe: Unsubscriber | undefined = undefined; + const stream = new ReadableStream({ + start: (controller) => { + console.debug('ServiceDataEvent stream started'); + unsubscribe = serviceDataEvents.subscribe((event) => { + const data = `event:message\ndata:${JSON.stringify(event)}\n\n`; + controller.enqueue(data); + }); + }, + cancel: (reason) => { + console.debug('ServiceDataEvent stream canceled: ', reason); + if (unsubscribe != undefined) { + unsubscribe(); + } + } + }); + return new Response(stream, { + headers: { + 'Content-Type': 'text/event-stream' + } + }); +}; diff --git a/vite.config.ts b/vite.config.ts index 1329e84..3cc9a1d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,5 +8,6 @@ export default defineConfig({ include: ['src/**/*.{test,spec}.{js,ts}'] }, - build: { target: 'esnext' } + build: { target: 'esnext' }, + logLevel: 'info' });