visualproof 0.0.0-stage → 0.2.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/LICENSE +202 -0
- package/README.md +99 -2
- package/dist/assets/fonts/JuliaMono-Regular.ttf +0 -0
- package/dist/assets/fonts/JuliaMono-RegularItalic.ttf +0 -0
- package/dist/assets/fonts/JuliaMono-SemiBold.ttf +0 -0
- package/dist/assets/fonts/LICENSE-JuliaMono.txt +93 -0
- package/dist/assets/latex/visualproof.sty +308 -0
- package/dist/assets/webfonts/LICENSE-Jost.txt +93 -0
- package/dist/assets/webfonts/LICENSE-SourceSans3.txt +93 -0
- package/dist/assets/webfonts/LICENSE-SourceSerif4.txt +93 -0
- package/dist/assets/webfonts/jost-latin-ext-wght-normal.woff2 +0 -0
- package/dist/assets/webfonts/jost-latin-wght-normal.woff2 +0 -0
- package/dist/assets/webfonts/source-sans-3-greek-wght-italic.woff2 +0 -0
- package/dist/assets/webfonts/source-sans-3-greek-wght-normal.woff2 +0 -0
- package/dist/assets/webfonts/source-sans-3-latin-ext-wght-italic.woff2 +0 -0
- package/dist/assets/webfonts/source-sans-3-latin-ext-wght-normal.woff2 +0 -0
- package/dist/assets/webfonts/source-sans-3-latin-wght-italic.woff2 +0 -0
- package/dist/assets/webfonts/source-sans-3-latin-wght-normal.woff2 +0 -0
- package/dist/assets/webfonts/source-serif-4-greek-wght-italic.woff2 +0 -0
- package/dist/assets/webfonts/source-serif-4-greek-wght-normal.woff2 +0 -0
- package/dist/assets/webfonts/source-serif-4-latin-ext-wght-italic.woff2 +0 -0
- package/dist/assets/webfonts/source-serif-4-latin-ext-wght-normal.woff2 +0 -0
- package/dist/assets/webfonts/source-serif-4-latin-wght-italic.woff2 +0 -0
- package/dist/assets/webfonts/source-serif-4-latin-wght-normal.woff2 +0 -0
- package/dist/extractor/Extract.lean +1180 -0
- package/dist/extractor/Usage.lean +900 -0
- package/dist/mcp/visualproof-mcp.mjs +476 -0
- package/dist/server/access.js +95 -0
- package/dist/server/agent.js +3270 -0
- package/dist/server/agentdocs.js +35 -0
- package/dist/server/agentlibrary.js +202 -0
- package/dist/server/agentnotes.js +701 -0
- package/dist/server/agentviews.js +703 -0
- package/dist/server/askfollow.js +286 -0
- package/dist/server/boardspots.js +159 -0
- package/dist/server/dirs.js +175 -0
- package/dist/server/doctor.js +186 -0
- package/dist/server/events.js +198 -0
- package/dist/server/export.js +420 -0
- package/dist/server/extract.js +204 -0
- package/dist/server/fetchurl.js +321 -0
- package/dist/server/figures.js +158 -0
- package/dist/server/history.js +780 -0
- package/dist/server/historyapi.js +304 -0
- package/dist/server/hover.js +595 -0
- package/dist/server/identity.js +114 -0
- package/dist/server/invites.js +241 -0
- package/dist/server/layoutthread.js +51 -0
- package/dist/server/library.js +600 -0
- package/dist/server/libraryroute.js +211 -0
- package/dist/server/live.js +1101 -0
- package/dist/server/lostfound.js +199 -0
- package/dist/server/main.js +1406 -0
- package/dist/server/mcptools.js +496 -0
- package/dist/server/pastdoc.js +73 -0
- package/dist/server/pipeline.js +159 -0
- package/dist/server/poppler.js +251 -0
- package/dist/server/presence.js +252 -0
- package/dist/server/project.js +222 -0
- package/dist/server/prompt.js +53 -0
- package/dist/server/readonly.js +152 -0
- package/dist/server/readonlygate.js +123 -0
- package/dist/server/renames.js +268 -0
- package/dist/server/review.js +334 -0
- package/dist/server/reviewapi.js +188 -0
- package/dist/server/snapshot.js +50 -0
- package/dist/server/sourcestore.js +473 -0
- package/dist/server/status.js +360 -0
- package/dist/server/talk.js +476 -0
- package/dist/server/tokens.js +221 -0
- package/dist/server/usage.js +501 -0
- package/dist/server/versions.js +82 -0
- package/dist/server/view.js +164 -0
- package/dist/server/views.js +1167 -0
- package/dist/shared/askfollow.js +46 -0
- package/dist/shared/board.js +332 -0
- package/dist/shared/build.js +2 -0
- package/dist/shared/collab.js +241 -0
- package/dist/shared/comment.js +285 -0
- package/dist/shared/context.js +157 -0
- package/dist/shared/doc/authors.js +98 -0
- package/dist/shared/doc/build.js +358 -0
- package/dist/shared/doc/latex.js +455 -0
- package/dist/shared/doc/markdown.js +159 -0
- package/dist/shared/doc/model.js +37 -0
- package/dist/shared/doc/order.js +124 -0
- package/dist/shared/doc/outline.js +45 -0
- package/dist/shared/doc/proof.js +272 -0
- package/dist/shared/doc/sections.js +93 -0
- package/dist/shared/doc/settings.js +75 -0
- package/dist/shared/edgecodec.js +102 -0
- package/dist/shared/editor.js +64 -0
- package/dist/shared/excerpt.js +87 -0
- package/dist/shared/graph.js +16 -0
- package/dist/shared/items.js +92 -0
- package/dist/shared/keys.js +203 -0
- package/dist/shared/layout.js +926 -0
- package/dist/shared/lexer.js +130 -0
- package/dist/shared/library.js +711 -0
- package/dist/shared/lostfound.js +150 -0
- package/dist/shared/moves.js +244 -0
- package/dist/shared/notecard.js +295 -0
- package/dist/shared/noteplace.js +505 -0
- package/dist/shared/palette.js +46 -0
- package/dist/shared/pastgraph.js +103 -0
- package/dist/shared/places.js +80 -0
- package/dist/shared/projectnotes.js +244 -0
- package/dist/shared/query.js +818 -0
- package/dist/shared/review.js +642 -0
- package/dist/shared/reviewqueue.js +333 -0
- package/dist/shared/ring.js +366 -0
- package/dist/shared/scope.js +255 -0
- package/dist/shared/scopeedit.js +81 -0
- package/dist/shared/search.js +191 -0
- package/dist/shared/separate.js +178 -0
- package/dist/shared/sharesnap.js +281 -0
- package/dist/shared/sticky.js +501 -0
- package/dist/shared/timeline.js +73 -0
- package/dist/shared/titles.js +300 -0
- package/dist/shared/viewgraph.js +157 -0
- package/dist/shared/views.js +859 -0
- package/dist/web/bundle/app.js +52671 -0
- package/dist/web/bundle/edges.js +407 -0
- package/dist/web/bundle/layout.js +1010 -0
- package/dist/web/bundle/pdfworker.js +48322 -0
- package/dist/web/index.html +257 -0
- package/dist/web/src/motion.js +430 -0
- package/dist/web/src/past.js +224 -0
- package/dist/web/src/scene.js +804 -0
- package/dist/web/style.css +2548 -0
- package/docs/agent-api/README.md +91 -0
- package/docs/agent-api/advice.md +108 -0
- package/docs/agent-api/export.md +120 -0
- package/docs/agent-api/following.md +140 -0
- package/docs/agent-api/history.md +142 -0
- package/docs/agent-api/library.md +65 -0
- package/docs/agent-api/mcp.md +104 -0
- package/docs/agent-api/notes.md +93 -0
- package/docs/agent-api/reading.md +166 -0
- package/docs/agent-api/requests.md +94 -0
- package/docs/agent-api/talking.md +100 -0
- package/docs/agent-api/tcb.md +135 -0
- package/docs/agent-api/views.md +125 -0
- package/docs/agent-api/words.md +56 -0
- package/package.json +39 -4
- package/test/toolchains.json +5 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// A share link that carries exactly what the sharer sees (CollabSpec decision 7, the owner's decision
|
|
2
|
+
// of 2026-10-12): a snapshot of the parts following carries (web/src/surfaces.ts), which the page that
|
|
3
|
+
// opens the link applies once, as starting to follow applies them, after which the reader is free.
|
|
4
|
+
//
|
|
5
|
+
// It travels in the address's fragment, which no server sees, after the address as a page writes it
|
|
6
|
+
// (web/src/state.ts): `#v=…&mode=…&sel=…&snap=<data>`. The two agree, being written from one state
|
|
7
|
+
// at once: a page that reads the snapshot takes the board from the address first (its query's build,
|
|
8
|
+
// `at=`, is in no snapshot, presence not carrying it), then everything else from the snapshot; a page
|
|
9
|
+
// that cannot read it opens the address as before. The access link's key and a personal link's token
|
|
10
|
+
// are in the path before it, and stay as they are.
|
|
11
|
+
//
|
|
12
|
+
// The snapshot is each followed part in a form of its own (`SNAPSHOT_FORMS`, one per part of the
|
|
13
|
+
// registry): the fields as presence carries them (shared/collab.ts, PresenceView), checked on the way
|
|
14
|
+
// back as the server checks a heartbeat (`cleanView`), but for two. The camera travels as the region
|
|
15
|
+
// of the board it showed — its centre, and the width and height shown, in the board's world — and is
|
|
16
|
+
// fitted into the opener's screen, so that another screen shows the same region, not the same zoom.
|
|
17
|
+
// The visitors leave out where they were drawn in the world, which only pages from before 2026-10-11
|
|
18
|
+
// read. Written as JSON, deflated where that is shorter, in base64url: `z` and the deflated bytes, or
|
|
19
|
+
// `j` and the JSON's.
|
|
20
|
+
import { cleanView } from "./collab.js";
|
|
21
|
+
/** the fragment's part that holds the snapshot */
|
|
22
|
+
export const SNAP_PARAM = "snap";
|
|
23
|
+
/** the most characters of a snapshot a page reads, and the most bytes it inflates to: a link is
|
|
24
|
+
* anyone's to write, and what the page is handed stays small whatever it says */
|
|
25
|
+
export const SNAPSHOT_MAX = 32_000;
|
|
26
|
+
const INFLATED_MAX = 256_000;
|
|
27
|
+
/** the camera's zoom as the canvas bounds it */
|
|
28
|
+
const K_MIN = 0.02, K_MAX = 12;
|
|
29
|
+
/** The room a screen leaves for the board: where it is and how large; the whole canvas where the
|
|
30
|
+
* panels leave none. */
|
|
31
|
+
function room(s) {
|
|
32
|
+
const w = s.w - s.insets.left - s.insets.right;
|
|
33
|
+
const h = s.h - s.insets.top - s.insets.bottom;
|
|
34
|
+
if (!(w > 0 && h > 0))
|
|
35
|
+
return { cx: s.w / 2, cy: s.h / 2, w: Math.max(1, s.w), h: Math.max(1, s.h) };
|
|
36
|
+
return { cx: s.insets.left + w / 2, cy: s.insets.top + h / 2, w, h };
|
|
37
|
+
}
|
|
38
|
+
/** The region of the board camera `cam` shows on screen `s`: the room the panels leave. */
|
|
39
|
+
export function regionOf(cam, s) {
|
|
40
|
+
const r = room(s);
|
|
41
|
+
return { x: (r.cx - s.w / 2) / cam.k + cam.x, y: (r.cy - s.h / 2) / cam.k + cam.y, w: r.w / cam.k, h: r.h / cam.k };
|
|
42
|
+
}
|
|
43
|
+
/** The camera that shows region `g` on screen `s`: all of it, centred in the room the panels leave,
|
|
44
|
+
* as large as that room allows. */
|
|
45
|
+
export function camOf(g, s) {
|
|
46
|
+
const r = room(s);
|
|
47
|
+
const k = Math.min(K_MAX, Math.max(K_MIN, Math.min(r.w / g.w, r.h / g.h)));
|
|
48
|
+
return { x: g.x - (r.cx - s.w / 2) / k, y: g.y - (r.cy - s.h / 2) / k, k };
|
|
49
|
+
}
|
|
50
|
+
/** A camera and a screen that show exactly region `g`: for a region given as one (an agent's link),
|
|
51
|
+
* which the camera's form writes as it is. */
|
|
52
|
+
export function showing(g) {
|
|
53
|
+
return { cam: { x: g.x, y: g.y, k: 1 }, screen: { w: g.w, h: g.h, insets: { left: 0, right: 0, top: 0, bottom: 0 } } };
|
|
54
|
+
}
|
|
55
|
+
/** A region as a link writes it, `[x, y, w, h]`, read back: null where it is not one. */
|
|
56
|
+
export function readRegion(x) {
|
|
57
|
+
if (!Array.isArray(x) || x.length !== 4 || !x.every((n) => typeof n === "number" && Number.isFinite(n) && Math.abs(n) < 1e9))
|
|
58
|
+
return null;
|
|
59
|
+
const [cx, cy, w, h] = x;
|
|
60
|
+
return w > 0 && h > 0 ? { x: cx, y: cy, w, h } : null;
|
|
61
|
+
}
|
|
62
|
+
/** A part carried as presence carries it: each of its fields the page says, read back as the server
|
|
63
|
+
* reads a heartbeat. */
|
|
64
|
+
const same = (...fields) => ({
|
|
65
|
+
fields,
|
|
66
|
+
write: (v) => Object.fromEntries(fields.filter((f) => v[f] !== undefined).map((f) => [f, v[f]])),
|
|
67
|
+
read: (o, clean) => Object.fromEntries(fields.filter((f) => f in o && clean[f] !== undefined).map((f) => [f, clean[f]])),
|
|
68
|
+
});
|
|
69
|
+
/** to a tenth of a unit of the board's world: a tenth of a pixel at the closest zoom but one */
|
|
70
|
+
const tenth = (n) => Math.round(n * 10) / 10;
|
|
71
|
+
/** The form of every followed part, by its name in the registry (web/src/surfaces.ts, which holds the
|
|
72
|
+
* two to each other): a part declared followed without one does not typecheck there, and
|
|
73
|
+
* test/sharesnap.test.ts fails. */
|
|
74
|
+
export const SNAPSHOT_FORMS = {
|
|
75
|
+
view: same("view", "scope", "clusterMode"),
|
|
76
|
+
history: same("history"),
|
|
77
|
+
// the region the camera showed, fitted into the opener's screen
|
|
78
|
+
camera: {
|
|
79
|
+
fields: ["cam"],
|
|
80
|
+
write: (v, screen) => {
|
|
81
|
+
if (!v.cam || !screen)
|
|
82
|
+
return {};
|
|
83
|
+
const g = regionOf(v.cam, screen);
|
|
84
|
+
return { region: [tenth(g.x), tenth(g.y), tenth(g.w), tenth(g.h)] };
|
|
85
|
+
},
|
|
86
|
+
read: (o, _clean, screen) => {
|
|
87
|
+
const g = readRegion(o.region);
|
|
88
|
+
return g && screen ? { cam: camOf(g, screen) } : {};
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
tool: same("mode"),
|
|
92
|
+
selection: same("selection"),
|
|
93
|
+
focus: same("focus"),
|
|
94
|
+
// each visitor's declaration, what it was reached from and its place; not where it was drawn in the
|
|
95
|
+
// world, which only pages from before 2026-10-11 read
|
|
96
|
+
visitors: {
|
|
97
|
+
fields: ["visitors"],
|
|
98
|
+
write: (v) => (v.visitors?.length ? { visitors: v.visitors.map(({ at: _at, ...rest }) => rest) } : {}),
|
|
99
|
+
read: (o, clean) => ("visitors" in o && clean.visitors ? { visitors: clean.visitors } : {}),
|
|
100
|
+
},
|
|
101
|
+
panel: same("sel"),
|
|
102
|
+
sources: same("source", "sourceCollapsed", "pinned"),
|
|
103
|
+
paper: same("paper"),
|
|
104
|
+
activity: same("activity"),
|
|
105
|
+
lostFound: same("lostFound"),
|
|
106
|
+
colouring: same("scheme"),
|
|
107
|
+
edges: same("edges"),
|
|
108
|
+
linear: same("linear"),
|
|
109
|
+
artwork: same("artwork"),
|
|
110
|
+
};
|
|
111
|
+
/** The snapshot of where a page is (`v`, as presence carries it: the registry's parts, read by the
|
|
112
|
+
* page), as a link carries it after `snap=`. */
|
|
113
|
+
export async function writeSnapshot(v, screen) {
|
|
114
|
+
const o = {};
|
|
115
|
+
for (const form of Object.values(SNAPSHOT_FORMS))
|
|
116
|
+
Object.assign(o, form.write(v, screen));
|
|
117
|
+
const json = new TextEncoder().encode(JSON.stringify(o));
|
|
118
|
+
const z = await deflate(json);
|
|
119
|
+
return z && z.length < json.length ? "z" + base64url(z) : "j" + base64url(json);
|
|
120
|
+
}
|
|
121
|
+
/** A link's snapshot read back for a page on `screen`: null where it cannot be read (written by
|
|
122
|
+
* hand, cut short, too long, or deflated where this browser cannot inflate). */
|
|
123
|
+
export async function readSnapshot(text, screen) {
|
|
124
|
+
if (text.length < 2 || text.length > SNAPSHOT_MAX)
|
|
125
|
+
return null;
|
|
126
|
+
const bytes = unbase64url(text.slice(1));
|
|
127
|
+
if (!bytes)
|
|
128
|
+
return null;
|
|
129
|
+
const json = text[0] === "z" ? await inflate(bytes) : text[0] === "j" ? bytes : null;
|
|
130
|
+
if (!json)
|
|
131
|
+
return null;
|
|
132
|
+
let o;
|
|
133
|
+
try {
|
|
134
|
+
o = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(json));
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
if (!o || typeof o !== "object" || Array.isArray(o))
|
|
140
|
+
return null;
|
|
141
|
+
const raw = o;
|
|
142
|
+
const clean = cleanView({ ...raw, cam: null, tabs: [] });
|
|
143
|
+
const view = {};
|
|
144
|
+
for (const form of Object.values(SNAPSHOT_FORMS))
|
|
145
|
+
Object.assign(view, form.read(raw, clean, screen));
|
|
146
|
+
return { view, region: readRegion(raw.region) };
|
|
147
|
+
}
|
|
148
|
+
/** The snapshot a fragment carries (`snap=`), or null. */
|
|
149
|
+
export function snapshotIn(hash) {
|
|
150
|
+
const text = hash.startsWith("#") ? hash.slice(1) : hash;
|
|
151
|
+
return text ? new URLSearchParams(text).get(SNAP_PARAM) : null;
|
|
152
|
+
}
|
|
153
|
+
/** A fragment (`#…`, or none) with the snapshot after the address it holds. */
|
|
154
|
+
export function withSnapshot(hash, snap) {
|
|
155
|
+
const text = (hash.startsWith("#") ? hash.slice(1) : hash).split("&").filter((p) => p && !p.startsWith(`${SNAP_PARAM}=`)).join("&");
|
|
156
|
+
return `#${text ? text + "&" : ""}${SNAP_PARAM}=${snap}`;
|
|
157
|
+
}
|
|
158
|
+
/** The address as a page writes it (web/src/state.ts), for a link made off the page (an agent's): the
|
|
159
|
+
* view by its id, or by its query and the build it shows where nobody keeps it; the tool; the
|
|
160
|
+
* selection; and the declaration the panel tells of. */
|
|
161
|
+
export function addressOf(v) {
|
|
162
|
+
const p = new URLSearchParams();
|
|
163
|
+
if (v.scope) {
|
|
164
|
+
p.set("q", v.scope);
|
|
165
|
+
if (typeof v.version === "number")
|
|
166
|
+
p.set("at", String(v.version));
|
|
167
|
+
}
|
|
168
|
+
else if (v.view)
|
|
169
|
+
p.set("v", v.view);
|
|
170
|
+
p.set("mode", v.mode);
|
|
171
|
+
if (v.selection)
|
|
172
|
+
p.set("sel", v.selection);
|
|
173
|
+
if (v.sel)
|
|
174
|
+
p.set("s", v.sel);
|
|
175
|
+
return p.toString();
|
|
176
|
+
}
|
|
177
|
+
/** A snapshot sorted out before it is applied, by what the opener's page has: a build the history no
|
|
178
|
+
* longer holds, and a file of the library or a link no longer there, are taken out — the Time
|
|
179
|
+
* Machine would open at the latest build instead, the reference window say there is no such file —
|
|
180
|
+
* and said; the reference window opens on its file at its page where only the link is gone. */
|
|
181
|
+
export function sortOut(v, has) {
|
|
182
|
+
const view = { ...v };
|
|
183
|
+
const gone = {};
|
|
184
|
+
if (view.history && !has.build(view.history.snapshot)) {
|
|
185
|
+
delete view.history;
|
|
186
|
+
gone.build = true;
|
|
187
|
+
}
|
|
188
|
+
if (view.paper && !has.file(view.paper.file)) {
|
|
189
|
+
delete view.paper;
|
|
190
|
+
gone.file = true;
|
|
191
|
+
}
|
|
192
|
+
else if (view.paper?.link && !has.link(view.paper.link)) {
|
|
193
|
+
const { link: _gone, ...rest } = view.paper;
|
|
194
|
+
view.paper = rest;
|
|
195
|
+
gone.link = true;
|
|
196
|
+
}
|
|
197
|
+
return { view, gone };
|
|
198
|
+
}
|
|
199
|
+
/** What a snapshot names that the board's graph does not hold, once it is applied — what the panel
|
|
200
|
+
* tells of, the declarations of the selection (`picked`), the visitors, the pinned windows — and a
|
|
201
|
+
* focus on what is no target now. */
|
|
202
|
+
export function goneNames(v, picked, has) {
|
|
203
|
+
const named = [v.sel, ...picked, ...(v.visitors ?? []).map((w) => w.decl), ...(v.pinned ?? []).map((w) => w.decl)];
|
|
204
|
+
const decls = [...new Set(named.filter((n) => !!n && !has.decl(n)))];
|
|
205
|
+
return { decls, ...(typeof v.focus === "string" && !has.target(v.focus) ? { focus: v.focus } : {}) };
|
|
206
|
+
}
|
|
207
|
+
/** What could not be shown of a link's snapshot, said in one short line; null where everything was. */
|
|
208
|
+
export function missingLine(m) {
|
|
209
|
+
const short = (n) => n.split(".").pop() ?? n;
|
|
210
|
+
const decls = [...new Set(m.decls ?? [])];
|
|
211
|
+
const said = [
|
|
212
|
+
m.view ? "its view" : null,
|
|
213
|
+
m.build ? "its build" : null,
|
|
214
|
+
decls.length ? `${decls.length === 1 ? "1 declaration" : `${decls.length} declarations`} (${decls.slice(0, 3).map(short).join(", ")}${decls.length > 3 ? ", …" : ""})` : null,
|
|
215
|
+
m.focus ? `the focus ★${short(m.focus)}` : null,
|
|
216
|
+
m.file ? "the reference file" : m.link ? "the link in the reference" : null,
|
|
217
|
+
].filter((x) => x !== null);
|
|
218
|
+
return said.length ? `Not shown from the link (no longer here): ${said.join(", ")}.` : null;
|
|
219
|
+
}
|
|
220
|
+
// ------------------------------------------------------------------ bytes
|
|
221
|
+
/** `bytes` deflated (raw), or null where the browser cannot. */
|
|
222
|
+
async function deflate(bytes) {
|
|
223
|
+
if (typeof CompressionStream === "undefined")
|
|
224
|
+
return null;
|
|
225
|
+
try {
|
|
226
|
+
const s = new Blob([bytes]).stream().pipeThrough(new CompressionStream("deflate-raw"));
|
|
227
|
+
return new Uint8Array(await new Response(s).arrayBuffer());
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/** `bytes` inflated, or null: where the browser cannot, where they are not deflated, or where they
|
|
234
|
+
* inflate to more than a snapshot ever holds. */
|
|
235
|
+
async function inflate(bytes) {
|
|
236
|
+
if (typeof DecompressionStream === "undefined")
|
|
237
|
+
return null;
|
|
238
|
+
try {
|
|
239
|
+
const reader = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("deflate-raw")).getReader();
|
|
240
|
+
const parts = [];
|
|
241
|
+
let total = 0;
|
|
242
|
+
for (;;) {
|
|
243
|
+
const { done, value } = await reader.read();
|
|
244
|
+
if (done)
|
|
245
|
+
break;
|
|
246
|
+
total += value.length;
|
|
247
|
+
if (total > INFLATED_MAX) {
|
|
248
|
+
void reader.cancel().catch(() => { });
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
parts.push(value);
|
|
252
|
+
}
|
|
253
|
+
const out = new Uint8Array(total);
|
|
254
|
+
let at = 0;
|
|
255
|
+
for (const p of parts) {
|
|
256
|
+
out.set(p, at);
|
|
257
|
+
at += p.length;
|
|
258
|
+
}
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function base64url(b) {
|
|
266
|
+
let s = "";
|
|
267
|
+
for (let i = 0; i < b.length; i += 0x8000)
|
|
268
|
+
s += String.fromCharCode(...b.subarray(i, i + 0x8000));
|
|
269
|
+
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
270
|
+
}
|
|
271
|
+
function unbase64url(t) {
|
|
272
|
+
if (!/^[A-Za-z0-9_-]*$/.test(t))
|
|
273
|
+
return null;
|
|
274
|
+
try {
|
|
275
|
+
const s = atob(t.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((t.length + 3) % 4));
|
|
276
|
+
return Uint8Array.from(s, (c) => c.charCodeAt(0));
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
}
|