@braidlabs/gateway 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +464 -0
- package/package.json +44 -0
- package/src/appd.d.ts +130 -0
- package/src/appd.js +93 -0
- package/src/appd.js.map +1 -0
- package/src/breaker.d.ts +51 -0
- package/src/breaker.js +96 -0
- package/src/breaker.js.map +1 -0
- package/src/discovery.d.ts +91 -0
- package/src/discovery.js +127 -0
- package/src/discovery.js.map +1 -0
- package/src/gateway.d.ts +241 -0
- package/src/gateway.js +735 -0
- package/src/gateway.js.map +1 -0
- package/src/index.d.ts +36 -0
- package/src/index.js +27 -0
- package/src/index.js.map +1 -0
- package/src/node.d.ts +35 -0
- package/src/node.js +252 -0
- package/src/node.js.map +1 -0
- package/src/protocol.d.ts +79 -0
- package/src/protocol.js +89 -0
- package/src/protocol.js.map +1 -0
- package/src/registry.d.ts +184 -0
- package/src/registry.js +187 -0
- package/src/registry.js.map +1 -0
- package/src/rewriter/html-rewrite-stream.d.ts +70 -0
- package/src/rewriter/html-rewrite-stream.js +483 -0
- package/src/rewriter/html-rewrite-stream.js.map +1 -0
- package/src/rewriter/transforms.d.ts +107 -0
- package/src/rewriter/transforms.js +312 -0
- package/src/rewriter/transforms.js.map +1 -0
- package/src/single-flight.d.ts +53 -0
- package/src/single-flight.js +97 -0
- package/src/single-flight.js.map +1 -0
- package/src/telemetry.d.ts +115 -0
- package/src/telemetry.js +177 -0
- package/src/telemetry.js.map +1 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The owned streaming HTML rewriter (zero forked dependencies).
|
|
3
|
+
*
|
|
4
|
+
* Braid's streaming needs are narrow and known: rename three elements, neutralize scripts and
|
|
5
|
+
* script-preload links, inject content at slot elements, and interleave two HTML streams. That
|
|
6
|
+
* is a small enough surface to own outright rather than inherit a forked HTMLRewriter wasm
|
|
7
|
+
* build — and owning it is what makes the composition protocol testable as a spec.
|
|
8
|
+
*
|
|
9
|
+
* Properties this implementation guarantees:
|
|
10
|
+
*
|
|
11
|
+
* - **Streaming**: output is produced as input arrives. Only the current (partial) tag is
|
|
12
|
+
* buffered, so memory stays bounded regardless of document size.
|
|
13
|
+
* - **Chunk-boundary safe**: a tag, comment, doctype, or raw-text end sequence split across
|
|
14
|
+
* network chunks is reassembled before it is interpreted.
|
|
15
|
+
* - **Fidelity by default**: a tag no handler modifies is emitted byte-for-byte as it arrived.
|
|
16
|
+
* Only mutated tags are re-serialized.
|
|
17
|
+
* - **Stream injection**: injected content may itself be a `ReadableStream`, which is what makes
|
|
18
|
+
* piercing a true interleave (shell up to the slot, then the fragment's stream, then the rest
|
|
19
|
+
* of the shell) rather than a buffer-everything-then-concatenate.
|
|
20
|
+
*
|
|
21
|
+
* Scope note: this is a rewriter, not a parser. It does not build a tree, resolve implied tags,
|
|
22
|
+
* or handle malformed-markup recovery — it observes a linear token stream. That is sufficient
|
|
23
|
+
* for every transform in the composition protocol, and the conformance vectors in
|
|
24
|
+
* `html-rewrite-stream.spec.ts` are the oracle for it.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Elements whose content is not parsed as markup. Inside them, `<` starts a tag only in the
|
|
28
|
+
* element's own end-tag sequence.
|
|
29
|
+
*/
|
|
30
|
+
const RAW_TEXT_ELEMENTS = new Set(['script', 'style', 'title', 'textarea']);
|
|
31
|
+
/** Elements that never have an end tag, so renaming one must not open a rename scope. */
|
|
32
|
+
const VOID_ELEMENTS = new Set([
|
|
33
|
+
'area',
|
|
34
|
+
'base',
|
|
35
|
+
'br',
|
|
36
|
+
'col',
|
|
37
|
+
'embed',
|
|
38
|
+
'hr',
|
|
39
|
+
'img',
|
|
40
|
+
'input',
|
|
41
|
+
'link',
|
|
42
|
+
'meta',
|
|
43
|
+
'source',
|
|
44
|
+
'track',
|
|
45
|
+
'wbr',
|
|
46
|
+
]);
|
|
47
|
+
const encoder = new TextEncoder();
|
|
48
|
+
export function rewriteHtmlStream(input, options) {
|
|
49
|
+
return toReadableStream(rewrite(input, options));
|
|
50
|
+
}
|
|
51
|
+
async function* rewrite(input, options) {
|
|
52
|
+
const decoder = new TextDecoder();
|
|
53
|
+
const reader = input.getReader();
|
|
54
|
+
let buffer = '';
|
|
55
|
+
/** Non-null while inside a raw-text element, holding its lowercased tag name. */
|
|
56
|
+
let rawTextElement = null;
|
|
57
|
+
/**
|
|
58
|
+
* Open elements whose start tag a handler renamed. Renaming an element must rename its end
|
|
59
|
+
* tag too — otherwise the renamed element is never closed, and everything that follows nests
|
|
60
|
+
* inside it (a `<braid-head>` left open swallows the whole `<braid-body>`).
|
|
61
|
+
*/
|
|
62
|
+
const openRenames = [];
|
|
63
|
+
try {
|
|
64
|
+
for (;;) {
|
|
65
|
+
const { done, value } = await reader.read();
|
|
66
|
+
buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
|
|
67
|
+
const result = yield* consume(buffer, done);
|
|
68
|
+
buffer = result.remainder;
|
|
69
|
+
rawTextElement = result.rawTextElement;
|
|
70
|
+
if (done)
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
reader.releaseLock();
|
|
76
|
+
}
|
|
77
|
+
const trailing = options.onEnd?.();
|
|
78
|
+
if (trailing !== undefined) {
|
|
79
|
+
yield* emit(trailing);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Consumes as much of the buffer as can be interpreted unambiguously, yielding output.
|
|
83
|
+
* Returns whatever tail must wait for more input (a partial tag, or the start of what might
|
|
84
|
+
* be a raw-text end sequence).
|
|
85
|
+
*/
|
|
86
|
+
async function* consume(text, isFinal) {
|
|
87
|
+
let index = 0;
|
|
88
|
+
for (;;) {
|
|
89
|
+
if (rawTextElement) {
|
|
90
|
+
const closeIndex = findRawTextEnd(text, index, rawTextElement);
|
|
91
|
+
if (closeIndex === -1) {
|
|
92
|
+
if (isFinal) {
|
|
93
|
+
yield* emitText(text.slice(index));
|
|
94
|
+
return { remainder: '', rawTextElement };
|
|
95
|
+
}
|
|
96
|
+
// hold back enough characters that a split end-tag sequence stays reassemblable
|
|
97
|
+
const safeEnd = text.length - (rawTextElement.length + 3);
|
|
98
|
+
if (safeEnd > index) {
|
|
99
|
+
yield* emitText(text.slice(index, safeEnd));
|
|
100
|
+
index = safeEnd;
|
|
101
|
+
}
|
|
102
|
+
return { remainder: text.slice(index), rawTextElement };
|
|
103
|
+
}
|
|
104
|
+
if (closeIndex > index) {
|
|
105
|
+
yield* emitText(text.slice(index, closeIndex));
|
|
106
|
+
}
|
|
107
|
+
index = closeIndex;
|
|
108
|
+
rawTextElement = null;
|
|
109
|
+
}
|
|
110
|
+
const tagStart = text.indexOf('<', index);
|
|
111
|
+
if (tagStart === -1) {
|
|
112
|
+
yield* emitText(text.slice(index));
|
|
113
|
+
return { remainder: '', rawTextElement };
|
|
114
|
+
}
|
|
115
|
+
if (tagStart > index) {
|
|
116
|
+
yield* emitText(text.slice(index, tagStart));
|
|
117
|
+
index = tagStart;
|
|
118
|
+
}
|
|
119
|
+
const token = scanToken(text, tagStart);
|
|
120
|
+
if (!token) {
|
|
121
|
+
if (isFinal) {
|
|
122
|
+
// an unterminated tag at end of input is emitted verbatim rather than swallowed
|
|
123
|
+
yield* emitText(text.slice(index));
|
|
124
|
+
return { remainder: '', rawTextElement };
|
|
125
|
+
}
|
|
126
|
+
return { remainder: text.slice(index), rawTextElement };
|
|
127
|
+
}
|
|
128
|
+
const raw = text.slice(tagStart, token.end);
|
|
129
|
+
index = token.end;
|
|
130
|
+
switch (token.kind) {
|
|
131
|
+
case 'doctype':
|
|
132
|
+
if (!options.stripDoctype)
|
|
133
|
+
yield* emitText(raw);
|
|
134
|
+
break;
|
|
135
|
+
case 'comment':
|
|
136
|
+
case 'bogus':
|
|
137
|
+
yield* emitText(raw);
|
|
138
|
+
break;
|
|
139
|
+
case 'start': {
|
|
140
|
+
yield* emitStartTag(token, raw);
|
|
141
|
+
if (!token.selfClosing && RAW_TEXT_ELEMENTS.has(token.name)) {
|
|
142
|
+
rawTextElement = token.name;
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case 'end': {
|
|
147
|
+
yield* emitEndTag(token, raw);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function* emitStartTag(token, raw) {
|
|
154
|
+
// the wildcard handler runs first, so element-specific handlers see (and can override) its
|
|
155
|
+
// work rather than racing it
|
|
156
|
+
const elementHandlers = [options.handlers['*']?.element, options.handlers[token.name]?.element].filter((element) => element !== undefined);
|
|
157
|
+
if (elementHandlers.length === 0) {
|
|
158
|
+
yield* emitText(raw);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const originalName = token.name;
|
|
162
|
+
const before = [];
|
|
163
|
+
const prepend = [];
|
|
164
|
+
let dirty = false;
|
|
165
|
+
let name = token.name;
|
|
166
|
+
const attributes = token.attributes;
|
|
167
|
+
const tag = {
|
|
168
|
+
get tagName() {
|
|
169
|
+
return name;
|
|
170
|
+
},
|
|
171
|
+
set tagName(value) {
|
|
172
|
+
name = value.toLowerCase();
|
|
173
|
+
dirty = true;
|
|
174
|
+
},
|
|
175
|
+
selfClosing: token.selfClosing,
|
|
176
|
+
get attributeNames() {
|
|
177
|
+
return attributes.map((attribute) => attribute.name);
|
|
178
|
+
},
|
|
179
|
+
getAttribute(attributeName) {
|
|
180
|
+
return attributes.find((attribute) => attribute.name === attributeName.toLowerCase())?.value ?? null;
|
|
181
|
+
},
|
|
182
|
+
setAttribute(attributeName, value) {
|
|
183
|
+
const lowercased = attributeName.toLowerCase();
|
|
184
|
+
const existing = attributes.find((attribute) => attribute.name === lowercased);
|
|
185
|
+
if (existing) {
|
|
186
|
+
existing.value = value;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
attributes.push({ name: lowercased, rawName: attributeName, value });
|
|
190
|
+
}
|
|
191
|
+
dirty = true;
|
|
192
|
+
},
|
|
193
|
+
removeAttribute(attributeName) {
|
|
194
|
+
const lowercased = attributeName.toLowerCase();
|
|
195
|
+
const at = attributes.findIndex((attribute) => attribute.name === lowercased);
|
|
196
|
+
if (at !== -1) {
|
|
197
|
+
attributes.splice(at, 1);
|
|
198
|
+
dirty = true;
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
before(content) {
|
|
202
|
+
before.push(content);
|
|
203
|
+
},
|
|
204
|
+
prepend(content) {
|
|
205
|
+
prepend.push(content);
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
for (const element of elementHandlers) {
|
|
209
|
+
element(tag);
|
|
210
|
+
}
|
|
211
|
+
if (name !== originalName && !token.selfClosing && !VOID_ELEMENTS.has(originalName)) {
|
|
212
|
+
openRenames.push({ original: originalName, renamed: name });
|
|
213
|
+
}
|
|
214
|
+
for (const content of before) {
|
|
215
|
+
yield* emit(content);
|
|
216
|
+
}
|
|
217
|
+
yield* emitText(dirty ? serializeStartTag(name, attributes, token.selfClosing) : raw);
|
|
218
|
+
for (const content of prepend) {
|
|
219
|
+
yield* emit(content);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function* emitEndTag(token, raw) {
|
|
223
|
+
const handler = options.handlers[token.name];
|
|
224
|
+
// close the innermost open element that was renamed under this name, discarding any renames
|
|
225
|
+
// nested inside it (their end tags were implied, or never arrived)
|
|
226
|
+
const renameIndex = openRenames.findLastIndex((entry) => entry.original === token.name);
|
|
227
|
+
const renamedTo = renameIndex === -1 ? null : openRenames[renameIndex].renamed;
|
|
228
|
+
if (renameIndex !== -1) {
|
|
229
|
+
openRenames.length = renameIndex;
|
|
230
|
+
}
|
|
231
|
+
const text = renamedTo ? `</${renamedTo}>` : raw;
|
|
232
|
+
if (!handler?.endTag) {
|
|
233
|
+
yield* emitText(text);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const before = [];
|
|
237
|
+
handler.endTag({
|
|
238
|
+
tagName: token.name,
|
|
239
|
+
before(content) {
|
|
240
|
+
before.push(content);
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
for (const content of before) {
|
|
244
|
+
yield* emit(content);
|
|
245
|
+
}
|
|
246
|
+
yield* emitText(text);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
async function* emit(content) {
|
|
250
|
+
if (typeof content === 'string') {
|
|
251
|
+
yield* emitText(content);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const reader = content.getReader();
|
|
255
|
+
try {
|
|
256
|
+
for (;;) {
|
|
257
|
+
const { done, value } = await reader.read();
|
|
258
|
+
if (done)
|
|
259
|
+
break;
|
|
260
|
+
if (value)
|
|
261
|
+
yield value;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
reader.releaseLock();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function* emitText(text) {
|
|
269
|
+
if (text)
|
|
270
|
+
yield encoder.encode(text);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Scans one token starting at `start` (which must point at `<`).
|
|
274
|
+
*
|
|
275
|
+
* @returns the token, or null when the buffer ends mid-token and more input is needed.
|
|
276
|
+
*/
|
|
277
|
+
function scanToken(text, start) {
|
|
278
|
+
const length = text.length;
|
|
279
|
+
let index = start + 1;
|
|
280
|
+
if (index >= length)
|
|
281
|
+
return null;
|
|
282
|
+
// markup declarations: comments, doctypes, CDATA
|
|
283
|
+
if (text[index] === '!') {
|
|
284
|
+
if (text.startsWith('<!--', start)) {
|
|
285
|
+
const close = text.indexOf('-->', start + 4);
|
|
286
|
+
return close === -1 ? null : { kind: 'comment', end: close + 3, name: '', selfClosing: false, attributes: [] };
|
|
287
|
+
}
|
|
288
|
+
const close = findDeclarationEnd(text, start + 2);
|
|
289
|
+
if (close === -1)
|
|
290
|
+
return null;
|
|
291
|
+
const isDoctype = /^<!doctype/i.test(text.slice(start, start + 9));
|
|
292
|
+
return { kind: isDoctype ? 'doctype' : 'bogus', end: close, name: '', selfClosing: false, attributes: [] };
|
|
293
|
+
}
|
|
294
|
+
if (text[index] === '?') {
|
|
295
|
+
const close = text.indexOf('>', index);
|
|
296
|
+
return close === -1 ? null : { kind: 'bogus', end: close + 1, name: '', selfClosing: false, attributes: [] };
|
|
297
|
+
}
|
|
298
|
+
const isEndTag = text[index] === '/';
|
|
299
|
+
if (isEndTag)
|
|
300
|
+
index++;
|
|
301
|
+
const nameStart = index;
|
|
302
|
+
while (index < length && isTagNameChar(text[index]))
|
|
303
|
+
index++;
|
|
304
|
+
// Running out of buffer is "incomplete", never "not a tag" — the name may continue in the
|
|
305
|
+
// next chunk, and `</` at a chunk boundary must not be mistaken for literal text.
|
|
306
|
+
if (index >= length)
|
|
307
|
+
return null;
|
|
308
|
+
// `<` not followed by a tag name is literal text (`a < b`), not a tag
|
|
309
|
+
if (index === nameStart) {
|
|
310
|
+
return { kind: 'bogus', end: start + 1, name: '', selfClosing: false, attributes: [] };
|
|
311
|
+
}
|
|
312
|
+
const name = text.slice(nameStart, index).toLowerCase();
|
|
313
|
+
const attributes = [];
|
|
314
|
+
let selfClosing = false;
|
|
315
|
+
for (;;) {
|
|
316
|
+
while (index < length && isWhitespace(text[index]))
|
|
317
|
+
index++;
|
|
318
|
+
if (index >= length)
|
|
319
|
+
return null;
|
|
320
|
+
if (text[index] === '>') {
|
|
321
|
+
return { kind: isEndTag ? 'end' : 'start', end: index + 1, name, selfClosing, attributes };
|
|
322
|
+
}
|
|
323
|
+
if (text[index] === '/') {
|
|
324
|
+
if (index + 1 >= length)
|
|
325
|
+
return null;
|
|
326
|
+
if (text[index + 1] === '>') {
|
|
327
|
+
selfClosing = true;
|
|
328
|
+
return { kind: isEndTag ? 'end' : 'start', end: index + 2, name, selfClosing, attributes };
|
|
329
|
+
}
|
|
330
|
+
index++;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const attributeNameStart = index;
|
|
334
|
+
while (index < length && !isWhitespace(text[index]) && text[index] !== '=' && text[index] !== '>') {
|
|
335
|
+
if (text[index] === '/' && text[index + 1] === '>')
|
|
336
|
+
break;
|
|
337
|
+
index++;
|
|
338
|
+
}
|
|
339
|
+
if (index >= length)
|
|
340
|
+
return null;
|
|
341
|
+
const rawName = text.slice(attributeNameStart, index);
|
|
342
|
+
if (!rawName) {
|
|
343
|
+
// defensive: no progress would loop forever
|
|
344
|
+
index++;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
let cursor = index;
|
|
348
|
+
while (cursor < length && isWhitespace(text[cursor]))
|
|
349
|
+
cursor++;
|
|
350
|
+
if (cursor >= length)
|
|
351
|
+
return null;
|
|
352
|
+
let value = null;
|
|
353
|
+
if (text[cursor] === '=') {
|
|
354
|
+
cursor++;
|
|
355
|
+
while (cursor < length && isWhitespace(text[cursor]))
|
|
356
|
+
cursor++;
|
|
357
|
+
if (cursor >= length)
|
|
358
|
+
return null;
|
|
359
|
+
const quote = text[cursor];
|
|
360
|
+
if (quote === '"' || quote === "'") {
|
|
361
|
+
const close = text.indexOf(quote, cursor + 1);
|
|
362
|
+
if (close === -1)
|
|
363
|
+
return null;
|
|
364
|
+
value = decodeAttributeValue(text.slice(cursor + 1, close));
|
|
365
|
+
cursor = close + 1;
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
const valueStart = cursor;
|
|
369
|
+
while (cursor < length && !isWhitespace(text[cursor]) && text[cursor] !== '>')
|
|
370
|
+
cursor++;
|
|
371
|
+
if (cursor >= length)
|
|
372
|
+
return null;
|
|
373
|
+
value = decodeAttributeValue(text.slice(valueStart, cursor));
|
|
374
|
+
}
|
|
375
|
+
index = cursor;
|
|
376
|
+
}
|
|
377
|
+
attributes.push({ name: rawName.toLowerCase(), rawName, value });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/** Finds the end of a markup declaration, honoring quoted strings (doctypes may contain `>`). */
|
|
381
|
+
function findDeclarationEnd(text, from) {
|
|
382
|
+
let index = from;
|
|
383
|
+
let quote = null;
|
|
384
|
+
while (index < text.length) {
|
|
385
|
+
const char = text[index];
|
|
386
|
+
if (quote) {
|
|
387
|
+
if (char === quote)
|
|
388
|
+
quote = null;
|
|
389
|
+
}
|
|
390
|
+
else if (char === '"' || char === "'") {
|
|
391
|
+
quote = char;
|
|
392
|
+
}
|
|
393
|
+
else if (char === '>') {
|
|
394
|
+
return index + 1;
|
|
395
|
+
}
|
|
396
|
+
index++;
|
|
397
|
+
}
|
|
398
|
+
return -1;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Finds the start of a raw-text element's end tag (`</script`, `</style`, …), which is the only
|
|
402
|
+
* markup recognized inside such an element.
|
|
403
|
+
*/
|
|
404
|
+
function findRawTextEnd(text, from, tagName) {
|
|
405
|
+
const needle = `</${tagName}`;
|
|
406
|
+
const haystack = text.toLowerCase();
|
|
407
|
+
let index = from;
|
|
408
|
+
for (;;) {
|
|
409
|
+
const found = haystack.indexOf(needle, index);
|
|
410
|
+
if (found === -1)
|
|
411
|
+
return -1;
|
|
412
|
+
const after = text[found + needle.length];
|
|
413
|
+
// the end tag is only terminated by whitespace, `/`, `>` — `</scriptfoo` is raw text
|
|
414
|
+
if (after === undefined)
|
|
415
|
+
return -1;
|
|
416
|
+
if (isWhitespace(after) || after === '/' || after === '>')
|
|
417
|
+
return found;
|
|
418
|
+
index = found + needle.length;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function serializeStartTag(name, attributes, selfClosing) {
|
|
422
|
+
const serializedAttributes = attributes
|
|
423
|
+
.map((attribute) => attribute.value === null ? ` ${attribute.rawName}` : ` ${attribute.rawName}="${escapeAttributeValue(attribute.value)}"`)
|
|
424
|
+
.join('');
|
|
425
|
+
return `<${name}${serializedAttributes}${selfClosing ? '/>' : '>'}`;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Decodes the handful of entities that realistically appear in the attributes Braid reads
|
|
429
|
+
* (`name`, `type`, `rel`). Full entity decoding is deliberately out of scope: this is a
|
|
430
|
+
* rewriter, and every value it round-trips it also re-encodes.
|
|
431
|
+
*/
|
|
432
|
+
function decodeAttributeValue(value) {
|
|
433
|
+
if (!value.includes('&'))
|
|
434
|
+
return value;
|
|
435
|
+
return value
|
|
436
|
+
.replaceAll('<', '<')
|
|
437
|
+
.replaceAll('>', '>')
|
|
438
|
+
.replaceAll('"', '"')
|
|
439
|
+
.replaceAll(''', "'")
|
|
440
|
+
.replaceAll(''', "'")
|
|
441
|
+
.replaceAll('&', '&');
|
|
442
|
+
}
|
|
443
|
+
function escapeAttributeValue(value) {
|
|
444
|
+
return value.replaceAll('&', '&').replaceAll('"', '"');
|
|
445
|
+
}
|
|
446
|
+
function isWhitespace(char) {
|
|
447
|
+
return char === ' ' || char === '\t' || char === '\n' || char === '\r' || char === '\f';
|
|
448
|
+
}
|
|
449
|
+
function isTagNameChar(char) {
|
|
450
|
+
return /[a-zA-Z0-9:_.-]/.test(char);
|
|
451
|
+
}
|
|
452
|
+
/** Adapts an async generator of chunks into a ReadableStream. */
|
|
453
|
+
export function toReadableStream(generator) {
|
|
454
|
+
return new ReadableStream({
|
|
455
|
+
async pull(controller) {
|
|
456
|
+
try {
|
|
457
|
+
const { done, value } = await generator.next();
|
|
458
|
+
if (done) {
|
|
459
|
+
controller.close();
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
controller.enqueue(value);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
controller.error(error);
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
async cancel(reason) {
|
|
470
|
+
await generator.return(undefined).catch(() => undefined);
|
|
471
|
+
void reason;
|
|
472
|
+
},
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
/** Builds a stream from literal strings and other streams, concatenated in order. */
|
|
476
|
+
export function concatStreams(parts) {
|
|
477
|
+
return toReadableStream((async function* () {
|
|
478
|
+
for (const part of parts) {
|
|
479
|
+
yield* emit(part);
|
|
480
|
+
}
|
|
481
|
+
})());
|
|
482
|
+
}
|
|
483
|
+
//# sourceMappingURL=html-rewrite-stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-rewrite-stream.js","sourceRoot":"","sources":["../../../../../libs/braid-gateway/src/rewriter/html-rewrite-stream.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The composition protocol's HTML transforms, expressed against the owned rewriter.
|
|
3
|
+
*
|
|
4
|
+
* These are the normative behaviors a Braid gateway must implement; the conformance vectors in
|
|
5
|
+
* `transforms.spec.ts` are their oracle, and any second implementation (a native `HTMLRewriter`
|
|
6
|
+
* path on workerd, say) must pass the same vectors before it is allowed to serve traffic.
|
|
7
|
+
*/
|
|
8
|
+
/** The `<style>` the shell needs for slots and fragment stand-ins to lay out as blocks. */
|
|
9
|
+
export declare const BRAID_SHELL_STYLES = "<style>fragment-slot { display: block; }</style>";
|
|
10
|
+
/**
|
|
11
|
+
* The shell's CSP nonce, read from its own response headers.
|
|
12
|
+
*
|
|
13
|
+
* **The gateway injects inline markup into someone else's document**, and under a strict policy —
|
|
14
|
+
* `script-src 'nonce-…'`, `style-src 'nonce-…'` — anything without the right nonce is dropped
|
|
15
|
+
* silently by the browser. That is the worst possible failure shape: the page renders, the slot
|
|
16
|
+
* layout rule is gone, and nothing in the server logs mentions it.
|
|
17
|
+
*
|
|
18
|
+
* Reading the nonce off the shell's *own* CSP is the only correct source. Minting one here would
|
|
19
|
+
* be worse than useless: a nonce the shell's policy does not list is not trusted, and a nonce
|
|
20
|
+
* reused across responses is not a nonce. If the shell does not send a policy, or sends one
|
|
21
|
+
* without a nonce, this returns null and nothing is stamped — an unrestricted page needs no
|
|
22
|
+
* attribute, and a policy using hashes or `'unsafe-inline'` is not ours to second-guess.
|
|
23
|
+
*/
|
|
24
|
+
export declare function cspNonceOf(headers: Headers): string | null;
|
|
25
|
+
/** Stamps a nonce onto generated inline markup. A no-op when the shell has no policy. */
|
|
26
|
+
export declare function withNonce(markup: string, nonce: string | null): string;
|
|
27
|
+
/** The `<style>` that goes inside every fragment's shadow root. */
|
|
28
|
+
export declare const BRAID_FRAGMENT_STYLES = "<style>:host, braid-document, braid-html, braid-body { display: block; } braid-head { display: none; }</style>";
|
|
29
|
+
/**
|
|
30
|
+
* Prepares a fragment's HTML for life inside the host page:
|
|
31
|
+
*
|
|
32
|
+
* - the doctype is stripped (a nested doctype makes some parsers choke, and it materializes
|
|
33
|
+
* nothing in the DOM anyway);
|
|
34
|
+
* - `<html>`/`<head>`/`<body>` become `braid-html`/`braid-head`/`braid-body`, because the DOM
|
|
35
|
+
* forbids duplicates of those singletons and would silently drop them;
|
|
36
|
+
* - every `<script>` is neutralized (`type="inert"`, real type parked in `data-script-type`) so
|
|
37
|
+
* it cannot execute in the host's JS context — the client activates it in the fragment's realm;
|
|
38
|
+
* - script preload/prefetch/modulepreload links become `rel="inert-*"` so they don't trigger a
|
|
39
|
+
* duplicate load in the host context;
|
|
40
|
+
* - inline event handler attributes are removed, and `<meta http-equiv="refresh">` is defanged.
|
|
41
|
+
*
|
|
42
|
+
* This is the server half of the born-inert invariant, and the last two rules are what make the
|
|
43
|
+
* invariant true rather than merely true-of-`<script>`: **no markup a fragment sends can execute
|
|
44
|
+
* JavaScript in the host realm or navigate the host page.** Both were verified executing in the
|
|
45
|
+
* host realm before this transform existed.
|
|
46
|
+
*
|
|
47
|
+
* Still not neutralized, because they require user interaction rather than executing on parse:
|
|
48
|
+
* `javascript:` URLs and form `action`s. Those remain within the trusted tier's stated model —
|
|
49
|
+
* a trusted fragment can navigate the page a user clicks through — and are called out in the
|
|
50
|
+
* security section of the README.
|
|
51
|
+
*/
|
|
52
|
+
export declare function prepareFragmentHtml(body: ReadableStream<Uint8Array>, options: {
|
|
53
|
+
fragmentId: string;
|
|
54
|
+
}): ReadableStream<Uint8Array>;
|
|
55
|
+
export interface PierceTarget {
|
|
56
|
+
fragmentId: string;
|
|
57
|
+
/**
|
|
58
|
+
* The fragment's prepared content (see {@link prepareFragmentHtml}), or null to pierce
|
|
59
|
+
* nothing — the slot is left for the client to fill by fetching (the `omit`/`placeholder`
|
|
60
|
+
* failure fallbacks).
|
|
61
|
+
*/
|
|
62
|
+
content: ReadableStream<Uint8Array> | null;
|
|
63
|
+
/** Set as `data-braid-fallback` on the slot when content is omitted, for skeleton styling. */
|
|
64
|
+
fallbackReason?: string;
|
|
65
|
+
/**
|
|
66
|
+
* The path the manifest says this fragment's content lives at, for unbound fragments.
|
|
67
|
+
*
|
|
68
|
+
* The host's own template is what tells the client where to mount an unbound fragment, so this is
|
|
69
|
+
* carried only to check the two against each other. Declaring it in both places is the accepted
|
|
70
|
+
* cost of the slot working identically whether or not the page was pierced; warning on
|
|
71
|
+
* disagreement is what stops that duplication from drifting in silence.
|
|
72
|
+
*/
|
|
73
|
+
src?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface PierceOptions {
|
|
76
|
+
/** The shell application's HTML. */
|
|
77
|
+
shell: ReadableStream<Uint8Array>;
|
|
78
|
+
/** The fragments to pierce into this document, in registration order. */
|
|
79
|
+
fragments: PierceTarget[];
|
|
80
|
+
/**
|
|
81
|
+
* Markup appended inside `<head>`, after the shell styles. Used for the optional web-vitals
|
|
82
|
+
* collector; empty for every other deployment, which is why it is a string the caller composes
|
|
83
|
+
* rather than a telemetry-shaped option this layer would have to know about.
|
|
84
|
+
*/
|
|
85
|
+
headScript?: string;
|
|
86
|
+
/**
|
|
87
|
+
* The shell's CSP nonce, so injected inline markup survives a strict policy. See
|
|
88
|
+
* {@link cspNonceOf} for why this must come from the shell rather than be minted here.
|
|
89
|
+
*/
|
|
90
|
+
nonce?: string | null;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Pierces a fragment into the shell's HTML stream.
|
|
94
|
+
*
|
|
95
|
+
* The fragment's server-rendered content is injected into the matching `<fragment-slot>` as a
|
|
96
|
+
* declarative shadow root, so the browser parses it into exactly the shape the client runtime
|
|
97
|
+
* would have built — the slot then adopts it instead of fetching, and the fragment paints as
|
|
98
|
+
* part of the shell's first response rather than a round trip later.
|
|
99
|
+
*
|
|
100
|
+
* Injection is stream-interleaved: the shell streams out until the slot is reached, the
|
|
101
|
+
* fragment's stream is spliced in as it arrives, then the rest of the shell follows.
|
|
102
|
+
*
|
|
103
|
+
* If the shell contains no matching slot, the fragment is appended before `</body>` (or at the
|
|
104
|
+
* end of the document — `</body>` is optional in HTML and often omitted) inside a slot element
|
|
105
|
+
* the gateway creates. This keeps piercing working for shells that haven't been marked up yet.
|
|
106
|
+
*/
|
|
107
|
+
export declare function pierceShellHtml(options: PierceOptions): ReadableStream<Uint8Array>;
|