AUDIT / src /workers /streamWorker.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 4)
641b62c verified
Raw
History Blame Contribute Delete
3.79 kB
/**
* streamWorker.ts — SSE Parsing in Web Worker (Sessione 11)
*
* Sposta il parsing degli eventi SSE dal main thread (React/UI) a un
* Dedicated Worker, liberando il frame budget per l'UI su iOS.
*
* PROTOCOLLO postMessage:
*
* Main → Worker:
* { type: "parse", id: string, chunk: string }
* { type: "ping" }
* { type: "shutdown" }
*
* Worker → Main:
* { type: "events", id: string, events: SSEWorkerEvent[] }
* { type: "pong" }
* { type: "error", id: string, message: string }
*
* COMPATIBILITÀ:
* - Safari 15+: Dedicated Worker ✅
* - SharedWorker: NON usato (Safari supporto limitato)
* - Fallback: workerManager ritorna null → parsing sincrono nel main thread
*
* FORMATO SSEWorkerEvent (sottoinsieme di SSEEvent del main thread):
* { event: string; data: string; id?: string }
*
* INVARIANTI:
* - Nessun import esterno (Worker gira in contesto isolato senza bundler)
* - Nessun accesso a DOM, localStorage, Dexie (non disponibili in Worker)
* - Nessun try/catch globale silenzioso — errori inviati via postMessage
* - Auto-cleanup: porta chiusa da workerManager dopo idle > 60s
*/
export interface SSEWorkerEvent {
event: string;
data: string;
id?: string;
}
// ─── SSE Chunk Parser ─────────────────────────────────────────────────────────
/**
* Parsa un chunk SSE raw (può contenere più eventi) in una lista di SSEWorkerEvent.
*
* Formato SSE RFC 6455:
* field: value\n
* field: value\n
* \n ← separa eventi multipli
*
* Campi supportati: event, data, id
*/
function parseSSEChunk(chunk: string): SSEWorkerEvent[] {
const events: SSEWorkerEvent[] = [];
// Separa per doppia newline (separatore evento SSE)
const rawBlocks = chunk.split(/\n\n+/);
for (const block of rawBlocks) {
const lines = block.split("\n");
let event = "message";
let data = "";
let id: string | undefined;
let hasData = false;
for (const line of lines) {
if (line.startsWith(":")) continue; // commento SSE
const colonIdx = line.indexOf(":");
if (colonIdx === -1) continue;
const field = line.slice(0, colonIdx).trim();
const value = line.slice(colonIdx + 1).trimStart();
switch (field) {
case "event": event = value; break;
case "data": data = data ? data + "\n" + value : value; hasData = true; break;
case "id": id = value; break;
default: /* ignora campi sconosciuti */
}
}
if (hasData) {
events.push({ event, data, ...(id !== undefined && { id }) });
}
}
return events;
}
// ─── Worker message handler ───────────────────────────────────────────────────
// Questo codice gira nel contesto del Worker (non del main thread).
// self = DedicatedWorkerGlobalScope
self.addEventListener("message", (e: MessageEvent) => {
const msg = e.data as { type: string; id?: string; chunk?: string };
switch (msg.type) {
case "parse": {
try {
const events = parseSSEChunk(msg.chunk ?? "");
self.postMessage({ type: "events", id: msg.id, events });
} catch (err) {
self.postMessage({
type: "error",
id: msg.id,
message: err instanceof Error ? err.message : String(err),
});
}
break;
}
case "ping": {
self.postMessage({ type: "pong" });
break;
}
case "shutdown": {
self.close();
break;
}
default:
// Ignora messaggi sconosciuti
}
});