import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { z } from "zod"; import { logger } from "~/services/logger.server"; import { sse } from "~/utils/sse.server"; export async function loader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); const searchParams = Object.fromEntries(url.searchParams.entries()); const options = z .object({ minDelay: z.coerce.number().int(), maxDelay: z.coerce.number().int(), undefinedProbability: z.coerce.number().min(0).max(1).default(0.1), }) .parse(searchParams); logger.debug("Test SSE stream", { options }); let lastSignals = calculateChangeSignals(Date.now()); return sse({ request, run: async (send, stop) => { const result = await dateForUpdates(options); if (!result) { return stop(); } const newSignals = calculateChangeSignals(result); if (lastSignals.ts !== newSignals.ts) { send({ data: JSON.stringify(newSignals) }); } lastSignals = newSignals; }, }); } async function dateForUpdates(opts: { minDelay: number; maxDelay: number; undefinedProbability: number; }): Promise { // Randomly await between minDelay and maxDelay await new Promise((resolve) => { setTimeout(resolve, Math.random() * (opts.maxDelay - opts.minDelay) + opts.minDelay); }); // There should be about a x% chance that this returns undefined if (Math.random() < opts.undefinedProbability) { logger.debug("Test SSE dataForUpdates returning undefined"); return undefined; } // Randomly return true or false return Date.now(); } function calculateChangeSignals(ts: number) { return { ts, }; }