@aiquants/markdown-explorer 0.1.0 → 0.3.0
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/CHANGELOG.md +53 -1
- package/README.md +53 -16
- package/dist/{ExplorerSplit-DwEFmvQM.js → ExplorerSplit-B7pmzR8t.js} +3 -3
- package/dist/{ExplorerSplit-BESGFcu6.cjs → ExplorerSplit-Bi3yU6NG.cjs} +1 -1
- package/dist/MultiLayout-BxeXOLX1.js +909 -0
- package/dist/MultiLayout-DtFrFRmS.cjs +1 -0
- package/dist/TreeLayout-BQUfVNlA.cjs +1 -0
- package/dist/TreeLayout-D6hCSihW.js +22 -0
- package/dist/core/config.d.cts +8 -0
- package/dist/core/config.d.ts +8 -0
- package/dist/core/labels.d.cts +6 -1
- package/dist/core/labels.d.ts +6 -1
- package/dist/{dataRoute-CKbdwkQ7.cjs → dataRoute-B9Wk-G7A.cjs} +1 -1
- package/dist/{dataRoute-Bkfwfp1R.js → dataRoute-zUX-oR9j.js} +39 -38
- package/dist/{index-BgkZ9ATV.js → index-BJTmp0lR.js} +947 -901
- package/dist/index-DkiGlM7C.cjs +3 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +13 -13
- package/dist/location-DfYTzfFS.cjs +1 -0
- package/dist/{location-Cb2Ak55B.js → location-V5BKeWNs.js} +189 -162
- package/dist/sanitizeWorker.cjs +1 -1
- package/dist/sanitizeWorker.js +196 -206
- package/dist/server/anonymous.d.cts +2 -2
- package/dist/server/anonymous.d.ts +2 -2
- package/dist/server/ports.d.cts +4 -1
- package/dist/server/ports.d.ts +4 -1
- package/dist/server.cjs +1 -1
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +631 -603
- package/dist/styles/markdown-explorer.css +1 -1
- package/docs/behavior/dom-hooks.md +4 -4
- package/docs/behavior/layout.md +21 -9
- package/docs/behavior/localization.md +4 -3
- package/docs/behavior/security.md +23 -5
- package/docs/behavior/url-contract.md +8 -2
- package/package.json +5 -5
- package/dist/MultiLayout-DxPiARsv.js +0 -896
- package/dist/MultiLayout-r_qx1Mfz.cjs +0 -1
- package/dist/TreeLayout-9-20bbSM.cjs +0 -1
- package/dist/TreeLayout-CW7lgV_e.js +0 -19
- package/dist/index-y9tVvDW7.cjs +0 -3
- package/dist/location-DWHQYntR.cjs +0 -1
package/dist/server.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { D as
|
|
3
|
-
import { createHash as
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { b as it, c as _t, f as Re, d as Nt, g as At, h as It, j as Mt, E as vt, k as Pt, i as te, l as $t, r as ct, m as jt, n as Lt, o as ut, p as zt, q as Ct, s as de, t as ee, e as Dt } from "./dataRoute-zUX-oR9j.js";
|
|
2
|
+
import { D as lo, M as fo, u as mo } from "./dataRoute-zUX-oR9j.js";
|
|
3
|
+
import { createHash as Ht, createHmac as Bt } from "node:crypto";
|
|
4
|
+
import { createLinkCardHandler as Ft } from "@aiquants/markdown/server";
|
|
5
|
+
import { data as Pe, redirect as Ut } from "react-router";
|
|
6
|
+
import { w as Xt, d as Gt, r as Jt, i as Wt, a as Yt, p as Kt, b as Vt, f as Zt, N as Qt } from "./location-V5BKeWNs.js";
|
|
7
|
+
import { existsSync as qt, statSync as er, realpathSync as tr, constants as ie } from "node:fs";
|
|
8
|
+
import { extname as rr, isAbsolute as $e, relative as je, sep as Le, join as nr, resolve as or } from "node:path";
|
|
8
9
|
import { fileURLToPath as be } from "node:url";
|
|
9
|
-
import { Worker as
|
|
10
|
-
import { open as
|
|
11
|
-
import { Readable as
|
|
12
|
-
const
|
|
13
|
-
authenticate: async () => ({ status: "signed-in", principal:
|
|
14
|
-
principalKey: () =>
|
|
10
|
+
import { Worker as sr } from "node:worker_threads";
|
|
11
|
+
import { open as lt, lstat as ar, realpath as ir, readdir as cr } from "node:fs/promises";
|
|
12
|
+
import { Readable as ur } from "node:stream";
|
|
13
|
+
const ze = Object.freeze({ kind: "anonymous" }), so = Object.freeze({
|
|
14
|
+
authenticate: async () => ({ status: "signed-in", principal: ze }),
|
|
15
|
+
principalKey: () => ze.kind,
|
|
15
16
|
signedOutPage: (e, t) => new Response(null, { status: 401, headers: t })
|
|
16
|
-
}),
|
|
17
|
+
}), lr = "application/octet-stream", dt = "application/pdf", dr = /* @__PURE__ */ new Set([
|
|
17
18
|
"image/png",
|
|
18
19
|
"image/apng",
|
|
19
20
|
"image/jpeg",
|
|
@@ -39,158 +40,158 @@ const $e = Object.freeze({ kind: "anonymous" }), Wn = Object.freeze({
|
|
|
39
40
|
"video/webm",
|
|
40
41
|
"video/ogg",
|
|
41
42
|
"video/quicktime",
|
|
42
|
-
|
|
43
|
-
]),
|
|
43
|
+
dt
|
|
44
|
+
]), ft = "default-src 'none'; media-src 'self'; style-src 'unsafe-inline'", fr = `${ft}; sandbox`, mr = `${ft}; sandbox allow-same-origin`, Ce = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/u, pr = /^[\t\n\r ]+|[\t\n\r ]+$/gu, _e = /^[\t ]+|[\t ]+$/gu, wr = (e) => {
|
|
44
45
|
const t = [];
|
|
45
46
|
let r = "", n = 0;
|
|
46
47
|
for (; ; ) {
|
|
47
48
|
for (; n < e.length && e[n] !== '"' && e[n] !== ","; )
|
|
48
49
|
r += e[n], n += 1;
|
|
49
50
|
if (n < e.length && e[n] === '"') {
|
|
50
|
-
const
|
|
51
|
+
const a = n;
|
|
51
52
|
for (n += 1; n < e.length && e[n] !== '"'; )
|
|
52
53
|
n += e[n] === "\\" ? 2 : 1;
|
|
53
|
-
if (n = Math.min(n + 1, e.length), r += e.slice(
|
|
54
|
+
if (n = Math.min(n + 1, e.length), r += e.slice(a, n), n < e.length) continue;
|
|
54
55
|
}
|
|
55
56
|
if (t.push(r.replace(_e, "")), r = "", n >= e.length) return t;
|
|
56
57
|
n += 1;
|
|
57
58
|
}
|
|
58
|
-
},
|
|
59
|
-
const t = e.replace(
|
|
59
|
+
}, hr = (e) => {
|
|
60
|
+
const t = e.replace(pr, ""), r = t.indexOf("/");
|
|
60
61
|
if (r < 0) return null;
|
|
61
|
-
const n = t.slice(0, r),
|
|
62
|
-
return
|
|
63
|
-
},
|
|
62
|
+
const n = t.slice(0, r), a = t.indexOf(";", r + 1), o = t.slice(r + 1, a < 0 ? t.length : a).replace(/[\t\n\r ]+$/u, "");
|
|
63
|
+
return Ce.test(n) && Ce.test(o) ? `${n}/${o}`.toLowerCase() : null;
|
|
64
|
+
}, gr = (e) => {
|
|
64
65
|
let t = null;
|
|
65
|
-
for (const r of
|
|
66
|
-
const n =
|
|
66
|
+
for (const r of wr(e)) {
|
|
67
|
+
const n = hr(r);
|
|
67
68
|
n !== null && n !== "*/*" && (t = n);
|
|
68
69
|
}
|
|
69
70
|
return t;
|
|
70
|
-
},
|
|
71
|
+
}, Er = /['()*]/gu, yr = (e) => encodeURIComponent(e).replace(Er, (t) => `%${t.charCodeAt(0).toString(16).toUpperCase()}`), br = Object.freeze({
|
|
71
72
|
"Cache-Control": "private, max-age=60, stale-while-revalidate=600, no-transform",
|
|
72
73
|
Vary: "Cookie"
|
|
73
|
-
}),
|
|
74
|
-
const r =
|
|
74
|
+
}), Sr = (e, t) => {
|
|
75
|
+
const r = gr(e), n = r !== null && dr.has(r), a = n && (r.startsWith("audio/") || r.startsWith("video/"));
|
|
75
76
|
return {
|
|
76
|
-
contentType: r === null ?
|
|
77
|
+
contentType: r === null ? lr : r,
|
|
77
78
|
inline: n,
|
|
78
79
|
headers: {
|
|
79
80
|
"X-Content-Type-Options": "nosniff",
|
|
80
|
-
"Content-Disposition": `${n ? "inline" : "attachment"}; filename*=UTF-8''${
|
|
81
|
-
...r ===
|
|
81
|
+
"Content-Disposition": `${n ? "inline" : "attachment"}; filename*=UTF-8''${yr(t)}`,
|
|
82
|
+
...r === dt ? {} : { "Content-Security-Policy": a ? mr : fr },
|
|
82
83
|
"Cross-Origin-Resource-Policy": "same-origin"
|
|
83
84
|
}
|
|
84
85
|
};
|
|
85
|
-
},
|
|
86
|
+
}, Tr = /^bytes=(?:(\d+)-(\d*)|-(\d+))$/iu, fe = Object.freeze({ kind: "whole" }), De = Object.freeze({ kind: "unsatisfiable" }), xr = (e, t, r) => {
|
|
86
87
|
const n = e.get("Range");
|
|
87
88
|
if (n === null) return fe;
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
90
|
-
const o =
|
|
89
|
+
const a = e.get("If-Range");
|
|
90
|
+
if (a !== null && a !== r) return fe;
|
|
91
|
+
const o = Tr.exec(n);
|
|
91
92
|
if (o === null) return fe;
|
|
92
|
-
const [, c,
|
|
93
|
+
const [, c, f, g] = o;
|
|
93
94
|
if (g !== void 0) {
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
96
|
-
const _ = Math.max(0, t -
|
|
95
|
+
const p = Number(g);
|
|
96
|
+
if (p === 0) return De;
|
|
97
|
+
const _ = Math.max(0, t - p);
|
|
97
98
|
return { kind: "partial", range: { offset: _, length: t - _ } };
|
|
98
99
|
}
|
|
99
|
-
const T = Number(c),
|
|
100
|
-
return
|
|
101
|
-
},
|
|
100
|
+
const T = Number(c), d = f === "" ? Number.POSITIVE_INFINITY : Number(f);
|
|
101
|
+
return d < T ? fe : T >= t ? De : { kind: "partial", range: { offset: T, length: Math.min(d, t - 1) - T + 1 } };
|
|
102
|
+
}, He = "W/", Or = (e) => {
|
|
102
103
|
const t = [];
|
|
103
104
|
let r = 0, n = !1;
|
|
104
|
-
for (let
|
|
105
|
-
e[
|
|
105
|
+
for (let a = 0; a < e.length; a += 1)
|
|
106
|
+
e[a] === '"' ? n = !n : e[a] === "," && !n && (t.push(e.slice(r, a).replace(_e, "")), r = a + 1);
|
|
106
107
|
return t.push(e.slice(r).replace(_e, "")), t;
|
|
107
|
-
},
|
|
108
|
+
}, Be = (e) => e.startsWith(He) ? e.slice(He.length) : e, kr = (e, t) => {
|
|
108
109
|
if (e === null) return !1;
|
|
109
|
-
const r =
|
|
110
|
+
const r = Or(e);
|
|
110
111
|
if (r.length === 1 && r[0] === "*") return !0;
|
|
111
|
-
const n =
|
|
112
|
-
return r.some((
|
|
113
|
-
},
|
|
112
|
+
const n = Be(t);
|
|
113
|
+
return r.some((a) => Be(a) === n);
|
|
114
|
+
}, Rr = (e, t) => {
|
|
114
115
|
let r = t;
|
|
115
116
|
return e.pipeThrough(
|
|
116
117
|
new TransformStream({
|
|
117
|
-
transform: (n,
|
|
118
|
+
transform: (n, a) => {
|
|
118
119
|
const o = n.byteLength > r ? n.subarray(0, r) : n;
|
|
119
|
-
r -= o.byteLength,
|
|
120
|
+
r -= o.byteLength, a.enqueue(o), r === 0 && a.terminate();
|
|
120
121
|
},
|
|
121
122
|
flush: (n) => {
|
|
122
123
|
r > 0 && n.error(new RangeError("[markdown-explorer] the asset ended before its declared length"));
|
|
123
124
|
}
|
|
124
125
|
})
|
|
125
126
|
);
|
|
126
|
-
}, Se = async (e, t, r, n,
|
|
127
|
+
}, Se = async (e, t, r, n, a) => {
|
|
127
128
|
if (n !== null && r.set("Content-Length", String(n)), e.method.toUpperCase() === "HEAD") return new Response(null, { status: t, headers: r });
|
|
128
|
-
const o = await
|
|
129
|
-
return new Response(n === null ? o :
|
|
130
|
-
},
|
|
129
|
+
const o = await a();
|
|
130
|
+
return new Response(n === null ? o : Rr(o, n), { status: t, headers: r });
|
|
131
|
+
}, _r = (e, t) => {
|
|
131
132
|
const r = new Headers(e);
|
|
132
|
-
for (const [n,
|
|
133
|
+
for (const [n, a] of Object.entries({ "Accept-Ranges": "bytes", "Content-Range": `bytes */${t}`, "Cache-Control": "private, no-store", "X-Content-Type-Options": "nosniff" })) r.set(n, a);
|
|
133
134
|
return new Response(null, { status: 416, headers: r });
|
|
134
|
-
},
|
|
135
|
-
const n = `"${t.version}"`,
|
|
136
|
-
for (const [g, T] of Object.entries({ ...
|
|
137
|
-
if (
|
|
138
|
-
if (o.set("Content-Type",
|
|
135
|
+
}, Nr = async (e, t, r) => {
|
|
136
|
+
const n = `"${t.version}"`, a = Sr(t.contentType, t.name), o = new Headers(r);
|
|
137
|
+
for (const [g, T] of Object.entries({ ...a.headers, ...br, ETag: n })) o.set(g, T);
|
|
138
|
+
if (kr(e.headers.get("If-None-Match"), n)) return new Response(null, { status: 304, headers: o });
|
|
139
|
+
if (o.set("Content-Type", a.contentType), t.size === null) return Se(e, 200, o, null, () => t.open(null));
|
|
139
140
|
if (t.size === 0)
|
|
140
141
|
return o.set("Content-Length", "0"), new Response(null, { status: 200, headers: o });
|
|
141
142
|
o.set("Accept-Ranges", "bytes");
|
|
142
|
-
const c =
|
|
143
|
-
if (c.kind === "unsatisfiable") return
|
|
143
|
+
const c = xr(e.headers, t.size, n);
|
|
144
|
+
if (c.kind === "unsatisfiable") return _r(r, t.size);
|
|
144
145
|
if (c.kind === "whole") return Se(e, 200, o, t.size, () => t.open(null));
|
|
145
|
-
const { range:
|
|
146
|
-
return o.set("Content-Range", `bytes ${
|
|
146
|
+
const { range: f } = c;
|
|
147
|
+
return o.set("Content-Range", `bytes ${f.offset}-${f.offset + f.length - 1}/${t.size}`), Se(e, 206, o, f.length, () => t.open(f));
|
|
147
148
|
}, Te = (e, t) => {
|
|
148
149
|
const r = new AbortController(), n = t(AbortSignal.any([e.signal, r.signal]));
|
|
149
|
-
return new Promise((
|
|
150
|
+
return new Promise((a) => {
|
|
150
151
|
let o;
|
|
151
152
|
const c = setTimeout(() => {
|
|
152
|
-
|
|
153
|
-
}, e.timeoutMs),
|
|
153
|
+
a(e.late), o = setTimeout(() => r.abort(), e.handOverMs);
|
|
154
|
+
}, e.timeoutMs), f = () => {
|
|
154
155
|
clearTimeout(c), clearTimeout(o);
|
|
155
156
|
};
|
|
156
157
|
n.then(
|
|
157
158
|
(g) => {
|
|
158
|
-
|
|
159
|
+
f(), a(g);
|
|
159
160
|
},
|
|
160
161
|
() => {
|
|
161
|
-
|
|
162
|
+
f(), a(e.late);
|
|
162
163
|
}
|
|
163
164
|
);
|
|
164
165
|
});
|
|
165
|
-
},
|
|
166
|
+
}, Ar = Object.freeze({
|
|
166
167
|
error: (e, t) => {
|
|
167
168
|
console.error(e, t);
|
|
168
169
|
}
|
|
169
|
-
}),
|
|
170
|
+
}), mt = /* @__PURE__ */ Symbol.for("@aiquants/markdown-explorer:ExplorerError");
|
|
170
171
|
class Ee extends Error {
|
|
171
172
|
reason;
|
|
172
173
|
code;
|
|
173
174
|
constructor(t, r = {}) {
|
|
174
|
-
if (!
|
|
175
|
-
if (r.code !== void 0 && !
|
|
176
|
-
super(`[markdown-explorer] ${t}`, r.cause === void 0 ? void 0 : { cause: r.cause }), this.name = "ExplorerError", this.reason = t, this.code = r.code, Object.defineProperty(this,
|
|
175
|
+
if (!it(t)) throw new RangeError(`[markdown-explorer] ExplorerError needs a failure reason; got ${JSON.stringify(t)}`);
|
|
176
|
+
if (r.code !== void 0 && !_t(r.code)) throw new RangeError(`[markdown-explorer] an ExplorerError code must be 1 to 64 lowercase letters, digits or hyphens; got ${JSON.stringify(r.code)}`);
|
|
177
|
+
super(`[markdown-explorer] ${t}`, r.cause === void 0 ? void 0 : { cause: r.cause }), this.name = "ExplorerError", this.reason = t, this.code = r.code, Object.defineProperty(this, mt, { value: !0 });
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
|
-
const
|
|
180
|
+
const Ir = (e) => e instanceof Error && Object.hasOwn(e, mt) && it(e.reason), Mr = (e) => typeof e == "object" && e !== null && e.name === "AbortError", Ne = (e, t, r) => {
|
|
180
181
|
try {
|
|
181
182
|
e.error(t, r);
|
|
182
183
|
} catch (n) {
|
|
183
184
|
console.error("[markdown-explorer] the logger threw while reporting a failure", { operation: r.operation, error: n });
|
|
184
185
|
}
|
|
185
|
-
}, q = (e, t) => Ne(e, `[markdown-explorer] ${t.operation} failed`, t),
|
|
186
|
+
}, q = (e, t) => Ne(e, `[markdown-explorer] ${t.operation} failed`, t), vr = (e, t) => Ne(e, `[markdown-explorer] ${t.operation} left out an entry`, t), Pr = (e, t) => Ne(e, `[markdown-explorer] ${t.operation} refused a path the deny rules match`, { ...t, error: new RangeError("[markdown-explorer] the path matches a deny rule") }), Fe = (e, t) => {
|
|
186
187
|
e === null || typeof e != "object" && typeof e != "function" || Promise.resolve(e).then(void 0, t);
|
|
187
|
-
}, Q = (e, t, r, n) =>
|
|
188
|
+
}, Q = (e, t, r, n) => Ir(e) ? Re(e.reason, e.code) : (Mr(e) || r.aborted || q(n, { ...t, error: e }), { reason: "failed" }), Ue = (e, t) => {
|
|
188
189
|
if (!Number.isSafeInteger(t) || t <= 0) throw new RangeError(`[markdown-explorer] a cache ${e} must be a positive integer; got ${String(t)}`);
|
|
189
|
-
},
|
|
190
|
-
|
|
190
|
+
}, pt = (e, t) => {
|
|
191
|
+
Ue("capacity", e), t !== void 0 && Ue("weight budget", t.max);
|
|
191
192
|
const r = /* @__PURE__ */ new Map();
|
|
192
193
|
let n = 0;
|
|
193
|
-
const
|
|
194
|
+
const a = (o) => {
|
|
194
195
|
const c = r.get(o);
|
|
195
196
|
c !== void 0 && (r.delete(o), n -= c.weight);
|
|
196
197
|
};
|
|
@@ -201,415 +202,421 @@ const br = (e) => e instanceof Error && Object.hasOwn(e, it) && et(e.reason), Sr
|
|
|
201
202
|
return r.delete(o), r.set(o, c), c.value;
|
|
202
203
|
},
|
|
203
204
|
set: (o, c) => {
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
if (!(t !== void 0 &&
|
|
207
|
-
r.set(o, { value: c, weight:
|
|
205
|
+
a(o);
|
|
206
|
+
const f = t === void 0 ? 0 : t.of(o, c);
|
|
207
|
+
if (!(t !== void 0 && f > t.max)) {
|
|
208
|
+
r.set(o, { value: c, weight: f }), n += f;
|
|
208
209
|
for (const g of r.keys()) {
|
|
209
210
|
if (r.size <= e && (t === void 0 || n <= t.max)) break;
|
|
210
|
-
|
|
211
|
+
a(g);
|
|
211
212
|
}
|
|
212
213
|
}
|
|
213
214
|
},
|
|
214
|
-
delete:
|
|
215
|
+
delete: a,
|
|
215
216
|
size: () => r.size,
|
|
216
217
|
weight: () => n
|
|
217
218
|
};
|
|
218
|
-
},
|
|
219
|
+
}, wt = (e) => Math.max(1, e - 1), $r = 2147483647, Xe = Object.freeze({ status: "aborted" }), re = (e, t, r) => {
|
|
219
220
|
const n = (e.get(t) ?? 0) + r;
|
|
220
221
|
n === 0 ? e.delete(t) : e.set(t, n);
|
|
221
|
-
},
|
|
222
|
+
}, se = (e, t, r) => {
|
|
222
223
|
if (!Number.isSafeInteger(t) || t <= 0 || t > r) throw new RangeError(`[markdown-explorer] a worker pool's ${e} must be an integer in 1..${String(r)}; got ${String(t)}`);
|
|
223
|
-
},
|
|
224
|
-
|
|
225
|
-
const c = Object.freeze({ maxOldGenerationSizeMb: o.maxOldGenerationSizeMb, maxYoungGenerationSizeMb: o.maxYoungGenerationSizeMb }),
|
|
224
|
+
}, jr = (e) => e !== null && typeof e == "object" && e.ready === !0 && !("id" in e), Lr = (e, t) => e !== null && typeof e == "object" && e.id === t && "output" in e, zr = ({ url: e, size: t, deadlineMs: r, jobsPerScope: n, maxQueuedJobs: a, heapLimits: o }) => {
|
|
225
|
+
se("size", t, Number.MAX_SAFE_INTEGER), se("deadline", r, $r), se("jobsPerScope", n, Number.MAX_SAFE_INTEGER), se("maxQueuedJobs", a, Number.MAX_SAFE_INTEGER), se("maxOldGenerationSizeMb", o.maxOldGenerationSizeMb, Number.MAX_SAFE_INTEGER), se("maxYoungGenerationSizeMb", o.maxYoungGenerationSizeMb, Number.MAX_SAFE_INTEGER);
|
|
226
|
+
const c = Object.freeze({ maxOldGenerationSizeMb: o.maxOldGenerationSizeMb, maxYoungGenerationSizeMb: o.maxYoungGenerationSizeMb }), f = /* @__PURE__ */ new Set(), g = [], T = /* @__PURE__ */ new Map(), d = /* @__PURE__ */ new Map(), p = wt(t);
|
|
226
227
|
let _ = 0;
|
|
227
|
-
const R = (u,
|
|
228
|
+
const R = (u, i) => {
|
|
228
229
|
const { job: E } = u;
|
|
229
|
-
u.timer !== null && clearTimeout(u.timer), u.timer = null, u.job = null, u.sent = !1, E !== null && (
|
|
230
|
+
u.timer !== null && clearTimeout(u.timer), u.timer = null, u.job = null, u.sent = !1, E !== null && (re(T, E.scope, -1), re(d, E.scope, -1), E.settle(i));
|
|
230
231
|
}, I = (u) => {
|
|
231
|
-
const [
|
|
232
|
-
|
|
232
|
+
const [i] = g.splice(u, 1);
|
|
233
|
+
i?.detach();
|
|
233
234
|
}, O = () => {
|
|
234
|
-
let u = -1,
|
|
235
|
-
for (const [
|
|
236
|
-
const A = T.get(k.scope) ?? 0, D =
|
|
237
|
-
(A <
|
|
235
|
+
let u = -1, i = p, E = Number.POSITIVE_INFINITY;
|
|
236
|
+
for (const [S, k] of g.entries()) {
|
|
237
|
+
const A = T.get(k.scope) ?? 0, D = d.get(k.scope) ?? 0;
|
|
238
|
+
(A < i || A === i && u >= 0 && D < E) && (u = S, i = A, E = D);
|
|
238
239
|
}
|
|
239
240
|
return u;
|
|
240
|
-
}, x = (u,
|
|
241
|
-
|
|
242
|
-
}),
|
|
243
|
-
},
|
|
241
|
+
}, x = (u, i) => {
|
|
242
|
+
f.has(u) && (f.delete(u), R(u, i), u.worker.terminate().catch(() => {
|
|
243
|
+
}), z());
|
|
244
|
+
}, P = (u) => {
|
|
244
245
|
u.timer = setTimeout(() => {
|
|
245
246
|
x(u, { status: "timeout" });
|
|
246
247
|
}, r);
|
|
247
|
-
},
|
|
248
|
-
u.sent = !0,
|
|
248
|
+
}, b = (u, i) => u.sent ? { status: "crashed", error: i } : { status: "unavailable", error: i }, N = (u, i) => {
|
|
249
|
+
u.sent = !0, P(u);
|
|
249
250
|
try {
|
|
250
|
-
u.worker.postMessage({ id:
|
|
251
|
+
u.worker.postMessage({ id: i.id, input: i.input });
|
|
251
252
|
} catch (E) {
|
|
252
253
|
R(u, { status: "crashed", error: E }), u.worker.unref();
|
|
253
254
|
}
|
|
254
|
-
},
|
|
255
|
-
const u = { worker: new
|
|
255
|
+
}, C = () => {
|
|
256
|
+
const u = { worker: new sr(e, { resourceLimits: c }), job: null, sent: !1, timer: null, ready: !1 };
|
|
256
257
|
return u.worker.unref(), u.timer = setTimeout(() => {
|
|
257
258
|
x(u, { status: "unavailable", error: new Error(`[markdown-explorer] a worker did not start within ${String(r)} ms`) });
|
|
258
|
-
}, r), u.worker.on("message", (
|
|
259
|
-
if (!u.ready &&
|
|
260
|
-
u.ready = !0, u.timer !== null && clearTimeout(u.timer), u.timer = null, u.job === null ? u.worker.unref() : N(u, u.job), u.job === null &&
|
|
259
|
+
}, r), u.worker.on("message", (i) => {
|
|
260
|
+
if (!u.ready && jr(i)) {
|
|
261
|
+
u.ready = !0, u.timer !== null && clearTimeout(u.timer), u.timer = null, u.job === null ? u.worker.unref() : N(u, u.job), u.job === null && z();
|
|
261
262
|
return;
|
|
262
263
|
}
|
|
263
264
|
const { job: E } = u;
|
|
264
|
-
if (E === null || !
|
|
265
|
-
x(u,
|
|
265
|
+
if (E === null || !Lr(i, E.id)) {
|
|
266
|
+
x(u, b(u, new Error("[markdown-explorer] a worker sent a message that answers no job")));
|
|
266
267
|
return;
|
|
267
268
|
}
|
|
268
|
-
R(u, { status: "done", output:
|
|
269
|
-
}), u.worker.on("error", (
|
|
270
|
-
x(u,
|
|
271
|
-
}), u.worker.on("messageerror", (
|
|
272
|
-
x(u,
|
|
273
|
-
}), u.worker.on("exit", (
|
|
274
|
-
x(u,
|
|
275
|
-
}),
|
|
276
|
-
},
|
|
269
|
+
R(u, { status: "done", output: i.output }), u.worker.unref(), z();
|
|
270
|
+
}), u.worker.on("error", (i) => {
|
|
271
|
+
x(u, b(u, i));
|
|
272
|
+
}), u.worker.on("messageerror", (i) => {
|
|
273
|
+
x(u, b(u, i));
|
|
274
|
+
}), u.worker.on("exit", (i) => {
|
|
275
|
+
x(u, b(u, new Error(`[markdown-explorer] a worker exited with code ${String(i)}`)));
|
|
276
|
+
}), f.add(u), u;
|
|
277
|
+
}, z = () => {
|
|
277
278
|
for (; ; ) {
|
|
278
|
-
const u = O(),
|
|
279
|
-
if (
|
|
279
|
+
const u = O(), i = g[u];
|
|
280
|
+
if (i === void 0) return;
|
|
280
281
|
let E;
|
|
281
|
-
for (const k of
|
|
282
|
+
for (const k of f)
|
|
282
283
|
if (k.job === null) {
|
|
283
284
|
E = k;
|
|
284
285
|
break;
|
|
285
286
|
}
|
|
286
|
-
if (E === void 0 &&
|
|
287
|
+
if (E === void 0 && f.size < t)
|
|
287
288
|
try {
|
|
288
|
-
E =
|
|
289
|
+
E = C();
|
|
289
290
|
} catch (k) {
|
|
290
|
-
I(u),
|
|
291
|
+
I(u), re(d, i.scope, -1), i.settle({ status: "unavailable", error: k });
|
|
291
292
|
continue;
|
|
292
293
|
}
|
|
293
294
|
if (E === void 0) return;
|
|
294
|
-
I(u),
|
|
295
|
-
const
|
|
296
|
-
|
|
295
|
+
I(u), re(T, i.scope, 1);
|
|
296
|
+
const S = E;
|
|
297
|
+
S.job = i, S.worker.ref(), S.ready && N(S, i);
|
|
297
298
|
}
|
|
298
299
|
};
|
|
299
300
|
return {
|
|
300
|
-
run: (u, { scope:
|
|
301
|
+
run: (u, { scope: i, signal: E }) => new Promise((S) => {
|
|
301
302
|
if (E.aborted) {
|
|
302
|
-
|
|
303
|
+
S(Xe);
|
|
303
304
|
return;
|
|
304
305
|
}
|
|
305
|
-
if ((
|
|
306
|
-
|
|
306
|
+
if ((d.get(i) ?? 0) >= n) {
|
|
307
|
+
S({ status: "refused", limit: "scope" });
|
|
307
308
|
return;
|
|
308
309
|
}
|
|
309
310
|
_ += 1;
|
|
310
|
-
const k = { id: _, input: u, scope:
|
|
311
|
+
const k = { id: _, input: u, scope: i, settle: S, detach: () => {
|
|
311
312
|
} };
|
|
312
|
-
g.push(k),
|
|
313
|
+
g.push(k), re(d, i, 1), z();
|
|
313
314
|
const A = g.indexOf(k);
|
|
314
315
|
if (A < 0) return;
|
|
315
|
-
if (g.length >
|
|
316
|
-
I(A),
|
|
316
|
+
if (g.length > a) {
|
|
317
|
+
I(A), re(d, i, -1), S({ status: "refused", limit: "queue" });
|
|
317
318
|
return;
|
|
318
319
|
}
|
|
319
320
|
const D = () => {
|
|
320
321
|
const M = g.indexOf(k);
|
|
321
|
-
M < 0 || (I(M),
|
|
322
|
+
M < 0 || (I(M), re(d, i, -1), S(Xe));
|
|
322
323
|
};
|
|
323
324
|
E.addEventListener("abort", D, { once: !0 }), k.detach = () => {
|
|
324
325
|
E.removeEventListener("abort", D);
|
|
325
326
|
};
|
|
326
327
|
}),
|
|
327
|
-
workers: () =>
|
|
328
|
+
workers: () => f.size
|
|
328
329
|
};
|
|
329
|
-
}, Ae = 1024 * 1024,
|
|
330
|
+
}, Ae = 1024 * 1024, Cr = 3e4, Dr = 4 * Ae, Hr = 64 * Ae, Br = 3, Fr = 2, Ur = (e, t) => e + wt(t) + Br, Xr = 4, Gr = 32, Jr = 512, Wr = 64, Yr = 4096, Kr = 32, Ge = 4096, Vr = 4, ht = ({ workerHeapMb: e, maxHtmlBytes: t }) => Math.floor(((e - Wr) * Ae - t * Kr) / Yr), Zr = ({ maxHtmlBytes: e }) => e, Qr = ({ maxHtmlBytes: e }) => Vr * e, qr = 1024, en = 2147483647, tn = 64, rn = 2 ** 29, nn = 1024 * 1024, on = 64, Je = Object.freeze({ status: "rejected", reason: "too-large", cause: null }), xe = Object.freeze({ status: "rejected", reason: "failed", cause: null }), ne = (e, t, r, n) => {
|
|
330
331
|
if (t === void 0) return r;
|
|
331
332
|
if (typeof t != "number") throw new TypeError(`[markdown-explorer] sanitizer.${e} must be a number; got ${typeof t}`);
|
|
332
333
|
if (!Number.isSafeInteger(t) || t <= 0 || t > n) throw new RangeError(`[markdown-explorer] sanitizer.${e} must be an integer in 1..${String(n)}; got ${String(t)}`);
|
|
333
334
|
return t;
|
|
334
|
-
},
|
|
335
|
+
}, sn = (e, t) => {
|
|
335
336
|
if (e !== void 0 && (e === null || typeof e != "object")) throw new TypeError("[markdown-explorer] sanitizer must be an object of { workers, deadlineMs, maxHtmlBytes, cacheBytes, workerHeapMb, jobsPerScope, maxQueuedJobs }");
|
|
336
|
-
const { workers: r, deadlineMs: n, maxHtmlBytes:
|
|
337
|
+
const { workers: r, deadlineMs: n, maxHtmlBytes: a, cacheBytes: o, workerHeapMb: c, jobsPerScope: f, maxQueuedJobs: g } = e ?? {}, T = ne("workers", r, Fr, tn), d = ne("jobsPerScope", f, Ur(t, T), Number.MAX_SAFE_INTEGER), p = Object.freeze({
|
|
337
338
|
workers: T,
|
|
338
|
-
deadlineMs:
|
|
339
|
-
maxHtmlBytes:
|
|
340
|
-
cacheBytes:
|
|
341
|
-
workerHeapMb:
|
|
342
|
-
jobsPerScope:
|
|
343
|
-
maxQueuedJobs:
|
|
339
|
+
deadlineMs: ne("deadlineMs", n, Cr, en),
|
|
340
|
+
maxHtmlBytes: ne("maxHtmlBytes", a, Dr, rn),
|
|
341
|
+
cacheBytes: ne("cacheBytes", o, Hr, Number.MAX_SAFE_INTEGER),
|
|
342
|
+
workerHeapMb: ne("workerHeapMb", c, Jr, nn),
|
|
343
|
+
jobsPerScope: d,
|
|
344
|
+
maxQueuedJobs: ne("maxQueuedJobs", g, Xr * d, Number.MAX_SAFE_INTEGER)
|
|
344
345
|
});
|
|
345
|
-
if (
|
|
346
|
-
throw new RangeError(`[markdown-explorer] sanitizer.jobsPerScope must be at least multiView.maxPanels (${String(t)}), because a page sends the document of every panel to the sanitizer at once for one reader; got ${String(
|
|
347
|
-
if (
|
|
348
|
-
throw new RangeError(`[markdown-explorer] sanitizer.maxQueuedJobs must be at least multiView.maxPanels (${String(t)}), because a reader whose page finds every worker busy waits with the document of every panel; got ${String(
|
|
349
|
-
if (
|
|
346
|
+
if (p.jobsPerScope < t)
|
|
347
|
+
throw new RangeError(`[markdown-explorer] sanitizer.jobsPerScope must be at least multiView.maxPanels (${String(t)}), because a page sends the document of every panel to the sanitizer at once for one reader; got ${String(p.jobsPerScope)}`);
|
|
348
|
+
if (p.maxQueuedJobs < t)
|
|
349
|
+
throw new RangeError(`[markdown-explorer] sanitizer.maxQueuedJobs must be at least multiView.maxPanels (${String(t)}), because a reader whose page finds every worker busy waits with the document of every panel; got ${String(p.maxQueuedJobs)}`);
|
|
350
|
+
if (ht(p) < Ge)
|
|
350
351
|
throw new RangeError(
|
|
351
|
-
`[markdown-explorer] sanitizer.workerHeapMb (${String(
|
|
352
|
+
`[markdown-explorer] sanitizer.workerHeapMb (${String(p.workerHeapMb)} MiB) must leave room for ${String(Ge)} parsed nodes beside HTML of sanitizer.maxHtmlBytes (${String(p.maxHtmlBytes)} bytes); raise workerHeapMb or lower maxHtmlBytes`
|
|
352
353
|
);
|
|
353
|
-
return
|
|
354
|
-
},
|
|
355
|
-
const e = import.meta.url, t = new URL(`./sanitizeWorker${
|
|
356
|
-
if (!
|
|
354
|
+
return p;
|
|
355
|
+
}, an = () => {
|
|
356
|
+
const e = import.meta.url, t = new URL(`./sanitizeWorker${rr(be(e))}`, e);
|
|
357
|
+
if (!qt(be(t))) throw new Error(`[markdown-explorer] the HTML sanitizer worker is missing: ${be(t)}`);
|
|
357
358
|
return t;
|
|
358
|
-
},
|
|
359
|
+
}, We = /* @__PURE__ */ new Map(), cn = (e, t) => {
|
|
359
360
|
const r = [e.href, t.workers, t.deadlineMs, t.workerHeapMb, t.jobsPerScope, t.maxQueuedJobs].map(String).join("\0");
|
|
360
|
-
let n =
|
|
361
|
-
return n === void 0 && (n =
|
|
361
|
+
let n = We.get(r);
|
|
362
|
+
return n === void 0 && (n = zr({
|
|
362
363
|
url: e,
|
|
363
364
|
size: t.workers,
|
|
364
365
|
deadlineMs: t.deadlineMs,
|
|
365
366
|
jobsPerScope: t.jobsPerScope,
|
|
366
367
|
maxQueuedJobs: t.maxQueuedJobs,
|
|
367
|
-
heapLimits: { maxOldGenerationSizeMb: t.workerHeapMb, maxYoungGenerationSizeMb:
|
|
368
|
-
}),
|
|
369
|
-
},
|
|
368
|
+
heapLimits: { maxOldGenerationSizeMb: t.workerHeapMb, maxYoungGenerationSizeMb: Gr }
|
|
369
|
+
}), We.set(r, n)), n;
|
|
370
|
+
}, un = (e) => {
|
|
370
371
|
if (e === null || typeof e != "object") return !1;
|
|
371
372
|
const { status: t, html: r, message: n } = e;
|
|
372
373
|
return t === "sanitized" && typeof r == "string" || t === "too-large" || t === "error" && typeof n == "string";
|
|
373
|
-
},
|
|
374
|
-
const
|
|
374
|
+
}, ln = (e) => e instanceof Error && e.code === "ERR_WORKER_OUT_OF_MEMORY", dn = ({ iframeHosts: e, dataPath: t, options: r, pool: n }) => {
|
|
375
|
+
const a = Object.freeze([...e]), o = ht(r), c = Zr(r), f = Qr(r), g = pt(qr, {
|
|
375
376
|
max: r.cacheBytes,
|
|
376
|
-
of: (O, x) => 2 * O.length + (x.status === "sanitized" ? 2 * x.html.length :
|
|
377
|
-
}), T = /* @__PURE__ */ new Map(),
|
|
377
|
+
of: (O, x) => 2 * O.length + (x.status === "sanitized" ? 2 * x.html.length : on)
|
|
378
|
+
}), T = /* @__PURE__ */ new Map(), d = /* @__PURE__ */ new Map(), p = (O) => {
|
|
378
379
|
T.get(O.key) === O && T.delete(O.key);
|
|
379
|
-
const x =
|
|
380
|
-
x?.delete(O), x?.size === 0 &&
|
|
381
|
-
}, _ = async (O, x,
|
|
382
|
-
const
|
|
383
|
-
if (
|
|
384
|
-
const
|
|
385
|
-
return { outcome: { status: "rejected", reason: "failed", cause: new Error(`[markdown-explorer] the HTML sanitizer refused a document: ${
|
|
380
|
+
const x = d.get(O.hash);
|
|
381
|
+
x?.delete(O), x?.size === 0 && d.delete(O.hash);
|
|
382
|
+
}, _ = async (O, x, P) => {
|
|
383
|
+
const b = await n.run({ html: O, iframeHosts: a, dataPath: t, maxNodes: o, maxAttributeChars: c, maxSanitizedChars: f }, { scope: x, signal: P });
|
|
384
|
+
if (b.status === "refused") {
|
|
385
|
+
const C = b.limit === "scope" ? `its reader already has ${String(r.jobsPerScope)} documents being sanitized or waiting` : `${String(r.maxQueuedJobs)} documents are already waiting`;
|
|
386
|
+
return { outcome: { status: "rejected", reason: "failed", cause: new Error(`[markdown-explorer] the HTML sanitizer refused a document: ${C}`) }, lasting: !1 };
|
|
386
387
|
}
|
|
387
|
-
if (
|
|
388
|
-
if (
|
|
389
|
-
if (
|
|
390
|
-
if (
|
|
391
|
-
return { outcome: { status: "rejected", reason: "too-large", cause: new Error(`[markdown-explorer] the HTML sanitizer ran out of its ${String(r.workerHeapMb)} MiB worker heap`, { cause:
|
|
392
|
-
if (
|
|
393
|
-
const { output: N } =
|
|
394
|
-
return
|
|
395
|
-
}, R = (O, x,
|
|
388
|
+
if (b.status === "aborted") return { outcome: xe, lasting: !1 };
|
|
389
|
+
if (b.status === "timeout") return { outcome: { status: "rejected", reason: "too-large", cause: new Error(`[markdown-explorer] the HTML sanitizer did not finish within ${String(r.deadlineMs)} ms`) }, lasting: !0 };
|
|
390
|
+
if (b.status === "unavailable") return { outcome: { status: "rejected", reason: "failed", cause: new Error("[markdown-explorer] the HTML sanitizer worker could not start", { cause: b.error }) }, lasting: !1 };
|
|
391
|
+
if (b.status === "crashed" && ln(b.error))
|
|
392
|
+
return { outcome: { status: "rejected", reason: "too-large", cause: new Error(`[markdown-explorer] the HTML sanitizer ran out of its ${String(r.workerHeapMb)} MiB worker heap`, { cause: b.error }) }, lasting: !0 };
|
|
393
|
+
if (b.status === "crashed") return { outcome: { status: "rejected", reason: "failed", cause: new Error("[markdown-explorer] the HTML sanitizer worker failed", { cause: b.error }) }, lasting: !0 };
|
|
394
|
+
const { output: N } = b;
|
|
395
|
+
return un(N) ? N.status === "too-large" ? { outcome: Je, lasting: !0 } : N.status === "error" ? { outcome: { status: "rejected", reason: "failed", cause: new Error(`[markdown-explorer] the HTML sanitizer failed: ${N.message}`) }, lasting: !0 } : N.html.length > f ? { outcome: { status: "rejected", reason: "too-large", cause: new Error(`[markdown-explorer] the HTML sanitizer's output of ${String(N.html.length)} characters exceeds its limit of ${String(f)}`) }, lasting: !0 } : { outcome: { status: "sanitized", html: N.html }, lasting: !0 } : { outcome: { status: "rejected", reason: "failed", cause: new Error("[markdown-explorer] the HTML sanitizer worker answered something that is not an outcome") }, lasting: !0 };
|
|
396
|
+
}, R = (O, x, P, b) => {
|
|
396
397
|
let N = () => {
|
|
397
398
|
};
|
|
398
|
-
const
|
|
399
|
-
N =
|
|
400
|
-
}),
|
|
401
|
-
T.set(
|
|
402
|
-
const u =
|
|
403
|
-
return u === void 0 ?
|
|
404
|
-
if (
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
for (const A of
|
|
408
|
-
|
|
409
|
-
}),
|
|
410
|
-
}, I = (O, x) => new Promise((
|
|
399
|
+
const C = new Promise((i) => {
|
|
400
|
+
N = i;
|
|
401
|
+
}), z = { hash: x, key: P, outcome: C, answer: N, controller: new AbortController(), waiters: 0 };
|
|
402
|
+
T.set(P, z);
|
|
403
|
+
const u = d.get(x);
|
|
404
|
+
return u === void 0 ? d.set(x, /* @__PURE__ */ new Set([z])) : u.add(z), _(O, b, z.controller.signal).catch((i) => ({ outcome: { status: "rejected", reason: "failed", cause: i instanceof Error ? i : new Error(String(i)) }, lasting: !0 })).then(({ outcome: i, lasting: E }) => {
|
|
405
|
+
if (p(z), z.answer(i), !E) return;
|
|
406
|
+
const S = g.get(x), k = S ?? (i.status === "rejected" ? { ...i, cause: null } : i);
|
|
407
|
+
S === void 0 && g.set(x, k);
|
|
408
|
+
for (const A of d.get(x) ?? [])
|
|
409
|
+
p(A), A.answer(k), A.controller.abort();
|
|
410
|
+
}), z;
|
|
411
|
+
}, I = (O, x) => new Promise((P) => {
|
|
411
412
|
O.waiters += 1;
|
|
412
|
-
const
|
|
413
|
-
O.waiters -= 1,
|
|
413
|
+
const b = () => {
|
|
414
|
+
O.waiters -= 1, P(xe), !(O.waiters > 0) && (p(O), O.controller.abort());
|
|
414
415
|
};
|
|
415
|
-
x.addEventListener("abort",
|
|
416
|
-
x.removeEventListener("abort",
|
|
416
|
+
x.addEventListener("abort", b, { once: !0 }), O.outcome.then((N) => {
|
|
417
|
+
x.removeEventListener("abort", b), P(N);
|
|
417
418
|
});
|
|
418
419
|
});
|
|
419
|
-
return (O, { scope: x, signal:
|
|
420
|
-
if (O.length > r.maxHtmlBytes || Buffer.byteLength(O, "utf8") > r.maxHtmlBytes) return Promise.resolve(
|
|
421
|
-
const
|
|
420
|
+
return (O, { scope: x, signal: P }) => {
|
|
421
|
+
if (O.length > r.maxHtmlBytes || Buffer.byteLength(O, "utf8") > r.maxHtmlBytes) return Promise.resolve(Je);
|
|
422
|
+
const b = Ht("sha256").update(O, "utf8").digest("base64"), N = g.get(b);
|
|
422
423
|
if (N !== void 0) return Promise.resolve(N);
|
|
423
|
-
if (
|
|
424
|
-
const
|
|
425
|
-
return I(
|
|
424
|
+
if (P.aborted) return Promise.resolve(xe);
|
|
425
|
+
const C = `${b}\0${x}`, z = T.get(C);
|
|
426
|
+
return I(z === void 0 ? R(O, b, C, x) : z, P);
|
|
426
427
|
};
|
|
427
|
-
}, ye = (e) => ({ result: e, json: JSON.stringify(e) }), Oe = Object.freeze(ye(Object.freeze({ status: "error", failure: Object.freeze({ reason: "failed" }) }))),
|
|
428
|
-
const
|
|
429
|
-
const _ = new AbortController(), R = { waiters: 0, superseded: !1, started: !1, loadTree:
|
|
430
|
-
const u = ye(
|
|
431
|
-
return !(R.superseded || _.signal.aborted) &&
|
|
432
|
-
},
|
|
428
|
+
}, ye = (e) => ({ result: e, json: JSON.stringify(e) }), Oe = Object.freeze(ye(Object.freeze({ status: "error", failure: Object.freeze({ reason: "failed" }) }))), fn = (e) => e.status === "ok" && e.entries.length > 0, mn = ({ ttlMs: e, maxEntries: t, maxBytes: r, now: n }) => {
|
|
429
|
+
const a = pt(t, { max: r, of: (d, p) => 2 * p.tree.json.length }), o = /* @__PURE__ */ new Map(), c = /* @__PURE__ */ new Map(), f = (d, p) => {
|
|
430
|
+
const _ = new AbortController(), R = { waiters: 0, superseded: !1, started: !1, loadTree: p }, I = c.get(d), O = (C, z) => {
|
|
431
|
+
const u = ye(C);
|
|
432
|
+
return !(R.superseded || _.signal.aborted) && fn(C) && a.set(d, { tree: u, loadedAt: z }), u;
|
|
433
|
+
}, P = (async () => {
|
|
433
434
|
if (I !== void 0 && await I, _.signal.aborted) return Oe;
|
|
434
|
-
const
|
|
435
|
-
return R.started = !0, O(await R.loadTree(_.signal,
|
|
435
|
+
const C = n();
|
|
436
|
+
return R.started = !0, O(await R.loadTree(_.signal, C), C);
|
|
436
437
|
})().finally(() => {
|
|
437
|
-
o.get(
|
|
438
|
-
}),
|
|
439
|
-
o.set(
|
|
440
|
-
const N =
|
|
438
|
+
o.get(d) === b && o.delete(d);
|
|
439
|
+
}), b = { promise: P, controller: _, state: R };
|
|
440
|
+
o.set(d, b);
|
|
441
|
+
const N = P.then(
|
|
441
442
|
() => {
|
|
442
443
|
},
|
|
443
444
|
() => {
|
|
444
445
|
}
|
|
445
446
|
);
|
|
446
|
-
return c.set(
|
|
447
|
-
c.get(
|
|
448
|
-
}),
|
|
449
|
-
}, g = (
|
|
450
|
-
|
|
447
|
+
return c.set(d, N), N.then(() => {
|
|
448
|
+
c.get(d) === N && c.delete(d);
|
|
449
|
+
}), b;
|
|
450
|
+
}, g = (d, p, _) => {
|
|
451
|
+
p.state.waiters += 1;
|
|
451
452
|
const R = () => {
|
|
452
|
-
|
|
453
|
+
p.state.waiters -= 1, !(p.state.waiters > 0) && (o.get(d) === p && o.delete(d), p.controller.abort());
|
|
453
454
|
};
|
|
454
|
-
return _.addEventListener("abort", R, { once: !0 }),
|
|
455
|
+
return _.addEventListener("abort", R, { once: !0 }), p.promise.finally(() => {
|
|
455
456
|
_.removeEventListener("abort", R);
|
|
456
457
|
});
|
|
457
|
-
}, T = (
|
|
458
|
-
|
|
459
|
-
const
|
|
460
|
-
|
|
458
|
+
}, T = (d) => {
|
|
459
|
+
a.delete(d);
|
|
460
|
+
const p = o.get(d);
|
|
461
|
+
p?.state.started && (p.state.superseded = !0, o.delete(d));
|
|
461
462
|
};
|
|
462
463
|
return {
|
|
463
|
-
load: (
|
|
464
|
-
if (
|
|
465
|
-
const R =
|
|
464
|
+
load: (d, p, _) => {
|
|
465
|
+
if (p.aborted) return Promise.resolve(Oe);
|
|
466
|
+
const R = a.get(d);
|
|
466
467
|
if (R !== void 0) {
|
|
467
468
|
if (n() - R.loadedAt < e) return Promise.resolve(R.tree);
|
|
468
|
-
|
|
469
|
+
a.delete(d);
|
|
469
470
|
}
|
|
470
|
-
const I = o.get(
|
|
471
|
-
return g(
|
|
471
|
+
const I = o.get(d);
|
|
472
|
+
return g(d, I === void 0 ? f(d, _) : I, p);
|
|
472
473
|
},
|
|
473
|
-
refresh: (
|
|
474
|
-
if (
|
|
475
|
-
T(
|
|
476
|
-
const R = o.get(
|
|
477
|
-
return R === void 0 ? g(
|
|
474
|
+
refresh: (d, p, _) => {
|
|
475
|
+
if (p.aborted) return Promise.resolve(Oe);
|
|
476
|
+
T(d);
|
|
477
|
+
const R = o.get(d);
|
|
478
|
+
return R === void 0 ? g(d, f(d, _), p) : (R.state.loadTree = _, g(d, R, p));
|
|
478
479
|
},
|
|
479
480
|
forget: T
|
|
480
481
|
};
|
|
481
|
-
},
|
|
482
|
+
}, pn = (e) => ({
|
|
482
483
|
load: (t, r, n) => n(r, e()).then(ye),
|
|
483
484
|
refresh: (t, r, n) => n(r, e()).then(ye),
|
|
484
485
|
forget: () => {
|
|
485
486
|
}
|
|
486
|
-
}),
|
|
487
|
+
}), v = (e) => {
|
|
487
488
|
throw new TypeError(`[markdown-explorer] ${e}`);
|
|
488
|
-
},
|
|
489
|
+
}, wn = (e) => {
|
|
489
490
|
if (e === null || typeof e != "object") return null;
|
|
490
491
|
const { name: t, path: r } = e;
|
|
491
492
|
return typeof t != "string" || typeof r != "string" ? null : t === "" ? { path: r, reason: "an empty name" } : te(r) ? null : { path: r, reason: "a path the browser cannot address (empty, too long, a lone surrogate, a control character or a backslash)" };
|
|
492
|
-
},
|
|
493
|
+
}, hn = (e, t, r, n, a) => {
|
|
493
494
|
const o = /* @__PURE__ */ new Set(), c = (g) => {
|
|
494
|
-
if (!Array.isArray(g)) return
|
|
495
|
+
if (!Array.isArray(g)) return v("a source returned entries that are not an array");
|
|
495
496
|
const T = [];
|
|
496
|
-
for (const
|
|
497
|
-
const
|
|
498
|
-
|
|
497
|
+
for (const d of g) {
|
|
498
|
+
const p = f(d);
|
|
499
|
+
p !== null && T.push(p);
|
|
499
500
|
}
|
|
500
501
|
return T;
|
|
501
|
-
},
|
|
502
|
-
const T =
|
|
502
|
+
}, f = (g) => {
|
|
503
|
+
const T = wn(g);
|
|
503
504
|
if (T !== null)
|
|
504
505
|
return n(T), null;
|
|
505
|
-
const
|
|
506
|
-
if (
|
|
507
|
-
const
|
|
508
|
-
if (o.has(
|
|
509
|
-
const { name: _, path: R, type: I, mimeType: O, children: x, lazy:
|
|
506
|
+
const d = Pt(g);
|
|
507
|
+
if (d !== null) return v(d);
|
|
508
|
+
const p = g;
|
|
509
|
+
if (o.has(p)) return v("a source returned entries that contain themselves");
|
|
510
|
+
const { name: _, path: R, type: I, mimeType: O, children: x, lazy: P } = p;
|
|
510
511
|
if (t.deniesName(_) || t.deniesPath(R)) return null;
|
|
511
|
-
if (!
|
|
512
|
+
if (!a(R))
|
|
512
513
|
return n({ path: R, reason: "a path outside the source's boundary" }), null;
|
|
513
|
-
const
|
|
514
|
-
if (I === "file") return { ...
|
|
515
|
-
if (
|
|
516
|
-
return r ? { ...
|
|
517
|
-
if (x === void 0) return { ...
|
|
518
|
-
o.add(
|
|
514
|
+
const b = O === void 0 ? { name: _, path: R } : { name: _, path: R, mimeType: O };
|
|
515
|
+
if (I === "file") return { ...b, type: I };
|
|
516
|
+
if (P === !0)
|
|
517
|
+
return r ? { ...b, type: I, lazy: !0, children: [] } : v("a lazy directory came from a source without a children function");
|
|
518
|
+
if (x === void 0) return { ...b, type: I };
|
|
519
|
+
o.add(p);
|
|
519
520
|
const N = c(x);
|
|
520
|
-
return o.delete(
|
|
521
|
+
return o.delete(p), { ...b, type: I, children: N };
|
|
521
522
|
};
|
|
522
523
|
return c(e);
|
|
523
|
-
},
|
|
524
|
+
}, gn = /^[\x21\x23-\x7E]{1,256}$/u, gt = (e) => typeof e == "string" && gn.test(e), En = /* @__PURE__ */ new Set(["text", "image", "media", "file"]), yn = (e) => typeof e == "string" && En.has(e), bn = (e, t) => {
|
|
524
525
|
if (e !== null && typeof e == "object") {
|
|
525
|
-
const
|
|
526
|
-
if (
|
|
527
|
-
if (Object.hasOwn(
|
|
528
|
-
if (typeof
|
|
529
|
-
if (!
|
|
530
|
-
const c = t(
|
|
526
|
+
const a = e, { kind: o } = a;
|
|
527
|
+
if (yn(o)) {
|
|
528
|
+
if (Object.hasOwn(a, "url")) return v(`a ${o} document carries its version, not a url`);
|
|
529
|
+
if (typeof a.name != "string") return v("a document needs a name string");
|
|
530
|
+
if (!gt(a.version)) return v(`a ${o} document needs a version of 1 to 256 visible ASCII characters other than '"'`);
|
|
531
|
+
const c = t(a.version);
|
|
531
532
|
switch (o) {
|
|
532
533
|
case "image":
|
|
533
534
|
case "file":
|
|
534
|
-
return { kind: o, name:
|
|
535
|
+
return { kind: o, name: a.name, url: c };
|
|
535
536
|
case "media": {
|
|
536
|
-
const { mediaType:
|
|
537
|
-
return
|
|
537
|
+
const { mediaType: f } = a;
|
|
538
|
+
return It(f) ? { kind: o, name: a.name, url: c, mediaType: f } : v(`unknown media type ${typeof f == "string" ? JSON.stringify(f.slice(0, 64)) : typeof f}`);
|
|
538
539
|
}
|
|
539
540
|
case "text":
|
|
540
|
-
return typeof
|
|
541
|
+
return typeof a.text != "string" || typeof a.truncated != "boolean" ? v("a text document needs text and truncated") : { kind: o, name: a.name, text: a.text, truncated: a.truncated, url: c };
|
|
541
542
|
}
|
|
542
543
|
}
|
|
543
544
|
}
|
|
544
|
-
const r =
|
|
545
|
-
if (r !== null) return
|
|
545
|
+
const r = Mt(e);
|
|
546
|
+
if (r !== null) return v(r);
|
|
546
547
|
const n = e;
|
|
547
548
|
switch (n.kind) {
|
|
548
549
|
case "failure": {
|
|
549
|
-
const { reason:
|
|
550
|
-
return { kind: "failure", failure: Re(
|
|
550
|
+
const { reason: a, code: o } = n.failure;
|
|
551
|
+
return { kind: "failure", failure: Re(a, o) };
|
|
551
552
|
}
|
|
552
553
|
case "markdown":
|
|
553
|
-
return n.headings.length >
|
|
554
|
+
return n.headings.length > vt ? { kind: "failure", failure: Re("too-large") } : { kind: "unsanitized-markdown", name: n.name, html: n.htmlContent, headings: n.headings.map(({ text: a, depth: o, id: c }) => ({ text: a, depth: o, id: c })) };
|
|
554
555
|
}
|
|
555
|
-
},
|
|
556
|
-
if (e === null || typeof e != "object") return
|
|
557
|
-
const { name: t, contentType: r, size: n, version:
|
|
558
|
-
return typeof t != "string" || t === "" ?
|
|
559
|
-
},
|
|
560
|
-
if (t === null || typeof t != "object") return
|
|
561
|
-
const { id: r, tone: n, title:
|
|
562
|
-
if (typeof r != "string" || r === "" || typeof
|
|
563
|
-
if (!
|
|
564
|
-
if (o !== void 0 && typeof o != "string") return
|
|
565
|
-
const g = { id: r, tone: n, title:
|
|
556
|
+
}, Sn = (e) => {
|
|
557
|
+
if (e === null || typeof e != "object") return v("an asset file must be an object");
|
|
558
|
+
const { name: t, contentType: r, size: n, version: a, open: o } = e;
|
|
559
|
+
return typeof t != "string" || t === "" ? v("an asset file needs a non-empty name") : typeof r != "string" ? v("an asset file needs a contentType string ('' when unknown)") : n !== null && !(typeof n == "number" && Number.isSafeInteger(n) && n >= 0) ? v("an asset file's size must be null or a non-negative safe integer") : gt(a) ? typeof o != "function" ? v("an asset file needs an open function") : Object.freeze({ name: t, contentType: r, size: n, version: a, open: o }) : v(`an asset file needs a version of 1 to 256 visible ASCII characters other than '"'`);
|
|
560
|
+
}, Tn = (e) => Array.isArray(e) ? e.map((t) => {
|
|
561
|
+
if (t === null || typeof t != "object") return v("a notice must be an object");
|
|
562
|
+
const { id: r, tone: n, title: a, body: o, action: c } = t;
|
|
563
|
+
if (typeof r != "string" || r === "" || typeof a != "string") return v("a notice needs an id and a title");
|
|
564
|
+
if (!Nt(n)) return v(`unknown notice tone ${typeof n == "string" ? JSON.stringify(n.slice(0, 64)) : typeof n}`);
|
|
565
|
+
if (o !== void 0 && typeof o != "string") return v("a notice body must be a string");
|
|
566
|
+
const g = { id: r, tone: n, title: a, ...o === void 0 ? {} : { body: o } };
|
|
566
567
|
if (c === void 0) return g;
|
|
567
|
-
if (c === null || typeof c != "object") return
|
|
568
|
-
const { label: T, href:
|
|
569
|
-
return typeof T != "string" || !
|
|
570
|
-
}) :
|
|
568
|
+
if (c === null || typeof c != "object") return v("a notice action must be an object");
|
|
569
|
+
const { label: T, href: d } = c;
|
|
570
|
+
return typeof T != "string" || !At(d, $t) ? v("a notice action needs a label and an http(s), mailto or relative href") : { ...g, action: { label: T, href: d } };
|
|
571
|
+
}) : v("notices must be an array"), xn = 4e3, me = Object.freeze({ ttlMs: 3e5, maxEntries: 64, maxBytes: 64 * 1024 * 1024 }), Ye = 32, Et = "private, no-store", On = Object.freeze({
|
|
571
572
|
"Content-Type": "application/json; charset=utf-8",
|
|
572
|
-
"Cache-Control":
|
|
573
|
+
"Cache-Control": Et,
|
|
573
574
|
"X-Content-Type-Options": "nosniff",
|
|
574
575
|
"Content-Security-Policy": "default-src 'none'; frame-ancestors 'none'; sandbox",
|
|
575
576
|
"Cross-Origin-Resource-Policy": "same-origin"
|
|
576
|
-
}),
|
|
577
|
+
}), kn = 22, Rn = 2147483647, ke = Object.freeze([]), Ke = Object.freeze(["contains", "confirm"]), Ve = Object.freeze({ readOnly: !1 }), _n = Object.freeze({ readOnly: !0 }), G = (e, t, r) => {
|
|
577
578
|
if (!(r && t === void 0) && typeof t != "function")
|
|
578
579
|
throw new TypeError(`[markdown-explorer] ${e} must be a function; got ${typeof t}`);
|
|
579
580
|
}, ge = (e, t, r) => {
|
|
580
581
|
if (typeof t != "number") throw new TypeError(`[markdown-explorer] ${e} must be a number; got ${typeof t}`);
|
|
581
582
|
if (!Number.isSafeInteger(t) || t <= 0 || t > r) throw new RangeError(`[markdown-explorer] ${e} must be an integer in 1..${String(r)}; got ${String(t)}`);
|
|
582
583
|
return t;
|
|
583
|
-
},
|
|
584
|
+
}, Nn = (e) => {
|
|
584
585
|
if (e === null || typeof e != "object" || !Object.isFrozen(e)) throw new TypeError("[markdown-explorer] config must be the value returned by defineExplorerConfig");
|
|
585
|
-
return
|
|
586
|
-
},
|
|
586
|
+
return Jt(e);
|
|
587
|
+
}, Ze = (e) => {
|
|
588
|
+
try {
|
|
589
|
+
return new Headers(e);
|
|
590
|
+
} catch {
|
|
591
|
+
throw new TypeError("[markdown-explorer] authenticate returned headers that are not a valid headers init (their values are not logged)");
|
|
592
|
+
}
|
|
593
|
+
}, Qe = (e) => e instanceof Response ? new TypeError(`[markdown-explorer] authenticate threw a Response (status ${String(e.status)}), which a data request does not follow`) : e, An = (e) => {
|
|
587
594
|
if (typeof e != "string") throw new TypeError(`[markdown-explorer] scopeSecret must be a string; got ${typeof e}`);
|
|
588
|
-
if (e.length <
|
|
595
|
+
if (e.length < Ye) throw new RangeError(`[markdown-explorer] scopeSecret must have at least ${String(Ye)} characters; got ${String(e.length)}`);
|
|
589
596
|
return e;
|
|
590
|
-
},
|
|
591
|
-
if (!
|
|
597
|
+
}, In = (e, t) => {
|
|
598
|
+
if (!Zt(t)) throw new TypeError(`[markdown-explorer] ${e}.boundary must be a plain object { contains, confirm? }`);
|
|
592
599
|
for (const r of Object.keys(t))
|
|
593
|
-
if (!
|
|
594
|
-
|
|
595
|
-
},
|
|
600
|
+
if (!Ke.includes(r)) throw new RangeError(`[markdown-explorer] ${e}.boundary.${r} is not an option; expected ${Ke.join(", ")}`);
|
|
601
|
+
G(`${e}.boundary.contains`, t.contains, !1), G(`${e}.boundary.confirm`, t.confirm, !0);
|
|
602
|
+
}, Mn = (e) => {
|
|
596
603
|
if (!Array.isArray(e)) throw new TypeError("[markdown-explorer] sources must be an array");
|
|
597
604
|
const t = e;
|
|
598
605
|
if (t.length === 0) throw new RangeError("[markdown-explorer] sources must hold at least one source");
|
|
599
606
|
const r = /* @__PURE__ */ new Set();
|
|
600
|
-
for (const [n,
|
|
601
|
-
if (
|
|
602
|
-
const { id: o, label: c, boundary:
|
|
603
|
-
if (!
|
|
607
|
+
for (const [n, a] of t.entries()) {
|
|
608
|
+
if (a === null || typeof a != "object") throw new TypeError(`[markdown-explorer] sources[${String(n)}] must be an object`);
|
|
609
|
+
const { id: o, label: c, boundary: f, tree: g, children: T, cacheScope: d } = a;
|
|
610
|
+
if (!Wt(o))
|
|
604
611
|
throw new RangeError(`[markdown-explorer] sources[${String(n)}].id must use lowercase letters, digits and "-" and start with a letter or digit; got ${typeof o == "string" ? JSON.stringify(o) : typeof o}`);
|
|
605
|
-
const
|
|
612
|
+
const p = `source ${JSON.stringify(o)}`;
|
|
606
613
|
if (r.has(o)) throw new RangeError(`[markdown-explorer] source ids must be unique; ${JSON.stringify(o)} repeats at sources[${String(n)}]`);
|
|
607
|
-
if (r.add(o), typeof c != "string" && typeof c != "function") throw new TypeError(`[markdown-explorer] ${
|
|
608
|
-
if (typeof c == "string" && c.trim() === "") throw new RangeError(`[markdown-explorer] ${
|
|
609
|
-
if (
|
|
614
|
+
if (r.add(o), typeof c != "string" && typeof c != "function") throw new TypeError(`[markdown-explorer] ${p}.label must be a string or a function`);
|
|
615
|
+
if (typeof c == "string" && c.trim() === "") throw new RangeError(`[markdown-explorer] ${p}.label must not be blank`);
|
|
616
|
+
if (In(p, f), G(`${p}.tree`, g, !1), G(`${p}.children`, T, !0), d !== void 0 && d !== "principal" && d !== "shared") throw new RangeError(`[markdown-explorer] ${p}.cacheScope must be "principal" or "shared"; got ${JSON.stringify(d)}`);
|
|
610
617
|
}
|
|
611
618
|
return t;
|
|
612
|
-
},
|
|
619
|
+
}, vn = (e) => {
|
|
613
620
|
if (e === !1) return !1;
|
|
614
621
|
if (e === void 0) return me;
|
|
615
622
|
if (e === null || typeof e != "object") throw new TypeError("[markdown-explorer] treeCache must be { ttlMs, maxEntries, maxBytes } or false");
|
|
@@ -619,231 +626,252 @@ const br = (e) => e instanceof Error && Object.hasOwn(e, it) && et(e.reason), Sr
|
|
|
619
626
|
maxEntries: r === void 0 ? me.maxEntries : ge("treeCache.maxEntries", r, Number.MAX_SAFE_INTEGER),
|
|
620
627
|
maxBytes: n === void 0 ? me.maxBytes : ge("treeCache.maxBytes", n, Number.MAX_SAFE_INTEGER)
|
|
621
628
|
};
|
|
622
|
-
},
|
|
629
|
+
}, qe = (e) => {
|
|
623
630
|
if (e === null || typeof e != "object") throw new TypeError("[markdown-explorer] authenticate must resolve to { status, ... }");
|
|
624
631
|
const { status: t } = e;
|
|
625
632
|
if (t !== "signed-in" && t !== "signed-out" && t !== "forbidden") throw new TypeError(`[markdown-explorer] authenticate resolved to an unknown status ${JSON.stringify(t)}`);
|
|
626
633
|
return e;
|
|
627
|
-
},
|
|
634
|
+
}, et = (e, t) => {
|
|
628
635
|
if (!(t instanceof Response)) throw new TypeError(`[markdown-explorer] ${e} must return a Response`);
|
|
629
636
|
return t;
|
|
630
|
-
},
|
|
637
|
+
}, tt = (e, t) => {
|
|
631
638
|
if (typeof t != "string" || t === "") throw new TypeError("[markdown-explorer] principalKey must return a non-empty string");
|
|
632
|
-
return
|
|
633
|
-
},
|
|
639
|
+
return Bt("sha256", e).update(t, "utf8").digest("base64url").slice(0, kn);
|
|
640
|
+
}, Pn = (e) => e.view === "multi" ? e.docs : e.doc === null ? [] : [e.doc], pe = (e, t) => {
|
|
634
641
|
const r = e.getAll(t), [n] = r;
|
|
635
642
|
return r.length === 1 && n !== void 0 && n !== "" ? n : null;
|
|
636
643
|
}, ae = (e, t, r, n = {}) => {
|
|
637
|
-
const
|
|
638
|
-
for (const [o, c] of Object.entries({ ...
|
|
639
|
-
return new Response(t, { status: e, headers:
|
|
640
|
-
},
|
|
641
|
-
|
|
644
|
+
const a = new Headers(r);
|
|
645
|
+
for (const [o, c] of Object.entries({ ...On, ...n })) a.set(o, c);
|
|
646
|
+
return new Response(t, { status: e, headers: a });
|
|
647
|
+
}, B = (e, t, r) => ae(Lt[e.reason], JSON.stringify({ failure: e }), t, r), rt = (e, t) => {
|
|
648
|
+
const r = t === "refresh" ? ["POST"] : ["GET", "HEAD"];
|
|
649
|
+
return r.includes(e.method.toUpperCase()) ? null : ae(405, JSON.stringify({ failure: { reason: "invalid" } }), void 0, { Allow: r.join(", ") });
|
|
650
|
+
}, nt = (e) => {
|
|
651
|
+
if (e.headers.get(ut) !== zt) return !1;
|
|
642
652
|
const t = e.headers.get("Sec-Fetch-Site");
|
|
643
653
|
return t === null || t === "same-origin";
|
|
644
|
-
},
|
|
645
|
-
const n = new Headers(),
|
|
646
|
-
for (const o of
|
|
647
|
-
for (const [c,
|
|
648
|
-
c !== "set-cookie" && n.set(c,
|
|
654
|
+
}, $n = ({ parentHeaders: e, loaderHeaders: t, errorHeaders: r }) => {
|
|
655
|
+
const n = new Headers(), a = r === void 0 ? [e, t] : [e, t, r];
|
|
656
|
+
for (const o of a)
|
|
657
|
+
for (const [c, f] of o)
|
|
658
|
+
c !== "set-cookie" && n.set(c, f);
|
|
649
659
|
return n;
|
|
650
|
-
},
|
|
660
|
+
}, ao = (e) => {
|
|
651
661
|
if (e === null || typeof e != "object") throw new TypeError("[markdown-explorer] the server configuration must be an object");
|
|
652
|
-
const t =
|
|
653
|
-
|
|
654
|
-
const n =
|
|
662
|
+
const t = Nn(e.config), r = An(e.scopeSecret);
|
|
663
|
+
G("authenticate", e.authenticate, !1), G("principalKey", e.principalKey, !1), G("document", e.document, !1), G("asset", e.asset, !1), G("signedOutPage", e.signedOutPage, !1), G("forbiddenPage", e.forbiddenPage, !0), G("notices", e.notices, !0), G("onTreeRefreshed", e.onTreeRefreshed, !0);
|
|
664
|
+
const n = Mn(e.sources), a = Xt(
|
|
655
665
|
t,
|
|
656
|
-
n.map((
|
|
666
|
+
n.map((s) => s.id)
|
|
657
667
|
), o = e.logger;
|
|
658
668
|
if (o !== void 0 && (o === null || typeof o != "object" || typeof o.error != "function"))
|
|
659
669
|
throw new TypeError("[markdown-explorer] logger must be an object with an error function");
|
|
660
|
-
const c = e.logger === void 0 ?
|
|
661
|
-
if (typeof
|
|
662
|
-
const
|
|
663
|
-
if (typeof
|
|
664
|
-
return
|
|
665
|
-
}, u = (
|
|
666
|
-
const
|
|
667
|
-
let
|
|
670
|
+
const c = e.logger === void 0 ? Ar : e.logger, f = ct(e.deny), g = e.deferredTimeoutMs === void 0 ? xn : ge("deferredTimeoutMs", e.deferredTimeoutMs, Rn), T = vn(e.treeCache), d = () => Date.now(), p = T === !1 ? pn(d) : mn({ ...T, now: d }), _ = sn(e.sanitizer, t.multiView.maxPanels), R = dn({ iframeHosts: t.iframeHosts, dataPath: t.dataPath, options: _, pool: cn(an(), _) }), I = new Map(n.map((s) => [s.id, s])), O = Gt(t), { authenticate: x, principalKey: P, signedOutPage: b, forbiddenPage: N, onTreeRefreshed: C } = e, z = (s, m) => {
|
|
671
|
+
if (typeof s.label == "string") return s.label;
|
|
672
|
+
const l = s.label(m);
|
|
673
|
+
if (typeof l != "string" || l.trim() === "") throw new TypeError(`[markdown-explorer] the label of source ${JSON.stringify(s.id)} must be a non-blank string`);
|
|
674
|
+
return l;
|
|
675
|
+
}, u = (s, m) => s.cacheScope === "shared" ? s.id : `${s.id}\0${m}`, i = (s, m) => {
|
|
676
|
+
const l = (h) => q(c, { operation: "boundary", sourceId: s.id, path: m, error: h });
|
|
677
|
+
let w;
|
|
668
678
|
try {
|
|
669
|
-
|
|
670
|
-
} catch (
|
|
671
|
-
return
|
|
679
|
+
w = s.boundary.contains(m);
|
|
680
|
+
} catch (h) {
|
|
681
|
+
return l(h), !1;
|
|
672
682
|
}
|
|
673
|
-
return typeof
|
|
674
|
-
}, E = async (
|
|
675
|
-
const { confirm:
|
|
676
|
-
if (
|
|
677
|
-
let
|
|
683
|
+
return typeof w == "boolean" ? w : (Fe(w, l), l(new TypeError(`[markdown-explorer] boundary.contains of source ${JSON.stringify(s.id)} must return a boolean; got ${typeof w}`)), !1);
|
|
684
|
+
}, E = async (s, m, l) => {
|
|
685
|
+
const { confirm: w } = s.boundary;
|
|
686
|
+
if (w === void 0) return { status: "inside", readPath: l };
|
|
687
|
+
let h;
|
|
678
688
|
try {
|
|
679
|
-
|
|
680
|
-
} catch (
|
|
681
|
-
return { status: "error", failure: Q(
|
|
689
|
+
h = await w(m, l);
|
|
690
|
+
} catch (V) {
|
|
691
|
+
return { status: "error", failure: Q(V, { operation: "boundary", sourceId: s.id, path: l }, m.signal, c) };
|
|
682
692
|
}
|
|
683
|
-
const
|
|
684
|
-
if (
|
|
685
|
-
const { inside: j, readPath:
|
|
686
|
-
return j === !1 ? { status: "outside" } : j !== !0 || typeof
|
|
687
|
-
},
|
|
688
|
-
const
|
|
689
|
-
if (
|
|
690
|
-
if (
|
|
691
|
-
let
|
|
692
|
-
for (const
|
|
693
|
-
const
|
|
694
|
-
if (
|
|
695
|
-
|
|
693
|
+
const y = (V) => (q(c, { operation: "boundary", sourceId: s.id, path: l, error: new TypeError(`[markdown-explorer] boundary.confirm of source ${JSON.stringify(s.id)} ${V}`) }), { status: "outside" });
|
|
694
|
+
if (h === null || typeof h != "object") return y(`must resolve to { inside, readPath? }; got ${h === null ? "null" : typeof h}`);
|
|
695
|
+
const { inside: j, readPath: L } = h;
|
|
696
|
+
return j === !1 ? { status: "outside" } : j !== !0 || typeof L != "string" ? y("must resolve to { inside: false } or { inside: true, readPath: string }") : !te(L) || f.deniesPath(L) ? (q(c, { operation: "boundary", sourceId: s.id, path: l, error: new RangeError(`[markdown-explorer] boundary.confirm of source ${JSON.stringify(s.id)} answered a readPath the explorer does not serve (unacceptable or denied)`) }), { status: "error", failure: { reason: "failed" } }) : { status: "inside", readPath: L };
|
|
697
|
+
}, S = async (s, m) => {
|
|
698
|
+
const l = n.filter((h) => i(h, m));
|
|
699
|
+
if (l.length === 0) return { status: "error", failure: { reason: "forbidden" } };
|
|
700
|
+
if (l.some((h) => h.boundary.confirm === void 0)) return { status: "inside", readPath: m };
|
|
701
|
+
let w = null;
|
|
702
|
+
for (const h of l) {
|
|
703
|
+
const y = await E(h, s, m);
|
|
704
|
+
if (y.status === "inside") return y;
|
|
705
|
+
y.status === "error" && w === null && (w = y.failure);
|
|
696
706
|
}
|
|
697
|
-
return { status: "error", failure:
|
|
698
|
-
}, k = (
|
|
699
|
-
const
|
|
707
|
+
return { status: "error", failure: w === null ? { reason: "forbidden" } : w };
|
|
708
|
+
}, k = (s, m, l) => {
|
|
709
|
+
const w = ({ path: h, reason: y }) => vr(c, { operation: l.operation, sourceId: l.sourceId, path: h, error: new TypeError(`[markdown-explorer] an entry with ${y} was left out`) });
|
|
700
710
|
try {
|
|
701
|
-
return { status: "ok", entries:
|
|
702
|
-
} catch (
|
|
703
|
-
return q(c, { ...
|
|
711
|
+
return { status: "ok", entries: hn(s, f, m.children !== void 0, w, (h) => i(m, h)) };
|
|
712
|
+
} catch (h) {
|
|
713
|
+
return q(c, { ...l, error: h }), { status: "error", failure: { reason: "failed" } };
|
|
704
714
|
}
|
|
705
|
-
}, A = async (
|
|
706
|
-
let
|
|
715
|
+
}, A = async (s, m, l) => {
|
|
716
|
+
let w;
|
|
707
717
|
try {
|
|
708
|
-
|
|
709
|
-
} catch (
|
|
710
|
-
return { status: "error", failure: Q(
|
|
718
|
+
w = await s.tree(m);
|
|
719
|
+
} catch (y) {
|
|
720
|
+
return { status: "error", failure: Q(y, { operation: "tree", sourceId: s.id }, m.signal, c) };
|
|
711
721
|
}
|
|
712
|
-
const
|
|
713
|
-
return
|
|
714
|
-
}, D = (
|
|
715
|
-
if (
|
|
716
|
-
const
|
|
722
|
+
const h = k(w, s, { operation: "tree", sourceId: s.id });
|
|
723
|
+
return h.status === "ok" ? { status: "ok", entries: h.entries, loadedAt: l } : h;
|
|
724
|
+
}, D = (s, m, l, w) => (h, y) => A(s, { principal: m, request: l, signal: h, refresh: w }, y), M = (s, m, l, w, h) => p.load(u(s, l), h, D(s, m, w, !1)), U = (s, m, l, w) => p.refresh(u(s, l), w.signal, D(s, m, w, !0)), X = (s, m) => {
|
|
725
|
+
if (C === void 0) return;
|
|
726
|
+
const l = (w) => q(c, { operation: "refresh", sourceId: s.id, error: w });
|
|
717
727
|
try {
|
|
718
|
-
|
|
719
|
-
} catch (
|
|
720
|
-
|
|
728
|
+
Fe(C(m), l);
|
|
729
|
+
} catch (w) {
|
|
730
|
+
l(w);
|
|
721
731
|
}
|
|
722
|
-
},
|
|
723
|
-
if (
|
|
724
|
-
const
|
|
725
|
-
if (
|
|
726
|
-
if (
|
|
727
|
-
let
|
|
732
|
+
}, F = async (s, m, l, w) => {
|
|
733
|
+
if (f.deniesPath(w) || !i(s, w)) return { status: "error", failure: { reason: "forbidden" } };
|
|
734
|
+
const h = await E(s, l, w);
|
|
735
|
+
if (h.status === "outside") return { status: "error", failure: { reason: "forbidden" } };
|
|
736
|
+
if (h.status === "error") return { status: "error", failure: h.failure };
|
|
737
|
+
let y;
|
|
728
738
|
try {
|
|
729
|
-
|
|
739
|
+
y = await m(l, w, h.readPath);
|
|
730
740
|
} catch (j) {
|
|
731
|
-
return { status: "error", failure: Q(j, { operation: "children", sourceId:
|
|
741
|
+
return { status: "error", failure: Q(j, { operation: "children", sourceId: s.id, path: w }, l.signal, c) };
|
|
732
742
|
}
|
|
733
|
-
return k(
|
|
734
|
-
}, Z = async (
|
|
735
|
-
if (
|
|
736
|
-
const
|
|
737
|
-
if (
|
|
738
|
-
let
|
|
743
|
+
return k(y, s, { operation: "children", sourceId: s.id, path: w });
|
|
744
|
+
}, Z = async (s, m, l) => {
|
|
745
|
+
if (f.deniesPath(l)) return ee("forbidden");
|
|
746
|
+
const w = await S(s, l);
|
|
747
|
+
if (w.status === "error") return { kind: "failure", failure: w.failure };
|
|
748
|
+
let h;
|
|
739
749
|
try {
|
|
740
|
-
|
|
741
|
-
} catch (
|
|
742
|
-
return { kind: "failure", failure: Q(
|
|
750
|
+
h = await e.document(s, l, w.readPath);
|
|
751
|
+
} catch (L) {
|
|
752
|
+
return { kind: "failure", failure: Q(L, { operation: "document", path: l }, s.signal, c) };
|
|
743
753
|
}
|
|
744
|
-
let
|
|
754
|
+
let y;
|
|
745
755
|
try {
|
|
746
|
-
|
|
747
|
-
} catch (
|
|
748
|
-
return q(c, { operation: "document", path:
|
|
756
|
+
y = bn(h, (L) => Dt(t.dataPath, l, L));
|
|
757
|
+
} catch (L) {
|
|
758
|
+
return q(c, { operation: "document", path: l, error: L }), ee("failed");
|
|
749
759
|
}
|
|
750
|
-
if (
|
|
751
|
-
const j = await R(
|
|
752
|
-
return j.status === "sanitized" ? { kind: "markdown", name:
|
|
753
|
-
}, $ = async (
|
|
760
|
+
if (y.kind !== "unsanitized-markdown") return y;
|
|
761
|
+
const j = await R(y.html, { scope: m, signal: s.signal });
|
|
762
|
+
return j.status === "sanitized" ? { kind: "markdown", name: y.name, htmlContent: j.html, headings: y.headings } : (j.cause !== null && q(c, { operation: "document", path: l, error: j.cause }), ee(j.reason));
|
|
763
|
+
}, $ = async (s) => {
|
|
754
764
|
if (e.notices === void 0) return ke;
|
|
755
765
|
try {
|
|
756
|
-
return
|
|
757
|
-
} catch (
|
|
758
|
-
return Q(
|
|
766
|
+
return Tn(await e.notices(s));
|
|
767
|
+
} catch (m) {
|
|
768
|
+
return Q(m, { operation: "notices" }, s.signal, c), ke;
|
|
759
769
|
}
|
|
760
|
-
}, J = async (
|
|
761
|
-
const
|
|
762
|
-
if (
|
|
763
|
-
if (
|
|
764
|
-
return
|
|
765
|
-
const
|
|
766
|
-
if (
|
|
770
|
+
}, J = async (s, m, l) => {
|
|
771
|
+
const w = pe(m, de.path);
|
|
772
|
+
if (w === null || !te(w)) return B({ reason: "invalid" }, l);
|
|
773
|
+
if (f.deniesPath(w))
|
|
774
|
+
return Pr(c, { operation: "asset", path: w }), B({ reason: "forbidden" }, l);
|
|
775
|
+
const h = await S(s, w);
|
|
776
|
+
if (h.status === "error") return B(h.failure, l);
|
|
767
777
|
try {
|
|
768
|
-
const
|
|
769
|
-
return await
|
|
770
|
-
} catch (
|
|
771
|
-
return
|
|
778
|
+
const y = Sn(await e.asset(s, w, h.readPath));
|
|
779
|
+
return await Nr(s.request, y, l);
|
|
780
|
+
} catch (y) {
|
|
781
|
+
return B(Q(y, { operation: "asset", path: w }, s.signal, c), l);
|
|
772
782
|
}
|
|
773
|
-
},
|
|
774
|
-
const
|
|
775
|
-
if (
|
|
776
|
-
if (
|
|
777
|
-
const
|
|
778
|
-
if (
|
|
779
|
-
const { location: j } =
|
|
780
|
-
if (
|
|
781
|
-
const
|
|
782
|
-
scope:
|
|
783
|
-
sources: n.map((H) => ({ id: H.id, label:
|
|
784
|
-
settings:
|
|
783
|
+
}, K = async ({ request: s, url: m, params: l }) => {
|
|
784
|
+
const w = qe(await x(s, Ve)), h = Ze(w.headers);
|
|
785
|
+
if (h.set("Cache-Control", Et), w.status === "signed-out") throw et("signedOutPage", b(s, h));
|
|
786
|
+
if (w.status === "forbidden") throw N === void 0 ? Pe(null, { status: 403, headers: h }) : et("forbiddenPage", N(s, h));
|
|
787
|
+
const y = Yt(a, l["*"], m.searchParams);
|
|
788
|
+
if (y.kind === "redirect") throw Ut(y.href, { headers: h });
|
|
789
|
+
const { location: j } = y, { principal: L } = w, V = tt(r, P(L)), ce = I.get(j.source);
|
|
790
|
+
if (ce === void 0) throw new RangeError(`[markdown-explorer] no source for ${JSON.stringify(j.source)}`);
|
|
791
|
+
const ue = { signal: s.signal, timeoutMs: g, late: null, handOverMs: g }, oe = {
|
|
792
|
+
scope: V,
|
|
793
|
+
sources: n.map((H) => ({ id: H.id, label: z(H, L) })),
|
|
794
|
+
settings: Kt(Vt(s.headers.get("Cookie"), t.settingsCookie), O),
|
|
785
795
|
tree: j.view === "single" ? null : {
|
|
786
796
|
sourceId: j.source,
|
|
787
|
-
result: Te(
|
|
797
|
+
result: Te(ue, (H) => M(ce, L, V, s, H).then((Y) => Y.result))
|
|
788
798
|
},
|
|
789
|
-
documents:
|
|
790
|
-
notices: Te({ signal:
|
|
799
|
+
documents: Pn(j).map((H) => ({ path: H, document: Te(ue, (Y) => Z({ principal: L, request: s, signal: Y, refresh: !1 }, V, H)) })),
|
|
800
|
+
notices: Te({ signal: s.signal, timeoutMs: g, late: ke, handOverMs: 0 }, (H) => $({ principal: L, request: s, signal: H, refresh: !1 }))
|
|
791
801
|
};
|
|
792
|
-
return
|
|
793
|
-
},
|
|
794
|
-
|
|
802
|
+
return Pe(oe, { headers: h });
|
|
803
|
+
}, W = async (s, m) => {
|
|
804
|
+
const l = qe(await x(s, m));
|
|
805
|
+
return { authentication: l, headers: Ze(l.headers) };
|
|
806
|
+
}, le = async (s, m, l) => {
|
|
807
|
+
let w;
|
|
795
808
|
try {
|
|
796
|
-
|
|
797
|
-
} catch (
|
|
798
|
-
return
|
|
809
|
+
w = await W(m, Ve);
|
|
810
|
+
} catch (Y) {
|
|
811
|
+
return B(Q(Qe(Y), { operation: "authenticate" }, m.signal, c));
|
|
812
|
+
}
|
|
813
|
+
const { authentication: h, headers: y } = w;
|
|
814
|
+
if (h.status === "signed-out") return B({ reason: "unauthorized" }, y);
|
|
815
|
+
if (h.status === "forbidden") return B({ reason: "forbidden" }, y);
|
|
816
|
+
const { principal: j } = h;
|
|
817
|
+
if (s === "asset") return J({ principal: j, request: m, signal: m.signal, refresh: !1 }, l, y);
|
|
818
|
+
const L = tt(r, P(j));
|
|
819
|
+
y.set(Ct, L);
|
|
820
|
+
const V = { principal: j, request: m, signal: m.signal, refresh: !1 }, ce = () => B({ reason: "invalid" }, y);
|
|
821
|
+
if (s === "document") {
|
|
822
|
+
const Y = pe(l, de.path);
|
|
823
|
+
if (Y === null || !te(Y)) return ce();
|
|
824
|
+
const ve = m.headers.has(ut);
|
|
825
|
+
return ve && !nt(m) ? B({ reason: "forbidden" }, y) : ae(200, JSON.stringify(await Z({ ...V, refresh: ve }, L, Y)), y);
|
|
826
|
+
}
|
|
827
|
+
const ue = pe(l, de.source), oe = pe(l, de.path);
|
|
828
|
+
if (ue === null || s === "children" && (oe === null || !te(oe))) return ce();
|
|
829
|
+
const H = I.get(ue);
|
|
830
|
+
if (H === void 0) return B({ reason: "not-found" }, y);
|
|
831
|
+
if (s === "tree") return ae(200, (await M(H, j, L, m, m.signal)).json, y);
|
|
832
|
+
if (s === "refresh") {
|
|
833
|
+
const Y = await U(H, j, L, m);
|
|
834
|
+
return m.signal.aborted || X(H, u(H, L)), ae(200, Y.json, y);
|
|
799
835
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
if (H === null || !te(H)) return K();
|
|
811
|
-
const se = h.headers.has(rt);
|
|
812
|
-
return se && !Ve(h) ? F({ reason: "forbidden" }, f) : ae(200, JSON.stringify(await Z({ ...C, refresh: se }, j, H)), f);
|
|
836
|
+
return H.children === void 0 || oe === null ? B({ reason: "not-found" }, y) : ae(200, JSON.stringify(await F(H, H.children, V, oe)), y);
|
|
837
|
+
}, kt = async (s) => {
|
|
838
|
+
const { authentication: m, headers: l } = await W(s, _n);
|
|
839
|
+
if (!l.keys().next().done) throw new TypeError("[markdown-explorer] authenticate returned headers in read-only mode; a read-only authentication must only read the session (none was forwarded)");
|
|
840
|
+
return m.status === "signed-in";
|
|
841
|
+
}, Ie = t.embeds.linkCardEndpoint === null ? null : Ft({ authorize: kt }), Rt = async (s, m) => {
|
|
842
|
+
try {
|
|
843
|
+
return await s(m);
|
|
844
|
+
} catch (l) {
|
|
845
|
+
return B(Q(Qe(l), { operation: "authenticate" }, m.signal, c));
|
|
813
846
|
}
|
|
814
|
-
|
|
815
|
-
if (
|
|
816
|
-
|
|
817
|
-
if (
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
return h.signal.aborted || G(V, u(V, j)), ae(200, H.json, f);
|
|
847
|
+
}, Me = async (s, m, l) => {
|
|
848
|
+
if (new URL(s.url).pathname.endsWith(".data")) return B({ reason: "not-found" });
|
|
849
|
+
if (!jt(l)) return B({ reason: "not-found" });
|
|
850
|
+
if (l === "link-card") {
|
|
851
|
+
if (Ie === null) return B({ reason: "not-found" });
|
|
852
|
+
const h = rt(s, l);
|
|
853
|
+
return h !== null ? h : Rt(Ie, s);
|
|
822
854
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
if (
|
|
826
|
-
if (!Rt(d)) return F({ reason: "not-found" });
|
|
827
|
-
const p = i.method.toUpperCase(), f = d === "refresh";
|
|
828
|
-
if (f ? p !== "POST" : p !== "GET" && p !== "HEAD") return ae(405, JSON.stringify({ failure: { reason: "invalid" } }), void 0, { Allow: f ? "POST" : "GET, HEAD" });
|
|
829
|
-
if (f && !Ve(i)) return F({ reason: "forbidden" });
|
|
855
|
+
const w = rt(s, l);
|
|
856
|
+
if (w !== null) return w;
|
|
857
|
+
if (l === "refresh" && !nt(s)) return B({ reason: "forbidden" });
|
|
830
858
|
try {
|
|
831
|
-
return await
|
|
832
|
-
} catch (
|
|
833
|
-
return
|
|
859
|
+
return await le(l, s, m.searchParams);
|
|
860
|
+
} catch (h) {
|
|
861
|
+
return B(Q(h, { operation: l }, s.signal, c));
|
|
834
862
|
}
|
|
835
863
|
};
|
|
836
864
|
return Object.freeze({
|
|
837
|
-
loader:
|
|
838
|
-
headers:
|
|
839
|
-
dataLoader: ({ request:
|
|
840
|
-
dataAction: ({ request:
|
|
841
|
-
forgetTree: (
|
|
842
|
-
if (typeof
|
|
843
|
-
|
|
865
|
+
loader: K,
|
|
866
|
+
headers: $n,
|
|
867
|
+
dataLoader: ({ request: s, url: m, params: l }) => Me(s, m, l["*"]),
|
|
868
|
+
dataAction: ({ request: s, url: m, params: l }) => Me(s, m, l["*"]),
|
|
869
|
+
forgetTree: (s) => {
|
|
870
|
+
if (typeof s != "string") throw new TypeError(`[markdown-explorer] forgetTree needs the string key onTreeRefreshed received; got ${typeof s}`);
|
|
871
|
+
p.forget(s);
|
|
844
872
|
}
|
|
845
873
|
});
|
|
846
|
-
}, we = Object.freeze({ maxEntries: 2e4, maxDocumentBytes: 2 * 1024 * 1024, textPreviewBytes: 8 * 1024, walkConcurrency: 8 }),
|
|
874
|
+
}, we = Object.freeze({ maxEntries: 2e4, maxDocumentBytes: 2 * 1024 * 1024, textPreviewBytes: 8 * 1024, walkConcurrency: 8 }), jn = /* @__PURE__ */ new Set(["md", "markdown", "mdx"]), yt = new Map([
|
|
847
875
|
...[
|
|
848
876
|
["png", "image/png"],
|
|
849
877
|
["jpg", "image/jpeg"],
|
|
@@ -878,7 +906,7 @@ const br = (e) => e instanceof Error && Object.hasOwn(e, it) && et(e.reason), Sr
|
|
|
878
906
|
["wma", "audio/x-ms-wma"]
|
|
879
907
|
].map(([e, t]) => [e, { kind: "media", mediaType: "audio", contentType: t }]),
|
|
880
908
|
["pdf", { kind: "media", mediaType: "pdf", contentType: "application/pdf" }]
|
|
881
|
-
]),
|
|
909
|
+
]), Ln = /* @__PURE__ */ new Set([
|
|
882
910
|
"txt",
|
|
883
911
|
"text",
|
|
884
912
|
"log",
|
|
@@ -1003,7 +1031,7 @@ const br = (e) => e instanceof Error && Object.hasOwn(e, it) && et(e.reason), Sr
|
|
|
1003
1031
|
"eslintrc",
|
|
1004
1032
|
"babelrc",
|
|
1005
1033
|
"mailmap"
|
|
1006
|
-
]),
|
|
1034
|
+
]), zn = /* @__PURE__ */ new Set([
|
|
1007
1035
|
"readme",
|
|
1008
1036
|
"license",
|
|
1009
1037
|
"licence",
|
|
@@ -1024,17 +1052,17 @@ const br = (e) => e instanceof Error && Object.hasOwn(e, it) && et(e.reason), Sr
|
|
|
1024
1052
|
"jenkinsfile",
|
|
1025
1053
|
"brewfile",
|
|
1026
1054
|
"codeowners"
|
|
1027
|
-
]),
|
|
1055
|
+
]), bt = typeof ie.O_NOFOLLOW == "number" ? ie.O_NOFOLLOW : 0, St = typeof ie.O_NONBLOCK == "number" ? ie.O_NONBLOCK : 0, ot = (e) => `${e.size}-${e.mtimeNs}`, Cn = (e) => e !== "" && e !== "." && e !== ".." && !e.includes(":") && !e.endsWith(".") && !e.endsWith(" "), st = (e) => te(e) && e === e.normalize("NFC") && e.split("/").every(Cn), Tt = (e) => {
|
|
1028
1056
|
const t = e.toLowerCase(), r = t.lastIndexOf(".");
|
|
1029
1057
|
return r < 0 ? "" : t.slice(r + 1);
|
|
1030
|
-
},
|
|
1031
|
-
const t =
|
|
1032
|
-
if (
|
|
1033
|
-
const r =
|
|
1058
|
+
}, Dn = (e) => {
|
|
1059
|
+
const t = Tt(e);
|
|
1060
|
+
if (jn.has(t)) return { kind: "markdown" };
|
|
1061
|
+
const r = yt.get(t);
|
|
1034
1062
|
if (r !== void 0) return r.kind === "image" ? { kind: "image" } : { kind: "media", mediaType: r.mediaType };
|
|
1035
1063
|
const n = e.toLowerCase();
|
|
1036
|
-
return
|
|
1037
|
-
},
|
|
1064
|
+
return Ln.has(t) || !n.includes(".") && zn.has(n) ? { kind: "text" } : { kind: "file" };
|
|
1065
|
+
}, xt = (e) => {
|
|
1038
1066
|
switch (typeof e == "object" && e !== null ? e.code : void 0) {
|
|
1039
1067
|
case "ENOENT":
|
|
1040
1068
|
case "ENOTDIR":
|
|
@@ -1050,122 +1078,122 @@ const br = (e) => e instanceof Error && Object.hasOwn(e, it) && et(e.reason), Sr
|
|
|
1050
1078
|
default:
|
|
1051
1079
|
return null;
|
|
1052
1080
|
}
|
|
1053
|
-
},
|
|
1054
|
-
const t =
|
|
1081
|
+
}, Ot = (e) => {
|
|
1082
|
+
const t = xt(e);
|
|
1055
1083
|
return t === null ? e : new Ee(t);
|
|
1056
|
-
},
|
|
1084
|
+
}, Hn = (e) => {
|
|
1057
1085
|
const t = typeof e == "object" && e !== null ? e.code : void 0;
|
|
1058
1086
|
return t === "EACCES" || t === "EPERM" || t === "ENOENT" || t === "ENOTDIR";
|
|
1059
|
-
},
|
|
1087
|
+
}, at = async (e, t) => {
|
|
1060
1088
|
const r = [];
|
|
1061
1089
|
for await (const n of e.createReadStream({ start: 0, end: t - 1, autoClose: !1 }))
|
|
1062
1090
|
r.push(n);
|
|
1063
1091
|
return Buffer.concat(r);
|
|
1064
|
-
},
|
|
1065
|
-
let
|
|
1092
|
+
}, Bn = (e, t) => e !== null ? { start: e.offset, end: e.offset + e.length - 1 } : t === 0n ? {} : { start: 0, end: Number(t) - 1 }, Fn = async (e, t, r, n) => {
|
|
1093
|
+
let a;
|
|
1066
1094
|
try {
|
|
1067
|
-
|
|
1068
|
-
const g = await
|
|
1095
|
+
a = await lt(e, ie.O_RDONLY | bt | St);
|
|
1096
|
+
const g = await a.stat({ bigint: !0 });
|
|
1069
1097
|
if (!g.isFile() || g.ino !== t.ino || g.dev !== t.dev || g.size !== t.size || g.mtimeNs !== t.mtimeNs) throw new Ee("not-found");
|
|
1070
1098
|
} catch (g) {
|
|
1071
|
-
throw await
|
|
1099
|
+
throw await a?.close(), Ot(g);
|
|
1072
1100
|
}
|
|
1073
|
-
const o =
|
|
1101
|
+
const o = a.createReadStream(Bn(r, t.size)), c = ur.toWeb(o), f = () => {
|
|
1074
1102
|
o.destroy(n.reason);
|
|
1075
1103
|
};
|
|
1076
|
-
return n.aborted ?
|
|
1077
|
-
},
|
|
1078
|
-
const r =
|
|
1104
|
+
return n.aborted ? f() : n.addEventListener("abort", f, { once: !0 }), c;
|
|
1105
|
+
}, Un = (e, t) => {
|
|
1106
|
+
const r = Qt.compare(e, t);
|
|
1079
1107
|
return r !== 0 ? r : e === t ? 0 : e < t ? -1 : 1;
|
|
1080
|
-
},
|
|
1108
|
+
}, Xn = (e) => {
|
|
1081
1109
|
let t = 0;
|
|
1082
1110
|
const r = [];
|
|
1083
1111
|
return async (n) => {
|
|
1084
|
-
t < e ? t += 1 : await new Promise((
|
|
1112
|
+
t < e ? t += 1 : await new Promise((a) => r.push(a));
|
|
1085
1113
|
try {
|
|
1086
1114
|
return await n();
|
|
1087
1115
|
} finally {
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1116
|
+
const a = r.shift();
|
|
1117
|
+
a === void 0 ? t -= 1 : a();
|
|
1090
1118
|
}
|
|
1091
1119
|
};
|
|
1092
|
-
},
|
|
1120
|
+
}, Gn = (e, t) => {
|
|
1093
1121
|
if (e.isSymbolicLink()) return "symbolic-link";
|
|
1094
1122
|
if (!(e.isDirectory() || e.isFile())) return "special-file";
|
|
1095
1123
|
const { name: r } = e;
|
|
1096
1124
|
return r !== r.normalize("NFC") ? "not-nfc" : r.includes(":") ? "colon" : r.endsWith(".") || r.endsWith(" ") ? "trailing-dot-or-space" : te(r) ? te(t) ? null : "path-too-long" : "control-character-or-backslash";
|
|
1097
|
-
},
|
|
1125
|
+
}, Jn = /* @__PURE__ */ new Set(["id", "label", "root", "pathPrefix", "parse", "deny", "maxEntries", "maxDocumentBytes", "textPreviewBytes", "walkConcurrency", "cacheScope", "reportSkipped"]), he = (e, t, r) => {
|
|
1098
1126
|
if (t === void 0) return r;
|
|
1099
1127
|
if (typeof t != "number" || !Number.isSafeInteger(t) || t <= 0) throw new RangeError(`[markdown-explorer] ${e} must be a positive integer; got ${String(t)}`);
|
|
1100
1128
|
return t;
|
|
1101
|
-
},
|
|
1129
|
+
}, io = (e) => {
|
|
1102
1130
|
if (e === null || typeof e != "object") throw new TypeError("[markdown-explorer] createFileSystemSource needs an options object");
|
|
1103
|
-
for (const
|
|
1104
|
-
if (!
|
|
1105
|
-
const { id: t, label: r, root: n, parse:
|
|
1131
|
+
for (const i of Object.keys(e))
|
|
1132
|
+
if (!Jn.has(i)) throw new TypeError(`[markdown-explorer] createFileSystemSource does not know the option ${JSON.stringify(i)}`);
|
|
1133
|
+
const { id: t, label: r, root: n, parse: a } = e;
|
|
1106
1134
|
if (typeof t != "string" || t === "") throw new TypeError("[markdown-explorer] a file-system source needs a non-empty id");
|
|
1107
1135
|
if (typeof r != "string" || r.trim() === "") throw new TypeError("[markdown-explorer] a file-system source needs a non-blank label");
|
|
1108
|
-
if (typeof
|
|
1109
|
-
if (typeof n != "string" ||
|
|
1136
|
+
if (typeof a != "function") throw new TypeError("[markdown-explorer] a file-system source needs a parse function");
|
|
1137
|
+
if (typeof n != "string" || !$e(n)) throw new RangeError(`[markdown-explorer] root must be an absolute path; got ${JSON.stringify(n)}`);
|
|
1110
1138
|
let o;
|
|
1111
1139
|
try {
|
|
1112
|
-
o =
|
|
1113
|
-
} catch (
|
|
1114
|
-
throw new RangeError(`[markdown-explorer] root does not exist: ${n}`, { cause:
|
|
1140
|
+
o = er(n).isDirectory();
|
|
1141
|
+
} catch (i) {
|
|
1142
|
+
throw new RangeError(`[markdown-explorer] root does not exist: ${n}`, { cause: i });
|
|
1115
1143
|
}
|
|
1116
1144
|
if (!o) throw new RangeError(`[markdown-explorer] root is not a directory: ${n}`);
|
|
1117
|
-
const c =
|
|
1118
|
-
if (typeof
|
|
1119
|
-
if (
|
|
1120
|
-
throw new RangeError(`[markdown-explorer] pathPrefix must be empty or relative segments ending with "/"; got ${JSON.stringify(
|
|
1121
|
-
const g =
|
|
1145
|
+
const c = tr(n), f = e.pathPrefix;
|
|
1146
|
+
if (typeof f != "string") throw new TypeError('[markdown-explorer] pathPrefix is required: "" for a source over the whole root, or relative segments ending with "/"');
|
|
1147
|
+
if (f !== "" && !(f.endsWith("/") && st(f.slice(0, -1))))
|
|
1148
|
+
throw new RangeError(`[markdown-explorer] pathPrefix must be empty or relative segments ending with "/"; got ${JSON.stringify(f)}`);
|
|
1149
|
+
const g = ct(e.deny), T = he("maxEntries", e.maxEntries, we.maxEntries), d = he("maxDocumentBytes", e.maxDocumentBytes, we.maxDocumentBytes), p = he("textPreviewBytes", e.textPreviewBytes, we.textPreviewBytes), _ = he("walkConcurrency", e.walkConcurrency, we.walkConcurrency), R = e.cacheScope === void 0 ? "principal" : e.cacheScope;
|
|
1122
1150
|
if (R !== "principal" && R !== "shared") throw new RangeError(`[markdown-explorer] cacheScope must be "principal" or "shared"; got ${JSON.stringify(R)}`);
|
|
1123
1151
|
const I = e.reportSkipped;
|
|
1124
1152
|
if (I !== void 0 && typeof I != "function") throw new TypeError("[markdown-explorer] reportSkipped must be a function");
|
|
1125
|
-
const O = I === void 0 ? (
|
|
1126
|
-
console.warn(`[markdown-explorer] the file-system source ${JSON.stringify(
|
|
1127
|
-
} : I, x = /* @__PURE__ */ new Set(),
|
|
1128
|
-
const E =
|
|
1129
|
-
return
|
|
1130
|
-
},
|
|
1131
|
-
const
|
|
1132
|
-
x.has(
|
|
1133
|
-
}, N = async ({ signal:
|
|
1134
|
-
|
|
1135
|
-
const E = new AbortController(),
|
|
1136
|
-
E.abort(
|
|
1153
|
+
const O = I === void 0 ? (i) => {
|
|
1154
|
+
console.warn(`[markdown-explorer] the file-system source ${JSON.stringify(i.sourceId)} does not list ${JSON.stringify(i.path)}: ${i.reason}`);
|
|
1155
|
+
} : I, x = /* @__PURE__ */ new Set(), P = (i) => {
|
|
1156
|
+
const E = or(c, ...i.split("/")), S = je(c, E);
|
|
1157
|
+
return S === "" || S === ".." || S.startsWith(`..${Le}`) || $e(S) ? null : E;
|
|
1158
|
+
}, b = (i, E) => {
|
|
1159
|
+
const S = `${E}\0${i}`;
|
|
1160
|
+
x.has(S) || x.size >= T || (x.add(S), O({ sourceId: t, path: i, reason: E }));
|
|
1161
|
+
}, N = async ({ signal: i }) => {
|
|
1162
|
+
i.throwIfAborted();
|
|
1163
|
+
const E = new AbortController(), S = () => {
|
|
1164
|
+
E.abort(i.reason);
|
|
1137
1165
|
};
|
|
1138
|
-
|
|
1139
|
-
const k =
|
|
1166
|
+
i.addEventListener("abort", S, { once: !0 });
|
|
1167
|
+
const k = Xn(_);
|
|
1140
1168
|
let A = 0;
|
|
1141
1169
|
const D = async (M, U) => {
|
|
1142
|
-
const
|
|
1170
|
+
const X = await k(async () => {
|
|
1143
1171
|
E.signal.throwIfAborted();
|
|
1144
1172
|
try {
|
|
1145
|
-
return await
|
|
1173
|
+
return await cr(M, { withFileTypes: !0 });
|
|
1146
1174
|
} catch ($) {
|
|
1147
|
-
if (U === "" || !
|
|
1175
|
+
if (U === "" || !Hn($)) throw $;
|
|
1148
1176
|
return null;
|
|
1149
1177
|
}
|
|
1150
1178
|
});
|
|
1151
|
-
if (E.signal.throwIfAborted(),
|
|
1152
|
-
return
|
|
1153
|
-
const
|
|
1154
|
-
for (const $ of
|
|
1179
|
+
if (E.signal.throwIfAborted(), X === null)
|
|
1180
|
+
return b(`${f}${U}`, "unreadable-directory"), null;
|
|
1181
|
+
const F = [];
|
|
1182
|
+
for (const $ of X) {
|
|
1155
1183
|
if (g.deniesName($.name)) continue;
|
|
1156
|
-
const J = U === "" ? $.name : `${U}/${$.name}`,
|
|
1157
|
-
if (
|
|
1158
|
-
|
|
1184
|
+
const J = U === "" ? $.name : `${U}/${$.name}`, K = `${f}${J}`, W = Gn($, K);
|
|
1185
|
+
if (W !== null) {
|
|
1186
|
+
b(K, W);
|
|
1159
1187
|
continue;
|
|
1160
1188
|
}
|
|
1161
|
-
|
|
1189
|
+
F.push({ dirent: $, relativePath: J, path: K });
|
|
1162
1190
|
}
|
|
1163
|
-
if (A +=
|
|
1164
|
-
return
|
|
1165
|
-
|
|
1166
|
-
if (!$.isDirectory()) return { name: $.name, path:
|
|
1167
|
-
const
|
|
1168
|
-
return
|
|
1191
|
+
if (A += F.length, A > T) throw new Ee("too-large");
|
|
1192
|
+
return F.sort(($, J) => Un($.dirent.name, J.dirent.name)), (await Promise.all(
|
|
1193
|
+
F.map(async ({ dirent: $, relativePath: J, path: K }) => {
|
|
1194
|
+
if (!$.isDirectory()) return { name: $.name, path: K, type: "file" };
|
|
1195
|
+
const W = await D(nr(M, $.name), J);
|
|
1196
|
+
return W === null ? null : { name: $.name, path: K, type: "directory", children: W };
|
|
1169
1197
|
})
|
|
1170
1198
|
)).filter(($) => $ !== null);
|
|
1171
1199
|
};
|
|
@@ -1176,78 +1204,78 @@ const br = (e) => e instanceof Error && Object.hasOwn(e, it) && et(e.reason), Sr
|
|
|
1176
1204
|
} catch (M) {
|
|
1177
1205
|
throw E.abort(), M;
|
|
1178
1206
|
} finally {
|
|
1179
|
-
|
|
1207
|
+
i.removeEventListener("abort", S);
|
|
1180
1208
|
}
|
|
1181
|
-
},
|
|
1182
|
-
if (!
|
|
1183
|
-
const E =
|
|
1184
|
-
if (!
|
|
1209
|
+
}, C = async (i) => {
|
|
1210
|
+
if (!i.startsWith(f)) return { status: "refused", reason: "not-found" };
|
|
1211
|
+
const E = i.slice(f.length);
|
|
1212
|
+
if (!st(E)) return { status: "refused", reason: "invalid" };
|
|
1185
1213
|
if (g.deniesPath(E)) return { status: "refused", reason: "forbidden" };
|
|
1186
|
-
const
|
|
1187
|
-
if (
|
|
1188
|
-
const k = await
|
|
1214
|
+
const S = P(E);
|
|
1215
|
+
if (S === null) return { status: "refused", reason: "forbidden" };
|
|
1216
|
+
const k = await ar(S, { bigint: !0 });
|
|
1189
1217
|
if (k.isSymbolicLink()) return { status: "refused", reason: "forbidden" };
|
|
1190
1218
|
if (!k.isFile()) return { status: "refused", reason: "unsupported" };
|
|
1191
|
-
const A = await
|
|
1192
|
-
return A !==
|
|
1193
|
-
},
|
|
1219
|
+
const A = await ir(S);
|
|
1220
|
+
return A !== S || g.deniesPath(je(c, A).split(Le).join("/")) ? { status: "refused", reason: "forbidden" } : { status: "file", target: S, name: E.slice(E.lastIndexOf("/") + 1), stats: k };
|
|
1221
|
+
}, z = async (i, E, S) => {
|
|
1194
1222
|
let k;
|
|
1195
1223
|
try {
|
|
1196
|
-
const A = await
|
|
1224
|
+
const A = await C(S);
|
|
1197
1225
|
if (A.status === "refused") return ee(A.reason);
|
|
1198
|
-
const { target: D, name: M, stats: U } = A,
|
|
1199
|
-
if (
|
|
1200
|
-
if (
|
|
1201
|
-
if (
|
|
1202
|
-
k = await
|
|
1226
|
+
const { target: D, name: M, stats: U } = A, X = Dn(M), F = ot(U);
|
|
1227
|
+
if (X.kind === "image") return { kind: "image", name: M, version: F };
|
|
1228
|
+
if (X.kind === "media") return { kind: "media", name: M, mediaType: X.mediaType, version: F };
|
|
1229
|
+
if (X.kind === "file") return { kind: "file", name: M, version: F };
|
|
1230
|
+
k = await lt(D, ie.O_RDONLY | bt | St);
|
|
1203
1231
|
const Z = await k.stat({ bigint: !0 });
|
|
1204
1232
|
if (!Z.isFile() || Z.ino !== U.ino || Z.dev !== U.dev) return ee("forbidden");
|
|
1205
|
-
if (
|
|
1206
|
-
if (Z.size > BigInt(
|
|
1207
|
-
const
|
|
1208
|
-
if (
|
|
1209
|
-
const
|
|
1210
|
-
return { kind: "markdown", name: M, htmlContent:
|
|
1233
|
+
if (X.kind === "markdown") {
|
|
1234
|
+
if (Z.size > BigInt(d)) return ee("too-large");
|
|
1235
|
+
const W = await at(k, d + 1);
|
|
1236
|
+
if (W.length > d) return ee("too-large");
|
|
1237
|
+
const le = await a(new TextDecoder("utf-8").decode(W), E);
|
|
1238
|
+
return { kind: "markdown", name: M, htmlContent: le.htmlContent, headings: le.headings };
|
|
1211
1239
|
}
|
|
1212
|
-
const $ = await
|
|
1213
|
-
if ($.includes(0)) return { kind: "file", name: M, version:
|
|
1214
|
-
const J = Z.size > BigInt(
|
|
1215
|
-
return { kind: "text", name: M, text:
|
|
1240
|
+
const $ = await at(k, p);
|
|
1241
|
+
if ($.includes(0)) return { kind: "file", name: M, version: F };
|
|
1242
|
+
const J = Z.size > BigInt(p), K = new TextDecoder("utf-8").decode($, { stream: J });
|
|
1243
|
+
return { kind: "text", name: M, text: K, truncated: J, version: F };
|
|
1216
1244
|
} catch (A) {
|
|
1217
|
-
const D =
|
|
1245
|
+
const D = xt(A);
|
|
1218
1246
|
if (D === null) throw A;
|
|
1219
1247
|
return ee(D);
|
|
1220
1248
|
} finally {
|
|
1221
1249
|
await k?.close();
|
|
1222
1250
|
}
|
|
1223
|
-
}, u = async (
|
|
1251
|
+
}, u = async (i, E, S) => {
|
|
1224
1252
|
let k;
|
|
1225
1253
|
try {
|
|
1226
|
-
k = await
|
|
1227
|
-
} catch (
|
|
1228
|
-
throw
|
|
1254
|
+
k = await C(S);
|
|
1255
|
+
} catch (F) {
|
|
1256
|
+
throw Ot(F);
|
|
1229
1257
|
}
|
|
1230
1258
|
if (k.status === "refused") throw new Ee(k.reason);
|
|
1231
|
-
const { target: A, name: D, stats: M } = k, U =
|
|
1232
|
-
return { name: D, contentType:
|
|
1259
|
+
const { target: A, name: D, stats: M } = k, U = yt.get(Tt(D)), X = U === void 0 ? "" : U.contentType;
|
|
1260
|
+
return { name: D, contentType: X, size: Number(M.size), version: ot(M), open: (F) => Fn(A, M, F, i.signal) };
|
|
1233
1261
|
};
|
|
1234
1262
|
return Object.freeze({
|
|
1235
1263
|
id: t,
|
|
1236
1264
|
label: r,
|
|
1237
1265
|
cacheScope: R,
|
|
1238
|
-
boundary: Object.freeze({ contains: (
|
|
1266
|
+
boundary: Object.freeze({ contains: (i) => i.startsWith(f) }),
|
|
1239
1267
|
tree: N,
|
|
1240
|
-
document:
|
|
1268
|
+
document: z,
|
|
1241
1269
|
asset: u
|
|
1242
1270
|
});
|
|
1243
1271
|
};
|
|
1244
1272
|
export {
|
|
1245
|
-
|
|
1273
|
+
lo as DEFAULT_DENIED_PATH_SEGMENTS,
|
|
1246
1274
|
Ee as ExplorerError,
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1275
|
+
fo as MAX_DOCUMENT_PATH_LENGTH,
|
|
1276
|
+
so as anonymousAccess,
|
|
1277
|
+
io as createFileSystemSource,
|
|
1278
|
+
ao as createMarkdownExplorer,
|
|
1251
1279
|
te as isAcceptableDocumentPath,
|
|
1252
|
-
|
|
1280
|
+
mo as isDeniedPath
|
|
1253
1281
|
};
|