@dev.fast/whiteboard 0.5.1 → 0.5.2-preview.20261104.147
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/dist/{account-alias-Bw3PT3Qo.js → account-alias-DXpd0Px8.js} +5 -5
- package/dist/{agent-cli-kHRrwO4n.js → agent-cli-COrZtFks.js} +9 -9
- package/dist/agent-cli-x6-Eh1S-.js +2 -0
- package/dist/{agent-client-CMacGbMp.js → agent-client-BFtYm3Ng.js} +4 -4
- package/dist/{authoring-tools-CiLy46kh.js → authoring-tools-D1avYp8-.js} +12 -8
- package/dist/{background-server-eZWXoVcI.js → background-server-BiRSqjxG.js} +4 -4
- package/dist/{background-server-BLNHbksB.js → background-server-HOJozY99.js} +1 -1
- package/dist/build-info.json +1 -1
- package/dist/{cli-Bxmiqutp.js → cli-CSlwGRvH.js} +1 -1
- package/dist/{cli-runner-DQPuu7kJ.js → cli-runner-CuffXA0Q.js} +22 -22
- package/dist/cli.js +2 -2
- package/dist/{fuzzy-match-DZjeOaPY.js → fuzzy-match-DEo941za.js} +3 -3
- package/dist/{headless-host-B4r0s1ks.js → headless-host-BCkXVVpU.js} +1 -1
- package/dist/{headless-host-Dl2KZ9IT.js → headless-host-CIuJJjmM.js} +561 -415
- package/dist/{import-YAIU6rB0.js → import-CjcW7EIQ.js} +235 -63
- package/dist/{local-data-C58vzLZR.js → local-data-B5PBZ0Ge.js} +101 -17
- package/dist/{mcp-COYWiGVn.js → mcp-Ba3WvhUh.js} +9 -6
- package/dist/{package-paths-Cg6fnaGp.js → package-paths-Dk7MOXxN.js} +1 -1
- package/dist/profile-BstQkIY0.js +2 -0
- package/dist/{profile--BYIjPkc.js → profile-CNMhLUhf.js} +2 -2
- package/dist/{remote-attach-C1prLjmj.js → remote-attach-DkZoYBHV.js} +4 -4
- package/dist/{remote-extensions-Bw029iIS.js → remote-extensions-BvV29_gk.js} +1 -1
- package/dist/{remote-extensions-MUUgbLJn.js → remote-extensions-L95HNwIH.js} +2 -2
- package/dist/{remote-language-server-z3rdHcLL.js → remote-language-server-l4ConTfj.js} +4 -4
- package/dist/{remote-uninstall-B3Slwor1.js → remote-uninstall-BAQM7cU8.js} +5 -5
- package/dist/{request-origin-B3_XI2VA.js → request-origin-DZexKlc4.js} +28 -5
- package/dist/{review-app-launcher-BZEbDKuK.js → review-app-launcher-B5F10y_4.js} +2 -2
- package/dist/{review-home-paths-LdGS4SpR.js → review-home-paths-BmZX2qiz.js} +1 -1
- package/dist/{review-telemetry-x8u-9EZb.js → review-telemetry-kQ7esDRE.js} +4 -4
- package/dist/server/desktop-host.js +26 -38
- package/dist/{server-discovery-WmuFl2S3.js → server-discovery-DYY_xeG9.js} +2 -2
- package/dist/sharing/index.d.ts +137 -14
- package/dist/sharing/index.js +1 -1
- package/dist/{src-DLWol7GZ.js → src-B-91Rd2d.js} +17 -1
- package/dist/{src-B2b8cCoI.js → src-DirPN_Qh.js} +82 -6
- package/dist/{stored-document-migration-iwIQ4l5G.js → stored-document-migration-vg0NAzXG.js} +4 -89
- package/dist/{thread-state-B7L1mpN_.js → thread-state-Jxoh1hde.js} +1 -1
- package/dist/{tool-failure-RLR8m9hq.js → tool-failure-BLBwcl3H.js} +319 -55
- package/instructions/animation.md +69 -5
- package/instructions/animations/before-after-structure/README.md +27 -0
- package/instructions/animations/before-after-structure/animation.html +89 -0
- package/instructions/animations/before-after-structure/block.json +10 -0
- package/instructions/animations/before-after-structure/frames.json +313 -0
- package/instructions/animations/cache-fill-evict/README.md +24 -0
- package/instructions/animations/cache-fill-evict/animation.html +81 -0
- package/instructions/animations/cache-fill-evict/block.json +13 -0
- package/instructions/animations/cache-fill-evict/frames.json +79 -0
- package/instructions/animations/cache-fill-evict/scenarios.json +17 -0
- package/instructions/animations/log-replay/README.md +25 -0
- package/instructions/animations/log-replay/animation.html +86 -0
- package/instructions/animations/log-replay/block.json +10 -0
- package/instructions/animations/log-replay/frames.json +143 -0
- package/instructions/animations/log-replay/scenarios.json +18 -0
- package/instructions/animations/queue-workers/README.md +23 -0
- package/instructions/animations/queue-workers/animation.html +107 -0
- package/instructions/animations/queue-workers/block.json +13 -0
- package/instructions/animations/queue-workers/frames.json +119 -0
- package/instructions/animations/queue-workers/scenarios.json +18 -0
- package/instructions/animations/race-two-lanes/README.md +22 -0
- package/instructions/animations/race-two-lanes/animation.html +97 -0
- package/instructions/animations/race-two-lanes/block.json +1 -0
- package/instructions/animations/race-two-lanes/frames.json +234 -0
- package/instructions/animations/race-two-lanes/scenarios.json +19 -0
- package/instructions/animations/request-across-layers/README.md +24 -0
- package/instructions/animations/request-across-layers/animation.html +85 -0
- package/instructions/animations/request-across-layers/block.json +17 -0
- package/instructions/animations/request-across-layers/frames.json +58 -0
- package/instructions/animations/request-across-layers/scenarios.json +12 -0
- package/instructions/animations/request-timing/README.md +22 -0
- package/instructions/animations/request-timing/animation.html +120 -0
- package/instructions/animations/request-timing/block.json +21 -0
- package/instructions/animations/request-timing/markers.json +32 -0
- package/instructions/animations/request-timing/scenarios.json +4 -0
- package/instructions/animations/retry-backoff/README.md +22 -0
- package/instructions/animations/retry-backoff/animation.html +122 -0
- package/instructions/animations/retry-backoff/block.json +10 -0
- package/instructions/animations/retry-backoff/frames.json +65 -0
- package/instructions/animations/retry-backoff/scenarios.json +12 -0
- package/instructions/animations/state-machine/README.md +24 -0
- package/instructions/animations/state-machine/animation.html +95 -0
- package/instructions/animations/state-machine/block.json +9 -0
- package/instructions/animations/state-machine/frames.json +79 -0
- package/instructions/animations/state-machine/scenarios.json +12 -0
- package/instructions/animations/video-platform-architecture/README.md +24 -0
- package/instructions/animations/video-platform-architecture/animation.html +76 -0
- package/instructions/animations/video-platform-architecture/block.json +17 -0
- package/instructions/animations/video-platform-architecture/frames.json +97 -0
- package/instructions/animations/video-platform-architecture/scenarios.json +28 -0
- package/instructions/whiteboard.d.ts +168 -14
- package/package.json +5 -5
- package/src/animation-stage-kit.ts +150 -10
- package/src/cli-runner.ts +4 -1
- package/src/fixtures/blocks/clip.json +19 -0
- package/src/review-api/README.md +1 -0
- package/src/review-api/agent-cli.ts +3 -2
- package/src/review-api/animation-drafts.ts +151 -47
- package/src/review-api/animation-references.ts +92 -0
- package/src/review-api/animation-render.ts +106 -0
- package/src/review-api/authoring-tools.ts +10 -3
- package/src/review-api/comparison-coverage.ts +6 -7
- package/src/review-api/document-text.ts +27 -12
- package/src/review-api/http.ts +52 -6
- package/src/review-api/instructions.ts +1 -1
- package/src/review-api/mcp.ts +10 -5
- package/src/review-api/public-tools.ts +71 -2
- package/src/review-api/review-progress.ts +2 -3
- package/src/review-api/store.ts +89 -12
- package/src/server/desktop-server.ts +17 -5
- package/src/server/headless-host.ts +4 -0
- package/src/server/review-gateway.ts +1 -0
- package/src/server/review-server-core.ts +71 -12
- package/src/sharing/client.ts +129 -32
- package/src/sharing/export.ts +42 -5
- package/src/sharing/file.ts +8 -0
- package/src/sharing/host.ts +202 -73
- package/src/sharing/import.ts +125 -33
- package/src/ui-telemetry-events.ts +26 -1
- package/dist/agent-cli-B8KBHuh_.js +0 -2
- package/dist/profile-BjcXi-oH.js +0 -2
- package/src/review-api/lens-alignment.ts +0 -52
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { f as gitAt } from "./src-Y30gweiK.js";
|
|
2
2
|
import { u as parseJsonText } from "./src-X9phtB2j.js";
|
|
3
|
-
import { $ as comparisonKey, A as parseMarkdown, B as lensSchema, E as sourceReferences, L as UNCATEGORIZED_LENS_ID, M as checkReferences, O as markdownNodes, Q as anchorSelection, T as selectionReferences, V as lensSelections, Z as SelectionUnavailableError, _ as readsAtBoardPins, at as
|
|
4
|
-
import { s as safeStorageSegment } from "./review-home-paths-
|
|
5
|
-
import { D as
|
|
3
|
+
import { $ as comparisonKey, A as parseMarkdown, B as lensSchema, E as sourceReferences, L as UNCATEGORIZED_LENS_ID, M as checkReferences, O as markdownNodes, Q as anchorSelection, T as selectionReferences, V as lensSelections, Z as SelectionUnavailableError, _ as readsAtBoardPins, at as sourceAnchors, c as documentSchema, h as pinsSchema, it as selectionKey, n as anchorPins, nt as patchRows, o as boardRepositoryId, ot as unionIntervals, q as fileLineRangeSchema, rt as resolveDiffSelection, st as ReviewInputError, u as elements, v as resourceReference, y as resourceReferences } from "./src-DirPN_Qh.js";
|
|
4
|
+
import { s as safeStorageSegment } from "./review-home-paths-BmZX2qiz.js";
|
|
5
|
+
import { C as emptyCoverage, D as subtractIntervals, E as sourcesForFile, S as coverageSources, T as scopedCoverage, b as textIncludesQuote, c as traceSchema, n as migrateStoredDocument, r as migrateStoredLenses, s as softwareModelDataSchema, t as liftFileLenses, u as pullRequestUrl } from "./stored-document-migration-vg0NAzXG.js";
|
|
6
6
|
import { N as devReviewHome } from "./s3-config-CjcLyFV5.js";
|
|
7
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
8
8
|
import { z } from "zod";
|
|
@@ -36,51 +36,93 @@ const gitHubRepositoryUrlSchema = z.string().refine((value) => {
|
|
|
36
36
|
const commitSchema = z.string().regex(/^[a-f0-9]{40}([a-f0-9]{24})?$/);
|
|
37
37
|
const sideSchema = z.enum(["base", "head"]);
|
|
38
38
|
/** An excerpt is the lines a board references. */
|
|
39
|
-
const shareSourceSchema = z.discriminatedUnion("kind", [z.
|
|
39
|
+
const shareSourceSchema = z.discriminatedUnion("kind", [z.object({
|
|
40
40
|
kind: z.literal("excerpt"),
|
|
41
41
|
file: sharePathSchema,
|
|
42
42
|
commit: commitSchema,
|
|
43
43
|
fromLine: z.number().int().positive(),
|
|
44
44
|
lines: z.array(z.string()).min(1)
|
|
45
45
|
})]);
|
|
46
|
-
const lineRangeSchema = z.
|
|
46
|
+
const lineRangeSchema = z.object({
|
|
47
47
|
side: sideSchema,
|
|
48
48
|
file: sharePathSchema,
|
|
49
49
|
fromLine: z.number().int().positive(),
|
|
50
50
|
toLine: z.number().int().positive(),
|
|
51
|
-
pins: z.
|
|
51
|
+
pins: z.object({
|
|
52
52
|
repositoryId: z.string(),
|
|
53
53
|
head: z.string(),
|
|
54
54
|
base: z.string().optional()
|
|
55
55
|
}).optional()
|
|
56
56
|
});
|
|
57
|
-
|
|
57
|
+
/** 1-based base and head lines; null when a side lacks the line. */
|
|
58
|
+
const alignmentRowSchema = z.tuple([z.number().int().positive().nullable(), z.number().int().positive().nullable()]);
|
|
59
|
+
const shareAlignmentSchema = z.object({
|
|
60
|
+
file: sharePathSchema,
|
|
61
|
+
previousFile: sharePathSchema.optional(),
|
|
62
|
+
head: z.string(),
|
|
63
|
+
base: z.string().optional(),
|
|
64
|
+
rows: z.array(alignmentRowSchema).min(1)
|
|
65
|
+
});
|
|
66
|
+
/** Unknown fields drop, so a reader opens bundles from newer sharers. */
|
|
67
|
+
const shareBundleSchema = z.object({
|
|
58
68
|
format: z.literal(SHARE_FORMAT),
|
|
59
69
|
reviewId: z.string().min(1).max(256),
|
|
60
70
|
version: z.number().int().nonnegative(),
|
|
61
71
|
title: z.string().max(4096),
|
|
62
72
|
/** Validated against the document schema by the reader. */
|
|
63
73
|
snapshot: z.record(z.string(), z.unknown()),
|
|
64
|
-
images: z.record(z.string(), z.
|
|
74
|
+
images: z.record(z.string(), z.object({ png: z.base64() })),
|
|
65
75
|
traces: z.record(z.string(), z.unknown()),
|
|
66
76
|
maps: z.record(z.string(), z.unknown()),
|
|
67
77
|
commits: z.array(z.unknown()),
|
|
68
78
|
/** Each reference's lines, resolved where the board was shared. */
|
|
69
79
|
selections: z.record(z.string(), z.array(lineRangeSchema)),
|
|
70
80
|
sources: z.array(shareSourceSchema),
|
|
71
|
-
|
|
81
|
+
/** The sharer's diff over the selections, since 0.4. */
|
|
82
|
+
alignments: z.array(shareAlignmentSchema).optional(),
|
|
83
|
+
repository: z.object({ cloneUrl: gitHubRepositoryUrlSchema }).optional(),
|
|
84
|
+
/** Quotes uncommitted changes: view only, never imported. Since 0.6. */
|
|
85
|
+
frozen: z.literal(true).optional()
|
|
72
86
|
});
|
|
73
|
-
z.
|
|
87
|
+
z.object({
|
|
74
88
|
requestId: z.uuid(),
|
|
75
89
|
bundle: shareBundleSchema
|
|
76
90
|
});
|
|
91
|
+
const shareCreatedSchema = z.strictObject({
|
|
92
|
+
shareId: shareIdSchema,
|
|
93
|
+
url: z.url()
|
|
94
|
+
});
|
|
95
|
+
const shareSizeSchema = z.number().int().positive().max(MAX_SHARE_BYTES);
|
|
96
|
+
const shareRevisionRefSchema = z.object({
|
|
97
|
+
id: shareIdSchema,
|
|
98
|
+
version: z.number().int().nonnegative(),
|
|
99
|
+
createdAt: z.number()
|
|
100
|
+
});
|
|
101
|
+
const shareRevisionSchema = shareRevisionRefSchema.extend({
|
|
102
|
+
size: shareSizeSchema,
|
|
103
|
+
url: z.url()
|
|
104
|
+
});
|
|
105
|
+
const shareRevisionsSchema = z.object({
|
|
106
|
+
latest: shareIdSchema,
|
|
107
|
+
items: z.array(shareRevisionSchema)
|
|
108
|
+
});
|
|
109
|
+
const sharedMetaSchema = z.object({
|
|
110
|
+
sender: z.strictObject({ login: z.string().min(1).max(256) }),
|
|
111
|
+
sharedAt: z.number(),
|
|
112
|
+
size: shareSizeSchema,
|
|
113
|
+
revision: shareRevisionRefSchema,
|
|
114
|
+
latest: shareIdSchema
|
|
115
|
+
});
|
|
77
116
|
function parseShareLink(value) {
|
|
78
117
|
const url = new URL(value);
|
|
79
|
-
if (url.protocol !== "https:" || url.username || url.password || url.hash || [...url.searchParams.keys()].some((key) => key !== "app")) throw new Error("Use an HTTPS Whiteboard share link.");
|
|
80
|
-
|
|
118
|
+
if (url.protocol !== "https:" || url.username || url.password || url.hash || [...url.searchParams.keys()].some((key) => key !== "app" && key !== "rev")) throw new Error("Use an HTTPS Whiteboard share link.");
|
|
119
|
+
const link = {
|
|
81
120
|
origin: url.origin,
|
|
82
121
|
capability: capabilitySchema.parse(/^\/s\/([^/]+)$/.exec(url.pathname)?.[1])
|
|
83
122
|
};
|
|
123
|
+
const revision = url.searchParams.get("rev");
|
|
124
|
+
if (revision !== null) link.revision = shareIdSchema.parse(revision);
|
|
125
|
+
return link;
|
|
84
126
|
}
|
|
85
127
|
//#endregion
|
|
86
128
|
//#region src/server/bounded-stream.ts
|
|
@@ -257,7 +299,7 @@ async function reviewProgress(store, data, snapshot, signal = new AbortControlle
|
|
|
257
299
|
})));
|
|
258
300
|
if (base === null && head === null) throw new SelectionUnavailableError("missing_file", "File is unavailable at these pins.");
|
|
259
301
|
if (base !== null && head !== null && base !== head) throw new SelectionUnavailableError("no_alignment", "Changed context file has no alignment.");
|
|
260
|
-
rows =
|
|
302
|
+
rows = patchRows("", base?.split("\n").length ?? 0, head?.split("\n").length ?? 0);
|
|
261
303
|
}
|
|
262
304
|
if (!rows) throw new SelectionUnavailableError("no_alignment", "File has no text alignment.");
|
|
263
305
|
const result = resolveDiffSelection(source, rows, file ?? { path: source.file });
|
|
@@ -425,7 +467,11 @@ async function exportShare(input) {
|
|
|
425
467
|
const stored = structuredClone(input.store.read(input.reviewId, input.version));
|
|
426
468
|
const repositoryId = boardRepositoryId(stored);
|
|
427
469
|
const { target, staleSources: _staleSources, sourceUnavailable: _sourceUnavailable, lastEdit: _lastEdit, trashedAt: _trashedAt, personEdit: _personEdit, ...snapshot } = stored;
|
|
428
|
-
|
|
470
|
+
const live = target?.kind === "worktree" ? await input.data.sourcePins(stored) : void 0;
|
|
471
|
+
if (live) {
|
|
472
|
+
const { worktreeRevision: _worktreeRevision, ...committed } = live;
|
|
473
|
+
snapshot.pins = committed;
|
|
474
|
+
}
|
|
429
475
|
const pullRequestUrl = snapshot.origin?.pullRequestUrl;
|
|
430
476
|
const baseBranch = pullRequestUrl && !snapshot.origin?.baseBranch && (snapshot.origin?.pullRequestBaseBranch ?? input.data.pullRequestBaseBranch(pullRequestUrl));
|
|
431
477
|
if (baseBranch) snapshot.origin = {
|
|
@@ -439,7 +485,7 @@ async function exportShare(input) {
|
|
|
439
485
|
if (block.type !== "markdown") continue;
|
|
440
486
|
for (const node of markdownNodes(parseMarkdown(block.markdown))) if (node.type === "image") throw new ReviewInputError("Convert Markdown images to managed image blocks before sharing.");
|
|
441
487
|
}
|
|
442
|
-
const pins = snapshot.pins;
|
|
488
|
+
const pins = live ?? snapshot.pins;
|
|
443
489
|
const references = [...sourceReferences(snapshot.document, { repositoryId }), ...lensSelections(snapshot.lenses ?? []).flatMap((selection) => sourceAnchors(selection.source).map((source) => ({ source })))];
|
|
444
490
|
const traces = /* @__PURE__ */ new Map();
|
|
445
491
|
const maps = /* @__PURE__ */ new Map();
|
|
@@ -480,7 +526,16 @@ async function exportShare(input) {
|
|
|
480
526
|
fromLine,
|
|
481
527
|
toLine
|
|
482
528
|
}));
|
|
483
|
-
|
|
529
|
+
let frozen = false;
|
|
530
|
+
const sources = await cutExcerpts({ async quote(at, range) {
|
|
531
|
+
const quote = await input.data.quote(at, range);
|
|
532
|
+
if (at.worktreeRevision && range.side === "head" && !frozen) {
|
|
533
|
+
const { worktreeRevision: _worktreeRevision, ...committed } = at;
|
|
534
|
+
if (await input.data.quote(committed, range).then((head) => head.text !== quote.text, () => true)) frozen = true;
|
|
535
|
+
}
|
|
536
|
+
return quote;
|
|
537
|
+
} }, excerpts);
|
|
538
|
+
const bundle = {
|
|
484
539
|
format: SHARE_FORMAT,
|
|
485
540
|
reviewId: snapshot.reviewId,
|
|
486
541
|
version: snapshot.version,
|
|
@@ -491,9 +546,49 @@ async function exportShare(input) {
|
|
|
491
546
|
maps: Object.fromEntries(maps),
|
|
492
547
|
commits: pins ? await input.data.commits((await input.data.resolveSource(stored)).pins) : [],
|
|
493
548
|
selections,
|
|
494
|
-
sources
|
|
549
|
+
sources,
|
|
495
550
|
...input.repository && { repository: input.repository }
|
|
496
551
|
};
|
|
552
|
+
if (frozen) bundle.frozen = true;
|
|
553
|
+
return bundle;
|
|
554
|
+
}
|
|
555
|
+
//#endregion
|
|
556
|
+
//#region src/slug.ts
|
|
557
|
+
/** Lowercase ASCII slug: runs of anything but `a-z0-9` become one `-`, outer
|
|
558
|
+
* dashes go. Empty input, or input with no letters or digits, yields `""`;
|
|
559
|
+
* callers choose their own fallback (`"section"`, `"diagram"`, `"database"`).
|
|
560
|
+
* This is the rule sequence and lens ids were already published with, so it
|
|
561
|
+
* must not change. */
|
|
562
|
+
function slugify(text) {
|
|
563
|
+
return text.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
564
|
+
}
|
|
565
|
+
//#endregion
|
|
566
|
+
//#region src/sharing/file.ts
|
|
567
|
+
const WHITEBOARD_FILE_EXTENSION = ".whiteboard";
|
|
568
|
+
const WHITEBOARD_UPGRADE_REQUIRED_MESSAGE = "This action requires a newer version of Whiteboard. Use Check for Updates, then try again.";
|
|
569
|
+
const headerSchema = z.looseObject({ format: z.string() });
|
|
570
|
+
function whiteboardFileName(title) {
|
|
571
|
+
return `${slugify(title) || "untitled"}${WHITEBOARD_FILE_EXTENSION}`;
|
|
572
|
+
}
|
|
573
|
+
function whiteboardFileText(bundle) {
|
|
574
|
+
return JSON.stringify(bundle, null, 2) + "\n";
|
|
575
|
+
}
|
|
576
|
+
function parseWhiteboardFile(text) {
|
|
577
|
+
let parsed;
|
|
578
|
+
try {
|
|
579
|
+
parsed = JSON.parse(text);
|
|
580
|
+
} catch {
|
|
581
|
+
parsed = void 0;
|
|
582
|
+
}
|
|
583
|
+
const header = headerSchema.safeParse(parsed);
|
|
584
|
+
if (!header.success) throw new ReviewInputError("This is not a Whiteboard file.", 400);
|
|
585
|
+
const version = /^review-share\/([1-9]\d*)$/.exec(header.data.format);
|
|
586
|
+
if (version && Number(version[1]) > Number("review-share/2".split("/")[1])) throw new ReviewInputError(WHITEBOARD_UPGRADE_REQUIRED_MESSAGE, 400);
|
|
587
|
+
if (header.data.format !== "review-share/2") throw new ReviewInputError("This file needs a different Whiteboard version.", 400);
|
|
588
|
+
return shareBundleSchema.parse(parsed);
|
|
589
|
+
}
|
|
590
|
+
function whiteboardFileKey(text) {
|
|
591
|
+
return createHash("sha256").update("whiteboard-file\0").update(text).digest("hex");
|
|
497
592
|
}
|
|
498
593
|
//#endregion
|
|
499
594
|
//#region src/sharing/client.ts
|
|
@@ -512,15 +607,6 @@ const pooledFetch = (input, init) => {
|
|
|
512
607
|
const jwtSchema = z.object({ token: z.string().min(1) });
|
|
513
608
|
const jwtPayloadSchema = z.object({ exp: z.number() });
|
|
514
609
|
const bearers = /* @__PURE__ */ new Map();
|
|
515
|
-
const linkSchema = z.strictObject({
|
|
516
|
-
shareId: z.uuid(),
|
|
517
|
-
url: z.url()
|
|
518
|
-
});
|
|
519
|
-
const receivedSchema = z.object({
|
|
520
|
-
sender: z.strictObject({ login: z.string().min(1).max(256) }),
|
|
521
|
-
sharedAt: z.number(),
|
|
522
|
-
size: z.number().int().positive().max(MAX_SHARE_BYTES)
|
|
523
|
-
});
|
|
524
610
|
/** Size is checked while reading, even if Content-Length is absent or false. */
|
|
525
611
|
async function readBoundedBytes(response, limit) {
|
|
526
612
|
if (!response.ok || !response.body) throw new Error(`Download failed (${response.status}).`);
|
|
@@ -543,6 +629,44 @@ var ShareAuthError = class extends Error {
|
|
|
543
629
|
this.name = "ShareAuthError";
|
|
544
630
|
}
|
|
545
631
|
};
|
|
632
|
+
var ShareRequestError = class extends Error {
|
|
633
|
+
status;
|
|
634
|
+
constructor(status, message) {
|
|
635
|
+
super(message);
|
|
636
|
+
this.status = status;
|
|
637
|
+
this.name = "ShareRequestError";
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
const errorSchema = z.object({ error: z.object({ code: z.string() }) });
|
|
641
|
+
async function errorCode(response) {
|
|
642
|
+
if (!response.body) return void 0;
|
|
643
|
+
if (!response.headers.get("content-type")?.includes("application/json")) {
|
|
644
|
+
await response.body.cancel();
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
try {
|
|
648
|
+
const bytes = await readBoundedStream(response.body, 16384);
|
|
649
|
+
return errorSchema.safeParse(parseJsonText(bytes.toString())).data?.error.code;
|
|
650
|
+
} catch {
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
function requestErrorMessage(response, code) {
|
|
655
|
+
const { status, headers } = response;
|
|
656
|
+
if (status === 426 || code === "upgrade_required") return WHITEBOARD_UPGRADE_REQUIRED_MESSAGE;
|
|
657
|
+
if (status === 403 && headers.get("content-type")?.includes("text/html") && headers.get("server")?.toLowerCase() === "cloudflare") return "The sharing service's security filter blocked this request. Please report this issue.";
|
|
658
|
+
if (status === 413) return "This review is too large to share. Remove large resources, then retry.";
|
|
659
|
+
if (status === 429) return code === "share_quota_exceeded" ? "Your sharing storage limit has been reached. Please contact support." : "Too many sharing requests. Wait a moment, then retry.";
|
|
660
|
+
if (status === 400 && code === "invalid_request") return "The sharing service could not accept this review. Update Whiteboard or report this issue.";
|
|
661
|
+
if (status >= 500) return `The sharing service is temporarily unavailable (HTTP ${status}). Try again shortly.`;
|
|
662
|
+
return `The sharing service refused this request (HTTP ${status}). Please report this issue.`;
|
|
663
|
+
}
|
|
664
|
+
async function requestError(response) {
|
|
665
|
+
const message = requestErrorMessage(response, await errorCode(response));
|
|
666
|
+
const ray = response.headers.get("cf-ray");
|
|
667
|
+
const reference = ray && /^[a-f0-9]{16}(?:-[a-z]{3})?$/i.test(ray) ? ` Reference: ${ray}.` : "";
|
|
668
|
+
return new ShareRequestError(response.status, message + reference);
|
|
669
|
+
}
|
|
546
670
|
/** Account tokens go only to the configured API origin. */
|
|
547
671
|
var ShareClient = class {
|
|
548
672
|
token;
|
|
@@ -570,9 +694,11 @@ var ShareClient = class {
|
|
|
570
694
|
if (body !== void 0) init.body = body;
|
|
571
695
|
const response = await this.send(this.origin + route, init);
|
|
572
696
|
if (!response.ok) {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
697
|
+
if (response.status === 401 && !anonymous) {
|
|
698
|
+
await response.body?.cancel();
|
|
699
|
+
throw new ShareAuthError();
|
|
700
|
+
}
|
|
701
|
+
throw await requestError(response);
|
|
576
702
|
}
|
|
577
703
|
return readBoundedBytes(response, maxBytes);
|
|
578
704
|
}
|
|
@@ -595,13 +721,18 @@ var ShareClient = class {
|
|
|
595
721
|
headers: { authorization: `Bearer ${token}` }
|
|
596
722
|
});
|
|
597
723
|
if (!response.ok) {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
724
|
+
if (response.status === 404 || response.status === 405) {
|
|
725
|
+
await response.body?.cancel();
|
|
726
|
+
return {
|
|
727
|
+
bearer: token,
|
|
728
|
+
until: Date.now() + 6e5
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
if (response.status === 401) {
|
|
732
|
+
await response.body?.cancel();
|
|
733
|
+
throw new ShareAuthError();
|
|
734
|
+
}
|
|
735
|
+
throw await requestError(response);
|
|
605
736
|
}
|
|
606
737
|
const { token: jwt } = jwtSchema.parse(parseJsonText(Buffer.from(await readBoundedBytes(response, 65536)).toString()));
|
|
607
738
|
const { exp } = jwtPayloadSchema.parse(parseJsonText(Buffer.from(jwt.split(".")[1] ?? "", "base64url").toString()));
|
|
@@ -618,29 +749,38 @@ var ShareClient = class {
|
|
|
618
749
|
})).body?.cancel();
|
|
619
750
|
}
|
|
620
751
|
async create(bundle, requestId = randomUUID()) {
|
|
621
|
-
return
|
|
752
|
+
return shareCreatedSchema.parse(await this.api("/api/v2/shares", "POST", shareRequestBody(bundle, requestId)));
|
|
622
753
|
}
|
|
623
754
|
async list(cursor) {
|
|
624
755
|
const query = cursor ? `?cursor=${z.uuid().parse(cursor)}` : "";
|
|
625
756
|
return this.api(`/api/v2/shares${query}`);
|
|
626
757
|
}
|
|
627
758
|
async recoverLink(shareId) {
|
|
628
|
-
return
|
|
759
|
+
return shareCreatedSchema.parse(await this.api(`/api/v2/shares/${z.uuid().parse(shareId)}/link`));
|
|
629
760
|
}
|
|
630
761
|
async revoke(shareId) {
|
|
631
762
|
return this.api(`/api/v2/shares/${z.uuid().parse(shareId)}`, "DELETE");
|
|
632
763
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
764
|
+
async revisions(shareId) {
|
|
765
|
+
return shareRevisionsSchema.parse(await this.api(`/api/v2/shares/${shareIdSchema.parse(shareId)}/revisions`));
|
|
766
|
+
}
|
|
767
|
+
/** Who shared a link's revision, and which revision is its latest. */
|
|
768
|
+
async shared(capability, revision) {
|
|
769
|
+
const query = revision === void 0 ? "" : `?rev=${shareIdSchema.parse(revision)}`;
|
|
770
|
+
return sharedMetaSchema.parse(await this.api(`/api/v2/shared/${capabilitySchema.parse(capability)}${query}`, "GET", void 0, true));
|
|
771
|
+
}
|
|
772
|
+
/** The bundle of the revision the meta names, and who shared it. */
|
|
773
|
+
async download(capability, revision) {
|
|
774
|
+
const meta = await this.shared(capability, revision);
|
|
775
|
+
const bytes = await this.request(`/api/v2/shared/${capability}/bundle?rev=${meta.revision.id}`, "GET", void 0, true, MAX_SHARE_BYTES);
|
|
638
776
|
return {
|
|
639
|
-
bundle:
|
|
777
|
+
bundle: parseWhiteboardFile(new TextDecoder().decode(bytes)),
|
|
640
778
|
attribution: {
|
|
641
|
-
login:
|
|
642
|
-
sharedAt:
|
|
643
|
-
}
|
|
779
|
+
login: meta.sender.login,
|
|
780
|
+
sharedAt: meta.sharedAt
|
|
781
|
+
},
|
|
782
|
+
revision: meta.revision,
|
|
783
|
+
latest: meta.latest
|
|
644
784
|
};
|
|
645
785
|
}
|
|
646
786
|
};
|
|
@@ -847,7 +987,7 @@ async function readShareRepository(root) {
|
|
|
847
987
|
}
|
|
848
988
|
//#endregion
|
|
849
989
|
//#region src/sharing/import.ts
|
|
850
|
-
const sharedSnapshotSchema = z.
|
|
990
|
+
const sharedSnapshotSchema = z.object({
|
|
851
991
|
reviewId: z.string().min(1),
|
|
852
992
|
version: z.number().int().nonnegative(),
|
|
853
993
|
/** Empty for a board never named; it reads "Untitled". */
|
|
@@ -859,7 +999,7 @@ const sharedSnapshotSchema = z.strictObject({
|
|
|
859
999
|
document: documentSchema,
|
|
860
1000
|
lenses: z.array(lensSchema).optional(),
|
|
861
1001
|
createdAt: z.string(),
|
|
862
|
-
origin: z.
|
|
1002
|
+
origin: z.object({
|
|
863
1003
|
branch: z.string().optional(),
|
|
864
1004
|
baseRef: z.string().optional(),
|
|
865
1005
|
baseBranch: z.string().optional(),
|
|
@@ -871,7 +1011,7 @@ const sharedSnapshotSchema = z.strictObject({
|
|
|
871
1011
|
}).optional()
|
|
872
1012
|
});
|
|
873
1013
|
const count = z.number().int().nonnegative();
|
|
874
|
-
const counts = z.
|
|
1014
|
+
const counts = z.object({
|
|
875
1015
|
additions: count,
|
|
876
1016
|
deletions: count
|
|
877
1017
|
});
|
|
@@ -881,9 +1021,9 @@ const mapSchema = softwareModelDataSchema.extend({
|
|
|
881
1021
|
countsByElementPath: z.record(z.string(), counts),
|
|
882
1022
|
unmappedByElementPath: z.record(z.string(), counts.extend({ files: z.array(counts.extend({
|
|
883
1023
|
file: z.string(),
|
|
884
|
-
hunks: z.array(z.
|
|
1024
|
+
hunks: z.array(z.object({
|
|
885
1025
|
startLine: count,
|
|
886
|
-
lines: z.array(z.
|
|
1026
|
+
lines: z.array(z.object({
|
|
887
1027
|
kind: z.enum(["add", "remove"]),
|
|
888
1028
|
oldLine: count.nullable(),
|
|
889
1029
|
newLine: count.nullable(),
|
|
@@ -892,13 +1032,14 @@ const mapSchema = softwareModelDataSchema.extend({
|
|
|
892
1032
|
}))
|
|
893
1033
|
})) }))
|
|
894
1034
|
});
|
|
895
|
-
z.
|
|
1035
|
+
z.object({ maps: z.record(z.string(), mapSchema) });
|
|
896
1036
|
/** Commits and traces live in a checkout; a document alone needs none. */
|
|
897
1037
|
function needsRepository(bundle, snapshot) {
|
|
898
1038
|
return Boolean(snapshot.pins || referenceCommits(snapshot).length || Object.keys(bundle.traces).length);
|
|
899
1039
|
}
|
|
900
1040
|
function validateShareBundle(input) {
|
|
901
1041
|
const bundle = shareBundleSchema.parse(input);
|
|
1042
|
+
if (bundle.frozen) throw new ReviewInputError("This whiteboard is a read-only snapshot of uncommitted changes. View it in the browser.", 409);
|
|
902
1043
|
const stored = bundle.snapshot;
|
|
903
1044
|
const snapshot = sharedSnapshotSchema.parse(stored.document === void 0 ? stored : liftSharedLenses(stored, migrateStoredDocument(stored.document)));
|
|
904
1045
|
if (snapshot.reviewId !== bundle.reviewId || snapshot.version !== bundle.version || snapshot.title !== bundle.title) throw new Error("Shared snapshot does not match its bundle.");
|
|
@@ -948,12 +1089,41 @@ function shareKey(origin, capability) {
|
|
|
948
1089
|
capabilitySchema.parse(capability);
|
|
949
1090
|
return createHash("sha256").update(JSON.stringify([url.origin, capability])).digest("hex");
|
|
950
1091
|
}
|
|
951
|
-
/**
|
|
952
|
-
|
|
953
|
-
|
|
1092
|
+
/** Which copy a served revision opens or refreshes in place; otherwise a new copy. */
|
|
1093
|
+
function planShareCopy(copies, served, pinned = false) {
|
|
1094
|
+
const same = copies.find(({ shared }) => shared.revisionId === void 0 ? shared.sharedAt === served.revision.createdAt : shared.revisionId === served.revision.id);
|
|
1095
|
+
if (same) return {
|
|
1096
|
+
kind: "open",
|
|
1097
|
+
reviewId: same.reviewId
|
|
1098
|
+
};
|
|
1099
|
+
const unedited = !pinned && served.revision.id === served.latest && copies.find((copy) => !copy.shared.pinned && copy.version === (copy.shared.importedVersion ?? 0));
|
|
1100
|
+
return unedited ? {
|
|
1101
|
+
kind: "refresh",
|
|
1102
|
+
reviewId: unedited.reviewId,
|
|
1103
|
+
version: unedited.version
|
|
1104
|
+
} : { kind: "import" };
|
|
1105
|
+
}
|
|
1106
|
+
/** Opens, refreshes or copies a link's board by the revision it serves. */
|
|
1107
|
+
async function receiveShare(input, key, served, download, pinned = false) {
|
|
1108
|
+
const plan = planShareCopy(input.store.copiesOf(key), served, pinned);
|
|
1109
|
+
if (plan.kind === "open") return plan.reviewId;
|
|
1110
|
+
const { bundle, attribution } = await download();
|
|
1111
|
+
const provenance = {
|
|
1112
|
+
...attribution,
|
|
1113
|
+
revisionId: served.revision.id,
|
|
1114
|
+
...pinned && { pinned }
|
|
1115
|
+
};
|
|
1116
|
+
if (plan.kind === "import") return importShare(input, key, bundle, provenance);
|
|
1117
|
+
try {
|
|
1118
|
+
return await importShare(input, key, bundle, provenance, plan);
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
if (error instanceof ReviewInputError && (error.status === 409 || error.status === 404)) return importShare(input, key, bundle, provenance);
|
|
1121
|
+
throw error;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
/** Imports a share as an ordinary board, or into a copy of its link. */
|
|
1125
|
+
async function importShare(input, key, received, provenance, into) {
|
|
954
1126
|
const { store, data } = input;
|
|
955
|
-
const existing = store.copyOf(key);
|
|
956
|
-
if (existing) return existing;
|
|
957
1127
|
const { bundle, snapshot, maps } = validateShareBundle(received);
|
|
958
1128
|
const cloneUrl = bundle.repository?.cloneUrl;
|
|
959
1129
|
let repositoryId;
|
|
@@ -988,7 +1158,7 @@ async function importShare(input, key, received, attribution) {
|
|
|
988
1158
|
}
|
|
989
1159
|
let document = JSON.stringify(snapshot.document);
|
|
990
1160
|
for (const [id, copy] of ids) document = document.replaceAll(id, copy);
|
|
991
|
-
const
|
|
1161
|
+
const create = { operation: {
|
|
992
1162
|
type: "create",
|
|
993
1163
|
...snapshot.title && { title: snapshot.title },
|
|
994
1164
|
...pins ? { target: {
|
|
@@ -998,17 +1168,19 @@ async function importShare(input, key, received, attribution) {
|
|
|
998
1168
|
kind: "project",
|
|
999
1169
|
repositoryId
|
|
1000
1170
|
} }
|
|
1001
|
-
} }
|
|
1171
|
+
} };
|
|
1172
|
+
const seed = {
|
|
1002
1173
|
document: JSON.parse(document),
|
|
1003
1174
|
lenses: snapshot.lenses,
|
|
1004
1175
|
origin: snapshot.origin,
|
|
1005
1176
|
shared: {
|
|
1006
|
-
...
|
|
1177
|
+
...provenance,
|
|
1007
1178
|
cloneUrl,
|
|
1008
1179
|
key
|
|
1009
1180
|
},
|
|
1010
1181
|
copiedFrom: snapshot.reviewId
|
|
1011
|
-
}
|
|
1182
|
+
};
|
|
1183
|
+
const { reviewId } = into ? await store.refreshCopy(into.reviewId, into.version, create, seed) : await store.execute(create, seed, { kind: "person" });
|
|
1012
1184
|
return reviewId;
|
|
1013
1185
|
}
|
|
1014
1186
|
/** A checkout of the share's repository with every commit it reads at: the
|
|
@@ -1050,4 +1222,4 @@ async function shareRepository({ store, data, ...git }, cloneUrl, pins, commits,
|
|
|
1050
1222
|
return repositoryId;
|
|
1051
1223
|
}
|
|
1052
1224
|
//#endregion
|
|
1053
|
-
export {
|
|
1225
|
+
export { StreamLimitError as C, shareIdSchema as D, shareBundleSchema as E, uncategorizedReport as S, parseShareLink as T, validateShareSources as _, shareKey as a, progressUpdateSchema as b, readShareRepository as c, ShareRequestError as d, parseWhiteboardFile as f, exportShare as g, whiteboardFileText as h, receiveShare as i, ShareAuthError as l, whiteboardFileName as m, needsRepository as n, validateShareBundle as o, whiteboardFileKey as p, planShareCopy as r, forgetSharedCommits as s, importShare as t, ShareClient as u, legacyModeSchema as v, readBoundedStream as w, reviewProgress as x, lensReport as y };
|