@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/client.ts
CHANGED
|
@@ -2,9 +2,10 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
|
|
3
3
|
import { type JsonValue, parseJsonText } from "@dev.fast/json";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
type
|
|
7
|
-
|
|
5
|
+
MAX_SHARE_BYTES,
|
|
6
|
+
type ShareBundle,
|
|
7
|
+
capabilitySchema,
|
|
8
|
+
shareBundleSchema,
|
|
8
9
|
} from "@dev.fast/review-share-protocol";
|
|
9
10
|
import {
|
|
10
11
|
StreamLimitError,
|
|
@@ -12,46 +13,18 @@ import {
|
|
|
12
13
|
} from "@review/server/bounded-stream.js";
|
|
13
14
|
import { z } from "zod";
|
|
14
15
|
|
|
15
|
-
import {
|
|
16
|
-
import { validateShareBundle } from "./import.js";
|
|
16
|
+
import { shareRequestBody } from "./export.js";
|
|
17
17
|
|
|
18
|
-
const
|
|
19
|
-
url: z.url(),
|
|
20
|
-
headers: z.record(z.string(), z.string()),
|
|
21
|
-
expiresAt: z.string(),
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const createdSchema = z.strictObject({
|
|
25
|
-
shareId: z.uuid(),
|
|
26
|
-
upload: uploadSchema,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const registeredSchema = z.object({
|
|
30
|
-
registered: z.literal(true),
|
|
31
|
-
uploads: z.record(z.string(), uploadSchema),
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const UPLOAD_CONCURRENCY = 4;
|
|
35
|
-
|
|
36
|
-
// Presigned URLs and headers are larger than manifest object descriptors.
|
|
37
|
-
const MAX_UPLOAD_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
18
|
+
const MAX_RESPONSE_BYTES = MAX_SHARE_BYTES;
|
|
38
19
|
|
|
39
20
|
const linkSchema = z.strictObject({ shareId: z.uuid(), url: z.url() });
|
|
40
21
|
|
|
41
|
-
const receivedSchema = z.
|
|
42
|
-
manifest: shareManifestSchema,
|
|
22
|
+
const receivedSchema = z.object({
|
|
43
23
|
sender: z.strictObject({ login: z.string().min(1).max(256) }),
|
|
44
24
|
sharedAt: z.number(),
|
|
25
|
+
size: z.number().int().positive().max(MAX_SHARE_BYTES),
|
|
45
26
|
});
|
|
46
27
|
|
|
47
|
-
const signedUrlSchema = z.url().refine((value) => {
|
|
48
|
-
const url = new URL(value);
|
|
49
|
-
|
|
50
|
-
return url.protocol === "https:" && !url.username && !url.password;
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const downloadSchema = z.strictObject({ url: z.url(), expiresAt: z.string() });
|
|
54
|
-
|
|
55
28
|
/** Size is checked while reading, even if Content-Length is absent or false. */
|
|
56
29
|
export async function readBoundedBytes(
|
|
57
30
|
response: Response,
|
|
@@ -83,25 +56,7 @@ export class ShareAuthError extends Error {
|
|
|
83
56
|
}
|
|
84
57
|
}
|
|
85
58
|
|
|
86
|
-
|
|
87
|
-
constructor(cause: unknown) {
|
|
88
|
-
super(
|
|
89
|
-
cause instanceof Error
|
|
90
|
-
? cause.message
|
|
91
|
-
: "Repository verification failed.",
|
|
92
|
-
{ cause },
|
|
93
|
-
);
|
|
94
|
-
this.name = "SharePreflightError";
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
class ShareRequestError extends Error {
|
|
99
|
-
constructor(readonly status: number) {
|
|
100
|
-
super(`Share request failed (${status}).`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Account tokens go only to the configured API origin, never to object storage. */
|
|
59
|
+
/** Account tokens go only to the configured API origin. */
|
|
105
60
|
export class ShareClient {
|
|
106
61
|
private readonly origin: string;
|
|
107
62
|
constructor(
|
|
@@ -124,221 +79,98 @@ export class ShareClient {
|
|
|
124
79
|
private async api(
|
|
125
80
|
route: string,
|
|
126
81
|
method = "GET",
|
|
127
|
-
body?:
|
|
128
|
-
|
|
129
|
-
maxBytes = MAX_SHARE_MANIFEST_BYTES,
|
|
82
|
+
body?: string,
|
|
83
|
+
anonymous = false,
|
|
130
84
|
): Promise<JsonValue> {
|
|
85
|
+
return parseJsonText(
|
|
86
|
+
Buffer.from(
|
|
87
|
+
await this.request(route, method, body, anonymous, MAX_RESPONSE_BYTES),
|
|
88
|
+
).toString(),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private async request(
|
|
93
|
+
route: string,
|
|
94
|
+
method: string,
|
|
95
|
+
body: string | undefined,
|
|
96
|
+
anonymous: boolean,
|
|
97
|
+
maxBytes: number,
|
|
98
|
+
) {
|
|
131
99
|
const headers = new Headers({ "content-type": "application/json" });
|
|
132
100
|
|
|
133
|
-
if (
|
|
134
|
-
|
|
101
|
+
if (this.token && !anonymous)
|
|
102
|
+
headers.set("authorization", `Bearer ${this.token}`);
|
|
135
103
|
|
|
136
104
|
const init: RequestInit = {
|
|
137
105
|
method,
|
|
138
106
|
redirect: "error",
|
|
139
|
-
signal: AbortSignal.timeout(
|
|
107
|
+
signal: AbortSignal.timeout(300_000),
|
|
140
108
|
headers,
|
|
141
109
|
};
|
|
142
110
|
|
|
143
|
-
if (body !== undefined) init.body =
|
|
111
|
+
if (body !== undefined) init.body = body;
|
|
144
112
|
const response = await this.send(this.origin + route, init);
|
|
145
113
|
|
|
146
114
|
if (!response.ok) {
|
|
147
115
|
await response.body?.cancel();
|
|
148
116
|
|
|
149
|
-
if (response.status === 401 && !
|
|
150
|
-
throw new
|
|
117
|
+
if (response.status === 401 && !anonymous) throw new ShareAuthError();
|
|
118
|
+
throw new Error(`Share request failed (${response.status}).`);
|
|
151
119
|
}
|
|
152
120
|
|
|
153
|
-
return
|
|
154
|
-
Buffer.from(await readBoundedBytes(response, maxBytes)).toString(),
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
private async upload(
|
|
159
|
-
upload: z.infer<typeof uploadSchema>,
|
|
160
|
-
bytes: Uint8Array,
|
|
161
|
-
) {
|
|
162
|
-
if (!signedUrlSchema.safeParse(upload.url).success)
|
|
163
|
-
throw new Error("Invalid object upload URL.");
|
|
164
|
-
|
|
165
|
-
const response = await this.send(upload.url, {
|
|
166
|
-
method: "PUT",
|
|
167
|
-
headers: upload.headers,
|
|
168
|
-
body: Buffer.from(bytes),
|
|
169
|
-
redirect: "error",
|
|
170
|
-
signal: AbortSignal.timeout(120_000),
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// Conditional create can return 412 after a successful upload whose response
|
|
174
|
-
// was lost. Completion checks the actual stored digest before publication.
|
|
175
|
-
await response.body?.cancel();
|
|
176
|
-
|
|
177
|
-
if (!response.ok && response.status !== 412)
|
|
178
|
-
throw new Error(`Object upload failed (${response.status}).`);
|
|
121
|
+
return readBoundedBytes(response, maxBytes);
|
|
179
122
|
}
|
|
180
123
|
|
|
181
|
-
async create(
|
|
182
|
-
|
|
183
|
-
requestId: string = randomUUID(),
|
|
184
|
-
beforeUpload?: () => Promise<void>,
|
|
185
|
-
) {
|
|
186
|
-
try {
|
|
187
|
-
validateShareBundle(bundle);
|
|
188
|
-
} catch (cause) {
|
|
189
|
-
throw new SharePreflightError(
|
|
190
|
-
new Error(
|
|
191
|
-
"Whiteboard could not prepare this review for sharing. Please report this issue.",
|
|
192
|
-
{ cause },
|
|
193
|
-
),
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const manifestBytes = Buffer.from(JSON.stringify(bundle.manifest));
|
|
198
|
-
|
|
199
|
-
const created = createdSchema.parse(
|
|
200
|
-
await this.api("/api/shares", "POST", {
|
|
201
|
-
requestId,
|
|
202
|
-
manifest: {
|
|
203
|
-
size: manifestBytes.byteLength,
|
|
204
|
-
sha256: digestBytes(manifestBytes),
|
|
205
|
-
},
|
|
206
|
-
}),
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
const route = `/api/shares/${created.shareId}`;
|
|
210
|
-
await this.upload(created.upload, manifestBytes);
|
|
211
|
-
|
|
212
|
-
const { uploads } = registeredSchema.parse(
|
|
124
|
+
async create(bundle: ShareBundle, requestId: string = randomUUID()) {
|
|
125
|
+
return linkSchema.parse(
|
|
213
126
|
await this.api(
|
|
214
|
-
|
|
127
|
+
"/api/v2/shares",
|
|
215
128
|
"POST",
|
|
216
|
-
|
|
217
|
-
undefined,
|
|
218
|
-
MAX_UPLOAD_RESPONSE_BYTES,
|
|
129
|
+
shareRequestBody(bundle, requestId),
|
|
219
130
|
),
|
|
220
131
|
);
|
|
221
|
-
|
|
222
|
-
try {
|
|
223
|
-
if (bundle.manifest.objects.some(({ id }) => !uploads[id]))
|
|
224
|
-
throw new Error("The share service omitted an object upload URL.");
|
|
225
|
-
|
|
226
|
-
await beforeUpload?.();
|
|
227
|
-
} catch (error) {
|
|
228
|
-
// A retry may recover an already published share after a lost response.
|
|
229
|
-
await this.recoverLink(created.shareId)
|
|
230
|
-
.catch(async (failure) => {
|
|
231
|
-
if (failure instanceof ShareRequestError && failure.status === 409)
|
|
232
|
-
await this.revoke(created.shareId);
|
|
233
|
-
})
|
|
234
|
-
.catch(() => {});
|
|
235
|
-
throw new SharePreflightError(error);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
for (
|
|
239
|
-
let offset = 0;
|
|
240
|
-
offset < bundle.manifest.objects.length;
|
|
241
|
-
offset += UPLOAD_CONCURRENCY
|
|
242
|
-
) {
|
|
243
|
-
const results = await Promise.allSettled(
|
|
244
|
-
bundle.manifest.objects
|
|
245
|
-
.slice(offset, offset + UPLOAD_CONCURRENCY)
|
|
246
|
-
.map(async (object) => {
|
|
247
|
-
await this.upload(
|
|
248
|
-
uploads[object.id]!,
|
|
249
|
-
bundle.objects.get(object.id)!,
|
|
250
|
-
);
|
|
251
|
-
}),
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
const failed = results.find((result) => result.status === "rejected");
|
|
255
|
-
|
|
256
|
-
if (failed) throw failed.reason;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
for (
|
|
260
|
-
let batch = 0;
|
|
261
|
-
batch <= Math.ceil(bundle.manifest.objects.length / 32);
|
|
262
|
-
batch++
|
|
263
|
-
) {
|
|
264
|
-
const result = await this.api(`${route}/complete`, "POST", {});
|
|
265
|
-
|
|
266
|
-
if (linkSchema.safeParse(result).success) return linkSchema.parse(result);
|
|
267
|
-
z.strictObject({ complete: z.literal(false) }).parse(result);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
throw new Error("Share verification did not finish.");
|
|
271
132
|
}
|
|
272
133
|
|
|
273
134
|
async list(cursor?: string) {
|
|
274
135
|
const query = cursor ? `?cursor=${z.uuid().parse(cursor)}` : "";
|
|
275
136
|
|
|
276
|
-
return this.api(`/api/shares${query}`);
|
|
137
|
+
return this.api(`/api/v2/shares${query}`);
|
|
277
138
|
}
|
|
278
139
|
|
|
279
140
|
async recoverLink(shareId: string) {
|
|
280
141
|
return linkSchema.parse(
|
|
281
|
-
await this.api(`/api/shares/${z.uuid().parse(shareId)}/link`),
|
|
142
|
+
await this.api(`/api/v2/shares/${z.uuid().parse(shareId)}/link`),
|
|
282
143
|
);
|
|
283
144
|
}
|
|
284
145
|
|
|
285
146
|
async revoke(shareId: string) {
|
|
286
|
-
return this.api(`/api/shares/${z.uuid().parse(shareId)}`, "DELETE");
|
|
147
|
+
return this.api(`/api/v2/shares/${z.uuid().parse(shareId)}`, "DELETE");
|
|
287
148
|
}
|
|
288
149
|
|
|
289
|
-
|
|
290
|
-
|
|
150
|
+
/** The bundle and who shared it, read with the link's capability. */
|
|
151
|
+
async download(capability: string) {
|
|
152
|
+
const route = `/api/v2/shared/${capabilitySchema.parse(capability)}`;
|
|
291
153
|
|
|
292
154
|
const received = receivedSchema.parse(
|
|
293
|
-
await this.api(route, "GET", undefined,
|
|
155
|
+
await this.api(route, "GET", undefined, true),
|
|
294
156
|
);
|
|
295
157
|
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
"GET",
|
|
304
|
-
undefined,
|
|
305
|
-
capability,
|
|
306
|
-
),
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
if (!signedUrlSchema.safeParse(signed.url).success)
|
|
310
|
-
throw new Error("Invalid object download URL.");
|
|
311
|
-
|
|
312
|
-
const bytes = await readBoundedBytes(
|
|
313
|
-
await this.send(signed.url, {
|
|
314
|
-
redirect: "error",
|
|
315
|
-
signal: AbortSignal.timeout(120_000),
|
|
316
|
-
}),
|
|
317
|
-
object.size,
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
if (
|
|
321
|
-
bytes.byteLength !== object.size ||
|
|
322
|
-
digestBytes(bytes) !== object.sha256
|
|
323
|
-
)
|
|
324
|
-
throw new Error("Downloaded share object failed its integrity check.");
|
|
325
|
-
objects.set(object.id, bytes);
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
for (let start = 0; start < manifest.objects.length; start += 4)
|
|
329
|
-
await Promise.all(manifest.objects.slice(start, start + 4).map(download));
|
|
158
|
+
const bytes = await this.request(
|
|
159
|
+
`${route}/bundle`,
|
|
160
|
+
"GET",
|
|
161
|
+
undefined,
|
|
162
|
+
true,
|
|
163
|
+
MAX_SHARE_BYTES,
|
|
164
|
+
);
|
|
330
165
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
166
|
+
return {
|
|
167
|
+
bundle: shareBundleSchema.parse(
|
|
168
|
+
parseJsonText(new TextDecoder().decode(bytes)),
|
|
169
|
+
),
|
|
334
170
|
attribution: {
|
|
335
171
|
login: received.sender.login,
|
|
336
172
|
sharedAt: received.sharedAt,
|
|
337
173
|
},
|
|
338
174
|
};
|
|
339
|
-
|
|
340
|
-
// Document validation happens once at the importer boundary.
|
|
341
|
-
|
|
342
|
-
return bundle;
|
|
343
175
|
}
|
|
344
176
|
}
|