UNPKG

3.07 kBJavaScriptView Raw
1/**
2 * react-router v8.4.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 function flushBufferedChunks(controller) {
25 for (let chunk of buffered) {
26 let buf = decoder.decode(chunk, { stream: true });
27 if (buf.endsWith(trailer)) buf = buf.slice(0, -14);
28 controller.enqueue(encoder.encode(buf));
29 }
30 buffered.length = 0;
31 timeout = null;
32 }
33 return new TransformStream({
34 transform(chunk, controller) {
35 buffered.push(chunk);
36 if (timeout) return;
37 timeout = setTimeout(async () => {
38 if (cancelled) return;
39 flushBufferedChunks(controller);
40 if (!startedRSC) {
41 startedRSC = true;
42 rscReader = rscStream.getReader();
43 writeRSCStream(rscReader, controller, () => cancelled, nonce).catch((err) => controller.error(err)).then(resolveFlightDataPromise);
44 }
45 }, 0);
46 },
47 async flush(controller) {
48 await flightDataPromise;
49 if (timeout) {
50 clearTimeout(timeout);
51 flushBufferedChunks(controller);
52 }
53 controller.enqueue(encoder.encode("</body></html>"));
54 },
55 async cancel(reason) {
56 cancelled = true;
57 if (timeout) {
58 clearTimeout(timeout);
59 timeout = null;
60 }
61 buffered.length = 0;
62 if (rscReader) await rscReader.cancel(reason).catch(() => {});
63 else await rscStream.cancel(reason).catch(() => {});
64 resolveFlightDataPromise();
65 }
66 });
67}
68async function writeRSCStream(reader, controller, isCancelled, nonce) {
69 let decoder = new TextDecoder("utf-8", { fatal: true });
70 try {
71 let read;
72 while ((read = await reader.read()) && !read.done) {
73 if (isCancelled()) return;
74 const chunk = read.value;
75 try {
76 writeChunk(JSON.stringify(decoder.decode(chunk, { stream: true })), controller, nonce);
77 } catch {
78 writeChunk(`Uint8Array.from(atob(${JSON.stringify(btoa(String.fromCodePoint(...chunk)))}), m => m.codePointAt(0))`, controller, nonce);
79 }
80 }
81 } finally {
82 reader.releaseLock();
83 }
84 let remaining = decoder.decode();
85 if (remaining.length && !isCancelled()) writeChunk(JSON.stringify(remaining), controller, nonce);
86}
87function writeChunk(chunk, controller, nonce) {
88 let nonceAttr = nonce == null ? "" : ` nonce="${escapeAttribute(nonce)}"`;
89 controller.enqueue(encoder.encode(`<script${nonceAttr}>${escapeScript(`(self.__FLIGHT_DATA||=[]).push(${chunk})`)}<\/script>`));
90}
91function escapeScript(script) {
92 return script.replace(/<!--/g, "<\\!--").replace(/<\/(script)/gi, "</\\$1");
93}
94//#endregion
95export { injectRSCPayload };