diff --git a/docs/sw.js b/docs/sw.js index eaa3c81..fdf787b 100644 --- a/docs/sw.js +++ b/docs/sw.js @@ -1,16 +1,91 @@ -const V = "1"; +const V = "2"; const send = (m) => self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((cs) => cs.forEach((c) => c.postMessage(m))); self.addEventListener("install", (e) => { self.skipWaiting(); }); self.addEventListener("activate", (e) => { - e.waitUntil((async () => { - await self.clients.claim(); - await send({ type: "SW_ACTIVE", v: V, ts: Date.now() }); + e.waitUntil(self.clients.claim().then(() => send({ type: "SW_ACTIVE", v: V, ts: Date.now() }))); +}); +const ORIGINS = ["openrouter.ai"]; +const isTarget = (req) => { + const u = new URL(req.url); + return ORIGINS.some((h) => u.hostname.endsWith(h)) && req.method === "POST"; +}; +const wantsStream = async (req) => { + try { + return /"stream"\s*:\s*true/i.test(await req.clone().text()); + } catch { + return false; + } +}; +self.addEventListener("fetch", (e) => { + const req = e.request; + const nav = req.mode === "navigate"; + if (nav) return; + if (!isTarget(req)) return; + e.respondWith((async () => { + if (!await wantsStream(req)) return fetch(req); + const netRes = await fetch(req); + const reader = netRes.body?.getReader(); + if (!reader) return netRes; + send({ type: "SW_STREAM_START", url: req.url, ts: Date.now() }); + let done = false, bytes = 0, buf = [], bufBytes = 0, notify = null; + const MAX = 2 * 1024 * 1024, LOW = 1 * 1024 * 1024; + const pump = (async () => { + for (; ; ) { + const r = await reader.read(); + if (r.done) { + done = true; + break; + } + const chunk = r.value; + buf.push(chunk); + bufBytes += chunk.byteLength || chunk.length; + bytes += chunk.byteLength || chunk.length; + send({ type: "SW_STREAM_BYTES", n: bytes }); + if (bufBytes > MAX) await new Promise((r2) => { + notify = r2; + }); + } + })(); + e.waitUntil(pump); + const stream = new ReadableStream({ + start(c) { + const loop = async () => { + for (; ; ) { + if (buf.length) { + const x = buf.shift(); + bufBytes -= x.byteLength || x.length; + c.enqueue(x); + if (notify && bufBytes <= LOW) { + const n = notify; + notify = null; + n(); + } + } else if (done) { + c.close(); + send({ type: "SW_STREAM_END", bytes }); + break; + } else await new Promise((r) => setTimeout(r, 30)); + } + }; + loop(); + }, + cancel() { + try { + reader.cancel(); + } catch { + } + } + }); + const h = new Headers(netRes.headers); + h.delete("content-length"); + return new Response(stream, { status: netRes.status, statusText: netRes.statusText, headers: h }); })()); }); self.addEventListener("message", (e) => { - if (e.data && e.data.type === "PING") { + const m = e.data || {}; + if (m && m.type === "PING") { (e.source || null)?.postMessage({ type: "PONG", v: V, ts: Date.now() }) || send({ type: "PONG", v: V, ts: Date.now() }); } });