| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 |
|
| 11 |
|
| 12 | const encoder = new TextEncoder();
|
| 13 | const trailer = "</body></html>";
|
| 14 | function injectRSCPayload(rscStream) {
|
| 15 | let decoder = new TextDecoder();
|
| 16 | let resolveFlightDataPromise;
|
| 17 | let flightDataPromise = new Promise((resolve) => resolveFlightDataPromise = resolve);
|
| 18 | let startedRSC = false;
|
| 19 | let buffered = [];
|
| 20 | let timeout = null;
|
| 21 | function flushBufferedChunks(controller) {
|
| 22 | for (let chunk of buffered) {
|
| 23 | let buf = decoder.decode(chunk, { stream: true });
|
| 24 | if (buf.endsWith(trailer)) buf = buf.slice(0, -14);
|
| 25 | controller.enqueue(encoder.encode(buf));
|
| 26 | }
|
| 27 | buffered.length = 0;
|
| 28 | timeout = null;
|
| 29 | }
|
| 30 | return new TransformStream({
|
| 31 | transform(chunk, controller) {
|
| 32 | buffered.push(chunk);
|
| 33 | if (timeout) return;
|
| 34 | timeout = setTimeout(async () => {
|
| 35 | flushBufferedChunks(controller);
|
| 36 | if (!startedRSC) {
|
| 37 | startedRSC = true;
|
| 38 | writeRSCStream(rscStream, controller).catch((err) => controller.error(err)).then(resolveFlightDataPromise);
|
| 39 | }
|
| 40 | }, 0);
|
| 41 | },
|
| 42 | async flush(controller) {
|
| 43 | await flightDataPromise;
|
| 44 | if (timeout) {
|
| 45 | clearTimeout(timeout);
|
| 46 | flushBufferedChunks(controller);
|
| 47 | }
|
| 48 | controller.enqueue(encoder.encode("</body></html>"));
|
| 49 | }
|
| 50 | });
|
| 51 | }
|
| 52 | async function writeRSCStream(rscStream, controller) {
|
| 53 | let decoder = new TextDecoder("utf-8", { fatal: true });
|
| 54 | const reader = rscStream.getReader();
|
| 55 | try {
|
| 56 | let read;
|
| 57 | while ((read = await reader.read()) && !read.done) {
|
| 58 | const chunk = read.value;
|
| 59 | try {
|
| 60 | writeChunk(JSON.stringify(decoder.decode(chunk, { stream: true })), controller);
|
| 61 | } catch (e) {
|
| 62 | writeChunk(`Uint8Array.from(atob(${JSON.stringify(btoa(String.fromCodePoint(...chunk)))}), m => m.codePointAt(0))`, controller);
|
| 63 | }
|
| 64 | }
|
| 65 | } finally {
|
| 66 | reader.releaseLock();
|
| 67 | }
|
| 68 | let remaining = decoder.decode();
|
| 69 | if (remaining.length) writeChunk(JSON.stringify(remaining), controller);
|
| 70 | }
|
| 71 | function writeChunk(chunk, controller) {
|
| 72 | controller.enqueue(encoder.encode(`<script>${escapeScript(`(self.__FLIGHT_DATA||=[]).push(${chunk})`)}<\/script>`));
|
| 73 | }
|
| 74 | function escapeScript(script) {
|
| 75 | return script.replace(/<!--/g, "<\\!--").replace(/<\/(script)/gi, "</\\$1");
|
| 76 | }
|
| 77 |
|
| 78 | export { injectRSCPayload };
|