2025-03-05 15:36:50 +01:00
|
|
|
import { Schedule } from "~/shared/types/schedule"
|
2025-03-10 16:26:52 +01:00
|
|
|
import { readAccounts } from "~/server/database";
|
|
|
|
import { canSeeCrew } from "./utils/schedule";
|
2025-02-27 18:39:04 +01:00
|
|
|
|
|
|
|
function sendMessage(
|
|
|
|
stream: WritableStream<string>,
|
|
|
|
message: string,
|
|
|
|
) {
|
|
|
|
const writer = stream.getWriter();
|
|
|
|
writer.ready
|
|
|
|
.then(() => writer.write(message))
|
|
|
|
.catch(console.error)
|
|
|
|
.finally(() => writer.releaseLock())
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
2025-03-10 16:26:52 +01:00
|
|
|
function sendMessageAndClose(
|
|
|
|
stream: WritableStream<string>,
|
|
|
|
message: string,
|
|
|
|
) {
|
|
|
|
const writer = stream.getWriter();
|
|
|
|
writer.ready
|
|
|
|
.then(() => {
|
|
|
|
writer.write(message);
|
|
|
|
writer.close();
|
|
|
|
}).catch(console.error)
|
|
|
|
.finally(() => writer.releaseLock())
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
const streams = new Map<WritableStream<string>, { sessionId?: number, accountId?: number }>();
|
2025-02-27 18:39:04 +01:00
|
|
|
|
|
|
|
let keepaliveInterval: ReturnType<typeof setInterval> | null = null
|
2025-03-10 16:26:52 +01:00
|
|
|
export function addStream(stream: WritableStream<string>, sessionId?: number, accountId?: number) {
|
2025-02-27 18:39:04 +01:00
|
|
|
if (streams.size === 0) {
|
|
|
|
console.log("Starting keepalive")
|
|
|
|
keepaliveInterval = setInterval(sendKeepalive, 4000)
|
|
|
|
}
|
2025-03-10 16:26:52 +01:00
|
|
|
streams.set(stream, { sessionId, accountId });
|
|
|
|
// Produce a response immediately to avoid the reply waiting for content.
|
|
|
|
const message = `data: connected sid:${sessionId ?? '-'} aid:${accountId ?? '-'}\n\n`;
|
|
|
|
sendMessage(stream, message);
|
2025-02-27 18:39:04 +01:00
|
|
|
}
|
|
|
|
export function deleteStream(stream: WritableStream<string>) {
|
|
|
|
streams.delete(stream);
|
|
|
|
if (streams.size === 0) {
|
|
|
|
console.log("Ending keepalive")
|
|
|
|
clearInterval(keepaliveInterval!);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-10 16:26:52 +01:00
|
|
|
export function cancelAccountStreams(accountId: number) {
|
|
|
|
for (const [stream, data] of streams) {
|
|
|
|
if (data.accountId === accountId) {
|
|
|
|
sendMessageAndClose(stream, `data: cancelled\n\n`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function cancelSessionStreams(sessionId: number) {
|
|
|
|
for (const [stream, data] of streams) {
|
|
|
|
if (data.sessionId === sessionId) {
|
|
|
|
sendMessageAndClose(stream, `data: cancelled\n\n`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function broadcastUpdate(schedule: Schedule) {
|
2025-02-27 18:39:04 +01:00
|
|
|
const id = Date.now();
|
2025-03-10 16:26:52 +01:00
|
|
|
console.log(`broadcasting update to ${streams.size} clients`);
|
|
|
|
if (!streams.size) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const accounts = await readAccounts();
|
|
|
|
const filteredSchedule = filterSchedule(schedule);
|
|
|
|
for (const [stream, streamData] of streams) {
|
|
|
|
let accountType: string | undefined;
|
|
|
|
if (streamData.accountId !== undefined) {
|
|
|
|
accountType = accounts.find(a => a.id === streamData.accountId)?.type
|
|
|
|
}
|
|
|
|
const data = JSON.stringify(canSeeCrew(accountType) ? schedule : filteredSchedule);
|
|
|
|
const message = `id: ${id}\nevent: update\ndata: ${data}\n\n`
|
2025-02-27 18:39:04 +01:00
|
|
|
sendMessage(stream, message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function sendKeepalive() {
|
2025-03-10 16:26:52 +01:00
|
|
|
for (const stream of streams.keys()) {
|
2025-05-25 23:38:24 +02:00
|
|
|
sendMessage(stream, ": keepalive\n");
|
2025-02-27 18:39:04 +01:00
|
|
|
}
|
|
|
|
}
|