UNPKG

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