INDIE UNPKG

3.31 kBJavaScriptView Raw
1/**
2 * react-router v8.5.0
3 *
4 * Copyright (c) Remix Software Inc.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE.md file in the root directory of this source tree.
8 *
9 * @license MIT
10 */
11import { escapeAttribute } from "../../dom/ssr/markup.js";
12//#region lib/rsc/html-stream/server.ts
13const encoder = new TextEncoder();
14const trailer = "</body></html>";
15function injectRSCPayload(rscStream, { nonce } = {}) {
16 let decoder = new TextDecoder();
17 let resolveFlightDataPromise;
18 let flightDataPromise = new Promise((resolve) => resolveFlightDataPromise = resolve);
19 let startedRSC = false;
20 let cancelled = false;
21 let rscReader = null;
22 let buffered = [];
23 let timeout = null;
24 async function stop(reason) {
25 cancelled = true;
26 if (timeout) {
27 clearTimeout(timeout);
28 timeout = null;
29 }
30 buffered.length = 0;
31 resolveFlightDataPromise();
32 if (rscReader && rscStream.locked) await rscReader.cancel(reason).catch(() => {});
33 else await rscStream.cancel(reason).catch(() => {});
34 }
35 function tryEnqueue(controller, chunk) {
36 try {
37 controller.enqueue(chunk);
38 return true;
39 } catch (error) {
40 stop(error);
41 return false;
42 }
43 }
44 function flushBufferedChunks(controller) {
45 for (let chunk of buffered) {
46 let buf = decoder.decode(chunk, { stream: true });
47 if (buf.endsWith(trailer)) buf = buf.slice(0, -14);
48 if (!tryEnqueue(controller, encoder.encode(buf))) return;
49 }
50 buffered.length = 0;
51 timeout = null;
52 }
53 return new TransformStream({
54 transform(chunk, controller) {
55 buffered.push(chunk);
56 if (timeout) return;
57 timeout = setTimeout(async () => {
58 if (cancelled) return;
59 flushBufferedChunks(controller);
60 if (!startedRSC) {
61 startedRSC = true;
62 rscReader = rscStream.getReader();
63 writeRSCStream(rscReader, controller, () => cancelled, nonce).catch((err) => controller.error(err)).then(resolveFlightDataPromise);
64 }
65 }, 0);
66 },
67 async flush(controller) {
68 await flightDataPromise;
69 if (timeout) {
70 clearTimeout(timeout);
71 flushBufferedChunks(controller);
72 }
73 tryEnqueue(controller, encoder.encode("</body></html>"));
74 },
75 async cancel(reason) {
76 await stop(reason);
77 }
78 });
79}
80async function writeRSCStream(reader, controller, isCancelled, nonce) {
81 let decoder = new TextDecoder("utf-8", { fatal: true });
82 try {
83 let read;
84 while ((read = await reader.read()) && !read.done) {
85 if (isCancelled()) return;
86 const chunk = read.value;
87 try {
88 writeChunk(JSON.stringify(decoder.decode(chunk, { stream: true })), controller, nonce);
89 } catch {
90 writeChunk(`Uint8Array.from(atob(${JSON.stringify(btoa(String.fromCodePoint(...chunk)))}), m => m.codePointAt(0))`, controller, nonce);
91 }
92 }
93 } finally {
94 reader.releaseLock();
95 }
96 let remaining = decoder.decode();
97 if (remaining.length && !isCancelled()) writeChunk(JSON.stringify(remaining), controller, nonce);
98}
99function writeChunk(chunk, controller, nonce) {
100 let nonceAttr = nonce == null ? "" : ` nonce="${escapeAttribute(nonce)}"`;
101 controller.enqueue(encoder.encode(`<script${nonceAttr}>${escapeScript(`(self.__FLIGHT_DATA||=[]).push(${chunk})`)}<\/script>`));
102}
103function escapeScript(script) {
104 return script.replace(/<!--/g, "<\\!--").replace(/<\/(script)/gi, "</\\$1");
105}
106//#endregion
107export { injectRSCPayload };