@dev.fast/whiteboard 0.4.0 → 0.4.1-preview.20261026.138
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-2yTAxoBx.js → account-alias-BPlPnzQM.js} +7 -7
- package/dist/{agent-cli-CHNDd4Ga.js → agent-cli-BbCf9uid.js} +11 -11
- package/dist/agent-cli-ipN_JmzG.js +2 -0
- package/dist/{agent-client-Dc0vHFJt.js → agent-client-DfpM8F5N.js} +3 -3
- package/dist/{authoring-tools-D8EkIDjm.js → authoring-tools-bm39DRoV.js} +23 -10
- package/dist/{background-server-DyWnPJyZ.js → background-server--0EeCCeV.js} +1 -1
- package/dist/{background-server-DHWHzpo5.js → background-server-B--5p72o.js} +6 -6
- package/dist/build-info.json +1 -1
- package/dist/{cli-qZomwJmK.js → cli-DBTQIroM.js} +2 -2
- package/dist/{cli-runner-DFBaVGij.js → cli-runner-CRFCdb7G.js} +25 -25
- package/dist/cli.js +3 -3
- package/dist/{fuzzy-match-CfSxqzBF.js → fuzzy-match-l3mJZ1Ge.js} +5 -5
- package/dist/{headless-host-dwlqhqMY.js → headless-host-5ei3nHS8.js} +1 -1
- package/dist/{headless-host-DJhcab6r.js → headless-host-BkJ9OUQY.js} +131 -299
- package/dist/{client-p9WB8Sj0.js → import-Bwvf9Wos.js} +505 -390
- package/dist/{lens-selection-9byzEgvU.js → lens-selection-EuZdag4Q.js} +1 -1
- package/dist/{local-data-DHedfhxw.js → local-data-BCSeTyic.js} +98 -154
- package/dist/{mcp-Br-r_wwR.js → mcp-_Xg1gQuA.js} +17 -8
- package/dist/{package-paths-BeYHYjeR.js → package-paths-Bg731FHS.js} +2 -2
- package/dist/profile-C8zL4u3L.js +2 -0
- package/dist/{profile-D9wdlPgw.js → profile-xzrUFgbT.js} +4 -4
- package/dist/{remote-attach-DSGpTOJ4.js → remote-attach-DF25Epoa.js} +4 -4
- package/dist/{remote-extensions-WtJO7pkb.js → remote-extensions-BJamgsCe.js} +1 -1
- package/dist/{remote-extensions-CsI_BJ_f.js → remote-extensions-CeywZL-Z.js} +4 -4
- package/dist/{remote-language-server-Dx13Mg2m.js → remote-language-server-CH8sG_CU.js} +6 -6
- package/dist/{remote-uninstall-Dp2o3qz0.js → remote-uninstall-Cq-lkBY7.js} +7 -7
- package/dist/{request-origin-CNSLVn7x.js → request-origin-CargJFvH.js} +14 -3
- package/dist/{review-app-launcher-BZAnR8Vp.js → review-app-launcher-D8ApNDUV.js} +3 -3
- package/dist/{review-home-paths-CG-sb9Cw.js → review-home-paths-DcB4xLD-.js} +2 -2
- package/dist/{review-telemetry-BM-_KdLi.js → review-telemetry-BW7gJrSo.js} +7 -7
- package/dist/{runtime-C6_1K-3Z.js → runtime-DY6Ctyog.js} +1 -1
- package/dist/runtime.js +1 -1
- package/dist/{s3-CsG2EWED.js → s3-BIWv4h1J.js} +2 -2
- package/dist/{s3-config-zz4GWh2T.js → s3-config-Du3RBIvv.js} +1 -1
- package/dist/server/desktop-host.js +51 -17
- package/dist/{server-discovery-DKLIOgg3.js → server-discovery-aHK_1r3U.js} +5 -5
- package/dist/sharing/index.d.ts +209 -98
- package/dist/sharing/index.js +1 -1
- package/dist/{src-DYHVzX3b.js → src-DEOc8bvC.js} +63 -2
- package/dist/{src-Z1x4-Qmb.js → src-DjRaCctk.js} +6 -6
- package/dist/{stored-document-migration-DEt8iwem.js → stored-document-migration-CwmTwTb8.js} +139 -13
- package/dist/{thread-state-CO97FUqK.js → thread-state-CbLB0jUA.js} +1 -1
- package/dist/{tool-failure-C1L1vJyz.js → tool-failure-DZaQzi4f.js} +2 -2
- package/instructions/animation.md +345 -0
- package/instructions/authoring.md +2 -2
- package/package.json +6 -6
- package/src/fixtures/blocks/animation.json +19 -0
- package/src/review-api/authoring-tools.ts +17 -6
- package/src/review-api/blocks/animation.ts +98 -0
- package/src/review-api/blocks/index.ts +3 -0
- package/src/review-api/blocks/sequence.ts +3 -1
- package/src/review-api/comparison-coverage.ts +120 -89
- package/src/review-api/component-reference.ts +16 -0
- package/src/review-api/document-text.ts +11 -0
- package/src/review-api/document.ts +26 -5
- package/src/review-api/http.ts +31 -2
- package/src/review-api/instructions.ts +11 -2
- package/src/review-api/local-data.ts +21 -4
- package/src/review-api/mcp.ts +22 -14
- package/src/review-api/onboarding.ts +100 -26
- package/src/review-preferences.ts +43 -0
- package/src/server/desktop-server.ts +24 -0
- package/src/server/json-review-reporting.ts +1 -1
- package/src/server/review-server-core.ts +2 -0
- package/src/server/structural-comparisons.ts +5 -11
- package/src/server/structural-diff.ts +8 -2
- package/src/sharing/cli.ts +1 -1
- package/src/sharing/client.ts +54 -222
- package/src/sharing/export.ts +172 -114
- package/src/sharing/host.ts +32 -36
- package/src/sharing/import.ts +93 -147
- package/src/sharing/index.ts +1 -6
- package/src/sharing/routes.ts +18 -17
- package/src/ui-telemetry-events.ts +11 -0
- package/dist/agent-cli-Cn4-16XA.js +0 -2
- package/dist/profile-BopGMZUh.js +0 -2
- package/dist/{s3-config-CjcLyFV5.js → s3-config-D7Yt0VhS.js} +1 -1
- package/dist/{src-X9ZebeOD.js → src-DXnn7RrI.js} +1 -1
package/src/sharing/import.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { existsSync } from "node:fs";
|
|
2
3
|
import {
|
|
3
4
|
mkdir,
|
|
@@ -12,17 +13,12 @@ import {
|
|
|
12
13
|
} from "node:fs/promises";
|
|
13
14
|
import path from "node:path";
|
|
14
15
|
|
|
15
|
-
import {
|
|
16
|
+
import { type JsonObject, type JsonValue } from "@dev.fast/review-protocol";
|
|
16
17
|
import {
|
|
17
|
-
|
|
18
|
-
type
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
MAX_SHARE_MANIFEST_BYTES,
|
|
23
|
-
type ShareManifest,
|
|
24
|
-
shareIdSchema,
|
|
25
|
-
shareManifestSchema,
|
|
18
|
+
MAX_SHARE_BYTES,
|
|
19
|
+
type ShareBundle,
|
|
20
|
+
capabilitySchema,
|
|
21
|
+
shareBundleSchema,
|
|
26
22
|
} from "@dev.fast/review-share-protocol";
|
|
27
23
|
import { textIncludesQuote } from "@review/evidence.js";
|
|
28
24
|
import { lensSchema } from "@review/review-api/diff-lenses.js";
|
|
@@ -50,12 +46,7 @@ import {
|
|
|
50
46
|
} from "@review/stored-document-migration.js";
|
|
51
47
|
import { z } from "zod";
|
|
52
48
|
|
|
53
|
-
import {
|
|
54
|
-
type ShareBundle,
|
|
55
|
-
digestBytes,
|
|
56
|
-
referenceCommits,
|
|
57
|
-
validateShareSources,
|
|
58
|
-
} from "./export.js";
|
|
49
|
+
import { referenceCommits, validateShareSources } from "./export.js";
|
|
59
50
|
import {
|
|
60
51
|
COMMIT_ID,
|
|
61
52
|
assertCommitIds,
|
|
@@ -132,59 +123,48 @@ export const sharePresentationSchema = z.strictObject({
|
|
|
132
123
|
maps: z.record(z.string(), mapSchema),
|
|
133
124
|
});
|
|
134
125
|
|
|
135
|
-
export
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
throw new Error("Share manifest is too large.");
|
|
140
|
-
|
|
141
|
-
if (bundle.objects.size !== manifest.objects.length)
|
|
142
|
-
throw new Error("Share object count does not match its manifest.");
|
|
143
|
-
|
|
144
|
-
for (const object of manifest.objects) {
|
|
145
|
-
const bytes = bundle.objects.get(object.id);
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
!bytes ||
|
|
149
|
-
bytes.byteLength !== object.size ||
|
|
150
|
-
digestBytes(bytes) !== object.sha256
|
|
151
|
-
)
|
|
152
|
-
throw new Error(`Shared object ${object.id} is missing or corrupt.`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const json = (id: string) =>
|
|
156
|
-
parseJsonText(Buffer.from(bundle.objects.get(id)!).toString());
|
|
126
|
+
export interface ShareAttribution {
|
|
127
|
+
login: string;
|
|
128
|
+
sharedAt: number;
|
|
129
|
+
}
|
|
157
130
|
|
|
158
|
-
|
|
131
|
+
export function validateShareBundle(input: ShareBundle) {
|
|
132
|
+
const bundle = shareBundleSchema.parse(input);
|
|
133
|
+
// SAFETY: a bundle is parsed from JSON, so its snapshot is a JSON object.
|
|
134
|
+
const stored = bundle.snapshot as JsonObject;
|
|
159
135
|
|
|
160
|
-
// Bundles shared before a field was retired still open; the
|
|
161
|
-
//
|
|
162
|
-
//
|
|
136
|
+
// Bundles shared before a field was retired still open; only the parsed
|
|
137
|
+
// document drops the retired form. Lenses shared as document blocks read
|
|
138
|
+
// as the snapshot's lenses.
|
|
163
139
|
const snapshot = sharedSnapshotSchema.parse(
|
|
164
|
-
|
|
165
|
-
?
|
|
166
|
-
: stored,
|
|
140
|
+
stored.document === undefined
|
|
141
|
+
? stored
|
|
142
|
+
: liftSharedLenses(stored, migrateStoredDocument(stored.document)),
|
|
167
143
|
);
|
|
168
144
|
|
|
169
145
|
if (
|
|
170
|
-
snapshot.reviewId !==
|
|
171
|
-
snapshot.version !==
|
|
172
|
-
snapshot.title !==
|
|
146
|
+
snapshot.reviewId !== bundle.reviewId ||
|
|
147
|
+
snapshot.version !== bundle.version ||
|
|
148
|
+
snapshot.title !== bundle.title
|
|
173
149
|
)
|
|
174
|
-
throw new Error("Shared snapshot does not match its
|
|
150
|
+
throw new Error("Shared snapshot does not match its bundle.");
|
|
175
151
|
checkReferences(snapshot.document);
|
|
176
152
|
validateShareSources(snapshot);
|
|
177
153
|
// Every commit it quotes reaches git as an argument.
|
|
178
154
|
assertCommitIds(referenceCommits(snapshot));
|
|
179
155
|
|
|
180
|
-
if (
|
|
181
|
-
|
|
156
|
+
if (
|
|
157
|
+
(snapshot.pins || referenceCommits(snapshot).length) &&
|
|
158
|
+
!bundle.repository
|
|
159
|
+
)
|
|
160
|
+
throw new ReviewInputError(
|
|
161
|
+
"This share can only be viewed in the browser.",
|
|
162
|
+
409,
|
|
163
|
+
);
|
|
182
164
|
|
|
183
|
-
const
|
|
184
|
-
json(manifest.presentation),
|
|
185
|
-
);
|
|
165
|
+
const maps = z.record(z.string(), mapSchema).parse(bundle.maps);
|
|
186
166
|
|
|
187
|
-
if (!snapshot.pins && Object.keys(
|
|
167
|
+
if (!snapshot.pins && Object.keys(maps).length)
|
|
188
168
|
throw new Error("Shared maps require source pins.");
|
|
189
169
|
|
|
190
170
|
const traces = new Map<
|
|
@@ -193,25 +173,18 @@ export function validateShareBundle(bundle: ShareBundle) {
|
|
|
193
173
|
>();
|
|
194
174
|
|
|
195
175
|
for (const block of resourceReferences(snapshot.document)) {
|
|
196
|
-
const reference = resourceReference(block);
|
|
197
|
-
|
|
198
|
-
const resource = manifest.resources.find(
|
|
199
|
-
(item) => item.id === reference?.id,
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
if (!reference || !resource || resource.kind !== reference.kind)
|
|
203
|
-
throw new Error("Missing or mismatched shared resource.");
|
|
204
|
-
|
|
205
176
|
if (block.type === "trace_quote") {
|
|
206
|
-
let events = traces.get(
|
|
177
|
+
let events = traces.get(block.traceId);
|
|
207
178
|
|
|
208
179
|
if (!events) {
|
|
209
|
-
|
|
180
|
+
if (!Object.hasOwn(bundle.traces, block.traceId))
|
|
181
|
+
throw new Error("Missing shared trace.");
|
|
182
|
+
const trace = traceSchema.parse(bundle.traces[block.traceId]);
|
|
210
183
|
events = new Map(trace.events.map((event) => [event.id, event]));
|
|
211
184
|
|
|
212
185
|
if (events.size !== trace.events.length)
|
|
213
186
|
throw new Error("Duplicate shared trace event.");
|
|
214
|
-
traces.set(
|
|
187
|
+
traces.set(block.traceId, events);
|
|
215
188
|
}
|
|
216
189
|
|
|
217
190
|
const event = events.get(block.eventId);
|
|
@@ -221,14 +194,16 @@ export function validateShareBundle(bundle: ShareBundle) {
|
|
|
221
194
|
}
|
|
222
195
|
|
|
223
196
|
if (block.type === "software_map") {
|
|
224
|
-
const map =
|
|
197
|
+
const map = Object.hasOwn(maps, block.mapVersionId)
|
|
198
|
+
? maps[block.mapVersionId]
|
|
199
|
+
: undefined;
|
|
225
200
|
|
|
226
201
|
if (!map || map.commit !== snapshot.pins?.[map.side])
|
|
227
202
|
throw new Error("Shared map pins do not match.");
|
|
228
203
|
}
|
|
229
204
|
}
|
|
230
205
|
|
|
231
|
-
return {
|
|
206
|
+
return { bundle, snapshot, maps };
|
|
232
207
|
}
|
|
233
208
|
|
|
234
209
|
function liftSharedLenses(stored: JsonObject, migrated: JsonValue): JsonObject {
|
|
@@ -246,16 +221,21 @@ function liftSharedLenses(stored: JsonObject, migrated: JsonValue): JsonObject {
|
|
|
246
221
|
};
|
|
247
222
|
}
|
|
248
223
|
|
|
249
|
-
export function sharedReviewId(origin: string,
|
|
224
|
+
export function sharedReviewId(origin: string, capability: string) {
|
|
250
225
|
const url = new URL(origin);
|
|
251
|
-
|
|
226
|
+
capabilitySchema.parse(capability);
|
|
252
227
|
|
|
253
|
-
return `shared-${
|
|
228
|
+
return `shared-${createHash("sha256")
|
|
229
|
+
.update(JSON.stringify([url.origin, capability]))
|
|
230
|
+
.digest("hex")}`;
|
|
254
231
|
}
|
|
255
232
|
|
|
256
233
|
/** Separate immutable directory tree; authoring mutation handlers cannot write it. */
|
|
257
234
|
export class SharedReviewStore {
|
|
258
|
-
private readonly loaded = new Map<
|
|
235
|
+
private readonly loaded = new Map<
|
|
236
|
+
string,
|
|
237
|
+
{ attribution?: ShareAttribution }
|
|
238
|
+
>();
|
|
259
239
|
private readonly validated = new Map<
|
|
260
240
|
string,
|
|
261
241
|
ReturnType<typeof validateShareBundle>
|
|
@@ -383,7 +363,7 @@ export class SharedReviewStore {
|
|
|
383
363
|
throw new ReviewInputError("Shared review is not available.", 404);
|
|
384
364
|
|
|
385
365
|
if (!saved.snapshot.pins) {
|
|
386
|
-
await this.prepareUnpinned(id, saved.
|
|
366
|
+
await this.prepareUnpinned(id, saved.bundle.repository?.cloneUrl);
|
|
387
367
|
|
|
388
368
|
return;
|
|
389
369
|
}
|
|
@@ -405,7 +385,7 @@ export class SharedReviewStore {
|
|
|
405
385
|
await rm(root, { recursive: true, force: true });
|
|
406
386
|
await this.fetchRepository(
|
|
407
387
|
root,
|
|
408
|
-
saved.
|
|
388
|
+
saved.bundle.repository!.cloneUrl,
|
|
409
389
|
saved.snapshot.pins,
|
|
410
390
|
false,
|
|
411
391
|
commits,
|
|
@@ -481,7 +461,7 @@ export class SharedReviewStore {
|
|
|
481
461
|
add({ ...anchorPins(source, pins), repositoryId }, source);
|
|
482
462
|
|
|
483
463
|
if (pins)
|
|
484
|
-
for (const map of Object.values(saved.
|
|
464
|
+
for (const map of Object.values(saved.maps))
|
|
485
465
|
for (const element of map.elements)
|
|
486
466
|
for (const range of element.sourceRanges ?? [])
|
|
487
467
|
add(pins, { ...range, side: map.side });
|
|
@@ -588,6 +568,13 @@ export class SharedReviewStore {
|
|
|
588
568
|
|
|
589
569
|
for (const id of await readdir(this.root)) {
|
|
590
570
|
if (!/^shared-[a-f0-9]{64}$/.test(id)) continue;
|
|
571
|
+
|
|
572
|
+
// Earlier share formats are not read; reopening the link imports again.
|
|
573
|
+
if (!existsSync(path.join(this.root, id, "bundle.json"))) {
|
|
574
|
+
await rm(path.join(this.root, id), { recursive: true, force: true });
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
|
|
591
578
|
let saved: Awaited<ReturnType<SharedReviewStore["readValidated"]>>;
|
|
592
579
|
|
|
593
580
|
try {
|
|
@@ -603,11 +590,8 @@ export class SharedReviewStore {
|
|
|
603
590
|
continue;
|
|
604
591
|
}
|
|
605
592
|
|
|
606
|
-
const {
|
|
607
|
-
this.loaded.set(id, {
|
|
608
|
-
manifest: bundle.manifest,
|
|
609
|
-
attribution: bundle.attribution,
|
|
610
|
-
});
|
|
593
|
+
const { attribution, validated } = saved;
|
|
594
|
+
this.loaded.set(id, { attribution });
|
|
611
595
|
this.validated.set(id, validated);
|
|
612
596
|
|
|
613
597
|
let prepared = false;
|
|
@@ -641,7 +625,7 @@ export class SharedReviewStore {
|
|
|
641
625
|
}
|
|
642
626
|
|
|
643
627
|
if (!validated.snapshot.pins) {
|
|
644
|
-
if (this.repositories.has(id) || !validated.
|
|
628
|
+
if (this.repositories.has(id) || !validated.bundle.repository)
|
|
645
629
|
this.setStatus(id, "ready");
|
|
646
630
|
else void this.prepare(id).catch(() => {});
|
|
647
631
|
} else if (
|
|
@@ -719,9 +703,9 @@ export class SharedReviewStore {
|
|
|
719
703
|
}
|
|
720
704
|
|
|
721
705
|
get(id: string) {
|
|
722
|
-
const
|
|
706
|
+
const loaded = this.loaded.get(id);
|
|
723
707
|
|
|
724
|
-
if (!
|
|
708
|
+
if (!loaded)
|
|
725
709
|
throw new ReviewInputError("Shared review is not available.", 404);
|
|
726
710
|
const validated = this.validated.get(id)!;
|
|
727
711
|
const repositoryId = this.repositories.get(id);
|
|
@@ -757,8 +741,8 @@ export class SharedReviewStore {
|
|
|
757
741
|
? { kind: "project" as const, repositoryId }
|
|
758
742
|
: undefined,
|
|
759
743
|
shared: {
|
|
760
|
-
...
|
|
761
|
-
cloneUrl: bundle.
|
|
744
|
+
...loaded.attribution,
|
|
745
|
+
cloneUrl: validated.bundle.repository?.cloneUrl,
|
|
762
746
|
},
|
|
763
747
|
},
|
|
764
748
|
};
|
|
@@ -795,25 +779,6 @@ export class SharedReviewStore {
|
|
|
795
779
|
return this.local?.store.withDiffStats(reviews) ?? reviews;
|
|
796
780
|
}
|
|
797
781
|
|
|
798
|
-
async readObject(id: string, objectId: string) {
|
|
799
|
-
const entry = this.get(id).manifest.objects.find(
|
|
800
|
-
(object) => object.id === objectId,
|
|
801
|
-
);
|
|
802
|
-
|
|
803
|
-
if (!entry)
|
|
804
|
-
throw new ReviewInputError("Shared object is unavailable.", 404);
|
|
805
|
-
const file = path.join(this.root, id, objectId);
|
|
806
|
-
|
|
807
|
-
if ((await stat(file)).size !== entry.size)
|
|
808
|
-
throw new Error("Corrupt cached shared object.");
|
|
809
|
-
const bytes = await readFile(file);
|
|
810
|
-
|
|
811
|
-
if (digestBytes(bytes) !== entry.sha256)
|
|
812
|
-
throw new Error("Corrupt cached shared object.");
|
|
813
|
-
|
|
814
|
-
return bytes;
|
|
815
|
-
}
|
|
816
|
-
|
|
817
782
|
async removeLocal(id: string) {
|
|
818
783
|
await this.jobs.get(id)?.catch(() => {});
|
|
819
784
|
|
|
@@ -893,30 +858,32 @@ export class SharedReviewStore {
|
|
|
893
858
|
for (const listener of this.listeners) listener();
|
|
894
859
|
}
|
|
895
860
|
|
|
896
|
-
async import(
|
|
861
|
+
async import(
|
|
862
|
+
origin: string,
|
|
863
|
+
capability: string,
|
|
864
|
+
bundle: ShareBundle,
|
|
865
|
+
attribution?: ShareAttribution,
|
|
866
|
+
) {
|
|
897
867
|
const validated = validateShareBundle(bundle);
|
|
898
|
-
const id = sharedReviewId(origin,
|
|
868
|
+
const id = sharedReviewId(origin, capability);
|
|
899
869
|
this.assertWritable(id);
|
|
900
870
|
await mkdir(this.root, { recursive: true, mode: 0o700 });
|
|
901
871
|
const staging = await mkdtemp(path.join(this.root, ".import-"));
|
|
902
872
|
|
|
903
873
|
try {
|
|
904
874
|
await writeFile(
|
|
905
|
-
path.join(staging, "
|
|
906
|
-
JSON.stringify(bundle
|
|
875
|
+
path.join(staging, "bundle.json"),
|
|
876
|
+
JSON.stringify(validated.bundle),
|
|
907
877
|
{ mode: 0o600 },
|
|
908
878
|
);
|
|
909
879
|
|
|
910
|
-
if (
|
|
880
|
+
if (attribution)
|
|
911
881
|
await writeFile(
|
|
912
882
|
path.join(staging, "attribution.json"),
|
|
913
|
-
JSON.stringify(
|
|
883
|
+
JSON.stringify(attribution),
|
|
914
884
|
{ mode: 0o600 },
|
|
915
885
|
);
|
|
916
886
|
|
|
917
|
-
for (const [objectId, bytes] of bundle.objects)
|
|
918
|
-
await writeFile(path.join(staging, objectId), bytes, { mode: 0o600 });
|
|
919
|
-
|
|
920
887
|
try {
|
|
921
888
|
await rename(staging, path.join(this.root, id));
|
|
922
889
|
} catch (error) {
|
|
@@ -930,19 +897,14 @@ export class SharedReviewStore {
|
|
|
930
897
|
throw error;
|
|
931
898
|
const existing = await this.read(id);
|
|
932
899
|
|
|
933
|
-
if (
|
|
934
|
-
|
|
935
|
-
)
|
|
936
|
-
throw new Error("An immutable share changed its manifest.");
|
|
900
|
+
if (JSON.stringify(existing) !== JSON.stringify(validated.bundle))
|
|
901
|
+
throw new Error("An immutable share changed its bundle.");
|
|
937
902
|
}
|
|
938
903
|
} finally {
|
|
939
904
|
await rm(staging, { recursive: true, force: true });
|
|
940
905
|
}
|
|
941
906
|
|
|
942
|
-
this.loaded.set(id, {
|
|
943
|
-
manifest: bundle.manifest,
|
|
944
|
-
attribution: bundle.attribution,
|
|
945
|
-
});
|
|
907
|
+
this.loaded.set(id, { attribution });
|
|
946
908
|
this.validated.set(id, validated);
|
|
947
909
|
|
|
948
910
|
for (const listener of this.listeners) listener();
|
|
@@ -953,35 +915,19 @@ export class SharedReviewStore {
|
|
|
953
915
|
}
|
|
954
916
|
|
|
955
917
|
async read(id: string): Promise<ShareBundle> {
|
|
956
|
-
return (await this.readValidated(id)).bundle;
|
|
918
|
+
return (await this.readValidated(id)).validated.bundle;
|
|
957
919
|
}
|
|
958
920
|
|
|
959
921
|
private async readValidated(id: string) {
|
|
960
922
|
if (!/^shared-[a-f0-9]{64}$/.test(id))
|
|
961
923
|
throw new Error("Invalid shared review ID.");
|
|
962
924
|
const dir = path.join(this.root, id);
|
|
925
|
+
const file = path.join(dir, "bundle.json");
|
|
963
926
|
|
|
964
|
-
if (
|
|
965
|
-
|
|
966
|
-
MAX_SHARE_MANIFEST_BYTES
|
|
967
|
-
)
|
|
968
|
-
throw new Error("Shared manifest is too large.");
|
|
969
|
-
|
|
970
|
-
const manifest: ShareManifest = shareManifestSchema.parse(
|
|
971
|
-
JSON.parse(await readFile(path.join(dir, "manifest.json"), "utf8")),
|
|
972
|
-
);
|
|
973
|
-
|
|
974
|
-
const objects = new Map<string, Uint8Array>();
|
|
975
|
-
|
|
976
|
-
for (const object of manifest.objects) {
|
|
977
|
-
const file = path.join(dir, object.id);
|
|
927
|
+
if ((await stat(file)).size > MAX_SHARE_BYTES)
|
|
928
|
+
throw new Error("Shared bundle is too large.");
|
|
978
929
|
|
|
979
|
-
|
|
980
|
-
throw new Error("Corrupt cached shared object.");
|
|
981
|
-
objects.set(object.id, await readFile(file));
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
let attribution: ShareBundle["attribution"];
|
|
930
|
+
let attribution: ShareAttribution | undefined;
|
|
985
931
|
|
|
986
932
|
try {
|
|
987
933
|
attribution = z
|
|
@@ -995,9 +941,9 @@ export class SharedReviewStore {
|
|
|
995
941
|
/* Local-only fixtures have no hosted sender. */
|
|
996
942
|
}
|
|
997
943
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
944
|
+
return {
|
|
945
|
+
attribution,
|
|
946
|
+
validated: validateShareBundle(JSON.parse(await readFile(file, "utf8"))),
|
|
947
|
+
};
|
|
1002
948
|
}
|
|
1003
949
|
}
|
package/src/sharing/index.ts
CHANGED
package/src/sharing/routes.ts
CHANGED
|
@@ -7,28 +7,29 @@ export class SharedReviewData {
|
|
|
7
7
|
constructor(private readonly store: SharedReviewStore) {}
|
|
8
8
|
|
|
9
9
|
map(id: string, resourceId: string) {
|
|
10
|
-
const
|
|
10
|
+
const { maps } = this.store.get(id);
|
|
11
11
|
|
|
12
|
-
if (!
|
|
12
|
+
if (!Object.hasOwn(maps, resourceId))
|
|
13
13
|
throw new ReviewInputError("Map is not included in this share.", 404);
|
|
14
14
|
|
|
15
|
-
return
|
|
15
|
+
return maps[resourceId]!;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
async resource(id: string, resourceId: string) {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
const { bundle } = this.store.get(id);
|
|
20
|
+
|
|
21
|
+
if (Object.hasOwn(bundle.images, resourceId))
|
|
22
|
+
return {
|
|
23
|
+
data: Buffer.from(bundle.images[resourceId]!.png, "base64"),
|
|
24
|
+
mimeType: "image/png",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
if (Object.hasOwn(bundle.traces, resourceId))
|
|
28
|
+
return {
|
|
29
|
+
data: Buffer.from(JSON.stringify(bundle.traces[resourceId])),
|
|
30
|
+
mimeType: "application/json",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
throw new ReviewInputError("Resource is not included in this share.", 404);
|
|
33
34
|
}
|
|
34
35
|
}
|
|
@@ -148,6 +148,7 @@ export const SETTING_NAME = [
|
|
|
148
148
|
"sharing_enabled",
|
|
149
149
|
"diffr_config",
|
|
150
150
|
"theme",
|
|
151
|
+
"diff_theme",
|
|
151
152
|
"ctrl_tab",
|
|
152
153
|
"open_files_in",
|
|
153
154
|
"ready_notification",
|
|
@@ -155,6 +156,7 @@ export const SETTING_NAME = [
|
|
|
155
156
|
"code_font_size",
|
|
156
157
|
"document_font_size",
|
|
157
158
|
"reading_font",
|
|
159
|
+
"reading_font_family",
|
|
158
160
|
"code_font_family",
|
|
159
161
|
"font_ligatures",
|
|
160
162
|
"diff_font_size",
|
|
@@ -621,6 +623,15 @@ export const UI_TELEMETRY_EVENTS = {
|
|
|
621
623
|
event: "review_diff_failed",
|
|
622
624
|
properties: { reason: DIFF_FAILED_REASON, files: "number" },
|
|
623
625
|
},
|
|
626
|
+
diff_loaded: {
|
|
627
|
+
event: "review_diff_loaded",
|
|
628
|
+
properties: {
|
|
629
|
+
first_file_ms: "number",
|
|
630
|
+
updates_ms: "number",
|
|
631
|
+
files: "number",
|
|
632
|
+
update_errors: "number",
|
|
633
|
+
},
|
|
634
|
+
},
|
|
624
635
|
discord_clicked: {
|
|
625
636
|
event: "review_discord_clicked",
|
|
626
637
|
properties: { via: DISCORD_VIA },
|
package/dist/profile-BopGMZUh.js
DELETED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { a as jsonNumber, c as jsonString, o as jsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import path from "node:path";
|
|
4
5
|
import { chmodSync, chownSync, closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeSync } from "node:fs";
|
|
5
6
|
import { chmod, chown, lstat, mkdir, open, readFile, realpath, rename, rm, stat, utimes, writeFile } from "node:fs/promises";
|
|
6
|
-
import path from "node:path";
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
9
9
|
import { setTimeout } from "node:timers/promises";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import fs from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
6
6
|
import "node:timers/promises";
|