@neta-art/cohub 8.10.2 → 8.12.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/dist/board/animation.js +14 -2
- package/dist/board/core/file-preview.d.ts +20 -141
- package/dist/board/core/file-preview.js +25 -202
- package/dist/board/core/file-snapshot.d.ts +106 -0
- package/dist/board/core/file-snapshot.js +503 -0
- package/dist/board/index.d.ts +4 -2
- package/dist/board/index.js +4 -2
- package/dist/board/mutation.js +2 -1
- package/dist/board/render/board-background.d.ts +16 -0
- package/dist/board/render/{themes/clean-theme.js → board-background.js} +28 -35
- package/dist/board/render/index.d.ts +3 -3
- package/dist/board/render/index.js +2 -2
- package/dist/board/render/renderers/file-card-renderer.js +62 -17
- package/dist/board/replay.d.ts +52 -0
- package/dist/board/replay.js +261 -0
- package/dist/board/semantic-document.d.ts +9 -2
- package/dist/board/semantic-document.js +1 -3
- package/dist/chunks/environment.d.ts +272 -7
- package/dist/chunks/environment.js +1 -0
- package/dist/chunks/http.d.ts +109 -7
- package/dist/chunks/http.js +47 -10
- package/dist/chunks/websocket.d.ts +1 -1
- package/dist/http.d.ts +3 -3
- package/dist/index.d.ts +65 -4
- package/dist/index.js +514 -64
- package/dist/protocol/dist/board-animation.d.ts +1 -0
- package/dist/protocol/dist/board-animation.js +34 -0
- package/dist/protocol/dist/board-authoring.d.ts +2 -1
- package/dist/protocol/dist/board-capability-registry.js +53 -3
- package/dist/protocol/dist/board-codec.js +149 -2
- package/dist/protocol/dist/board-constants.js +29 -7
- package/dist/protocol/dist/board-document.d.ts +30 -16
- package/dist/protocol/dist/board-document.js +15 -12
- package/dist/protocol/dist/board-effect.d.ts +4 -2
- package/dist/protocol/dist/board-effect.js +3 -2
- package/dist/protocol/dist/board.d.ts +148 -3
- package/dist/protocol/dist/board.js +7 -0
- package/dist/protocol/dist/index.d.ts +4 -2
- package/dist/protocol/dist/provenance.d.ts +21 -0
- package/dist/types.d.ts +1 -1
- package/docs/app-runtime-guide.md +28 -3
- package/package.json +1 -1
- package/dist/board/render/themes/board-theme-registry.d.ts +0 -22
- package/dist/board/render/themes/board-theme-registry.js +0 -13
- package/dist/board/render/themes/clean-theme.d.ts +0 -5
package/dist/index.js
CHANGED
|
@@ -8,8 +8,8 @@ import { z } from "zod";
|
|
|
8
8
|
const BOARD_DOCUMENT_KIND = "cohub.board";
|
|
9
9
|
const BOARD_MANIFEST_KIND = "cohub.board.manifest";
|
|
10
10
|
const idSchema = z.string().min(1).max(160);
|
|
11
|
-
const jsonObjectSchema = z.record(z.string(), z.unknown());
|
|
12
|
-
const finiteSchema = z.number().finite();
|
|
11
|
+
const jsonObjectSchema$1 = z.record(z.string(), z.unknown());
|
|
12
|
+
const finiteSchema$1 = z.number().finite();
|
|
13
13
|
z.object({
|
|
14
14
|
kind: z.literal(BOARD_MANIFEST_KIND),
|
|
15
15
|
version: z.literal(1),
|
|
@@ -17,18 +17,18 @@ z.object({
|
|
|
17
17
|
title: z.string().min(1).max(255)
|
|
18
18
|
});
|
|
19
19
|
z.object({
|
|
20
|
-
centerX: finiteSchema,
|
|
21
|
-
centerY: finiteSchema,
|
|
22
|
-
zoom: finiteSchema.positive()
|
|
20
|
+
centerX: finiteSchema$1,
|
|
21
|
+
centerY: finiteSchema$1,
|
|
22
|
+
zoom: finiteSchema$1.positive()
|
|
23
23
|
});
|
|
24
24
|
const BoardCameraFocusSchema = z.discriminatedUnion("type", [
|
|
25
25
|
z.object({
|
|
26
26
|
type: z.literal("rect"),
|
|
27
27
|
rect: z.object({
|
|
28
|
-
x: finiteSchema,
|
|
29
|
-
y: finiteSchema,
|
|
30
|
-
width: finiteSchema.positive(),
|
|
31
|
-
height: finiteSchema.positive()
|
|
28
|
+
x: finiteSchema$1,
|
|
29
|
+
y: finiteSchema$1,
|
|
30
|
+
width: finiteSchema$1.positive(),
|
|
31
|
+
height: finiteSchema$1.positive()
|
|
32
32
|
})
|
|
33
33
|
}),
|
|
34
34
|
z.object({
|
|
@@ -47,9 +47,9 @@ const BoardCameraFocusSchema = z.discriminatedUnion("type", [
|
|
|
47
47
|
const BoardCameraFocusParamsSchema = z.object({
|
|
48
48
|
focus: BoardCameraFocusSchema,
|
|
49
49
|
fit: z.enum(["contain", "cover"]).default("contain"),
|
|
50
|
-
padding: finiteSchema.nonnegative().default(32),
|
|
51
|
-
minZoom: finiteSchema.positive().optional(),
|
|
52
|
-
maxZoom: finiteSchema.positive().optional()
|
|
50
|
+
padding: finiteSchema$1.nonnegative().default(32),
|
|
51
|
+
minZoom: finiteSchema$1.positive().optional(),
|
|
52
|
+
maxZoom: finiteSchema$1.positive().optional()
|
|
53
53
|
}).superRefine((value, context) => {
|
|
54
54
|
if (value.minZoom !== void 0 && value.maxZoom !== void 0 && value.minZoom > value.maxZoom) context.addIssue({
|
|
55
55
|
code: "custom",
|
|
@@ -62,17 +62,17 @@ z.object({
|
|
|
62
62
|
type: z.string().min(1).max(40),
|
|
63
63
|
parentId: idSchema.nullable(),
|
|
64
64
|
orderKey: z.string().max(4096).nullable(),
|
|
65
|
-
x: finiteSchema,
|
|
66
|
-
y: finiteSchema,
|
|
67
|
-
width: finiteSchema.positive(),
|
|
68
|
-
height: finiteSchema.positive(),
|
|
69
|
-
rotation: finiteSchema,
|
|
65
|
+
x: finiteSchema$1,
|
|
66
|
+
y: finiteSchema$1,
|
|
67
|
+
width: finiteSchema$1.positive(),
|
|
68
|
+
height: finiteSchema$1.positive(),
|
|
69
|
+
rotation: finiteSchema$1,
|
|
70
70
|
refKind: z.string().max(40).nullable(),
|
|
71
71
|
refPath: z.string().max(4096).nullable(),
|
|
72
72
|
refUrl: z.string().max(4096).nullable(),
|
|
73
|
-
view: jsonObjectSchema,
|
|
74
|
-
style: jsonObjectSchema,
|
|
75
|
-
data: jsonObjectSchema
|
|
73
|
+
view: jsonObjectSchema$1,
|
|
74
|
+
style: jsonObjectSchema$1,
|
|
75
|
+
data: jsonObjectSchema$1
|
|
76
76
|
});
|
|
77
77
|
const stripBoardServerFields = (value) => {
|
|
78
78
|
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
@@ -86,7 +86,7 @@ const BoardCreateInputSchema = z.object({
|
|
|
86
86
|
/** Reused by clients when board creation is interrupted and retried. */
|
|
87
87
|
mutationId: z.string().max(128).optional(),
|
|
88
88
|
title: z.string().min(1).max(255).optional(),
|
|
89
|
-
metadata: jsonObjectSchema.optional(),
|
|
89
|
+
metadata: jsonObjectSchema$1.optional(),
|
|
90
90
|
items: z.array(BoardAuthoringItemSchema).max(5e4).optional(),
|
|
91
91
|
connections: z.array(BoardConnectionSchema).max(5e4).optional(),
|
|
92
92
|
effects: z.array(BoardCreateEffectInputSchema).max(5e4).optional(),
|
|
@@ -132,17 +132,24 @@ z.object({
|
|
|
132
132
|
effectIds: z.array(idSchema).max(5e4).optional(),
|
|
133
133
|
compositionIds: z.array(idSchema).max(5e4).optional(),
|
|
134
134
|
viewport: z.object({
|
|
135
|
-
x: finiteSchema,
|
|
136
|
-
y: finiteSchema,
|
|
137
|
-
width: finiteSchema.positive(),
|
|
138
|
-
height: finiteSchema.positive()
|
|
135
|
+
x: finiteSchema$1,
|
|
136
|
+
y: finiteSchema$1,
|
|
137
|
+
width: finiteSchema$1.positive(),
|
|
138
|
+
height: finiteSchema$1.positive()
|
|
139
139
|
}).optional()
|
|
140
140
|
});
|
|
141
|
+
z.object({
|
|
142
|
+
/** Return transactions with `version < before`. Omit for the newest page. */
|
|
143
|
+
before: z.number().int().positive().optional(),
|
|
144
|
+
limit: z.number().int().min(1).max(500).default(200),
|
|
145
|
+
/** Include the current rows on the newest page (default true). Tail refreshes turn this off. */
|
|
146
|
+
snapshot: z.boolean().default(true)
|
|
147
|
+
});
|
|
141
148
|
/** Persisted on `boards.metadata.playback`: how a Board plays when opened. */
|
|
142
149
|
const BoardPlaybackPolicySchema = z.object({
|
|
143
150
|
compositionId: idSchema,
|
|
144
151
|
/** Delay before the first local playback after opening the Board, in milliseconds. */
|
|
145
|
-
delayMs: finiteSchema.nonnegative().default(0)
|
|
152
|
+
delayMs: finiteSchema$1.nonnegative().default(0)
|
|
146
153
|
});
|
|
147
154
|
function parseBoardPlaybackPolicy(metadata) {
|
|
148
155
|
const parsed = BoardPlaybackPolicySchema.safeParse(metadata.playback);
|
|
@@ -153,8 +160,8 @@ z.discriminatedUnion("type", [
|
|
|
153
160
|
commandId: idSchema,
|
|
154
161
|
type: z.literal("play"),
|
|
155
162
|
compositionId: idSchema,
|
|
156
|
-
position: finiteSchema.nonnegative().optional(),
|
|
157
|
-
timeScale: finiteSchema.positive().max(4).optional(),
|
|
163
|
+
position: finiteSchema$1.nonnegative().optional(),
|
|
164
|
+
timeScale: finiteSchema$1.positive().max(4).optional(),
|
|
158
165
|
shared: z.boolean().optional(),
|
|
159
166
|
seed: idSchema.optional()
|
|
160
167
|
}),
|
|
@@ -167,7 +174,7 @@ z.discriminatedUnion("type", [
|
|
|
167
174
|
commandId: idSchema,
|
|
168
175
|
type: z.literal("seek"),
|
|
169
176
|
playbackId: z.string().uuid(),
|
|
170
|
-
position: finiteSchema.nonnegative()
|
|
177
|
+
position: finiteSchema$1.nonnegative()
|
|
171
178
|
}),
|
|
172
179
|
z.object({
|
|
173
180
|
commandId: idSchema,
|
|
@@ -176,9 +183,49 @@ z.discriminatedUnion("type", [
|
|
|
176
183
|
})
|
|
177
184
|
]);
|
|
178
185
|
//#endregion
|
|
186
|
+
//#region ../protocol/dist/board-animation.js
|
|
187
|
+
const extensionKindSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
|
|
188
|
+
const jsonObjectSchema = z.record(z.string(), z.unknown());
|
|
189
|
+
const finiteSchema = z.number().finite();
|
|
190
|
+
/** Parameters shared by the built-in deal entrance preset. */
|
|
191
|
+
const BoardDealParamsSchema = z.object({
|
|
192
|
+
lift: finiteSchema.nonnegative().max(2e3).optional(),
|
|
193
|
+
swing: finiteSchema.nonnegative().max(2e3).optional(),
|
|
194
|
+
curve: finiteSchema.nonnegative().max(2e3).optional(),
|
|
195
|
+
tilt: finiteSchema.nonnegative().max(180).optional(),
|
|
196
|
+
scale: finiteSchema.positive().max(2).optional(),
|
|
197
|
+
duration: finiteSchema.positive().max(1e4).optional(),
|
|
198
|
+
landing: finiteSchema.nonnegative().max(1e4).optional()
|
|
199
|
+
}).strict();
|
|
200
|
+
/** A versioned, reusable animation preset reference. */
|
|
201
|
+
const BoardAnimationSpecSchema = z.object({
|
|
202
|
+
kind: extensionKindSchema,
|
|
203
|
+
kindVersion: z.number().int().positive().default(1),
|
|
204
|
+
params: jsonObjectSchema.default({})
|
|
205
|
+
}).strict().superRefine((spec, context) => {
|
|
206
|
+
if (spec.kind !== "effects.deal" || spec.kindVersion !== 1) return;
|
|
207
|
+
const parsed = BoardDealParamsSchema.safeParse(spec.params);
|
|
208
|
+
if (parsed.success) return;
|
|
209
|
+
context.addIssue({
|
|
210
|
+
code: "custom",
|
|
211
|
+
message: parsed.error.issues[0]?.message ?? "invalid effects.deal parameters",
|
|
212
|
+
path: ["params", ...parsed.error.issues[0]?.path ?? []]
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
/** Board-wide default motion policies. Omitted policies mean no motion. */
|
|
216
|
+
const BoardMotionSchema = z.object({ enter: BoardAnimationSpecSchema.optional() }).strict();
|
|
217
|
+
//#endregion
|
|
179
218
|
//#region ../protocol/dist/board-capability-registry.js
|
|
180
219
|
const CLIPS = new Set(BOARD_BUILTIN_CLIP_KINDS);
|
|
181
|
-
new Set(BOARD_BUILTIN_EFFECT_KINDS);
|
|
220
|
+
const EFFECTS = new Set(BOARD_BUILTIN_EFFECT_KINDS);
|
|
221
|
+
const BUILTIN_VERSIONS = new Set(BOARD_BUILTIN_CAPABILITIES.map((c) => `${c.kind}:${c.id}@${c.version}`));
|
|
222
|
+
/**
|
|
223
|
+
* True when a built-in renderer exists for exactly this kind@version. A known
|
|
224
|
+
* kind at an unknown version is not built-in: it must not pass as one.
|
|
225
|
+
*/
|
|
226
|
+
function isBuiltinBoardCapability(kind, id, version) {
|
|
227
|
+
return BUILTIN_VERSIONS.has(`${kind}:${id}@${version}`);
|
|
228
|
+
}
|
|
182
229
|
const record = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
183
230
|
const finite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
184
231
|
function validateBuiltinBoardClip(clip, path = "clip") {
|
|
@@ -212,6 +259,47 @@ function validateBuiltinBoardClip(clip, path = "clip") {
|
|
|
212
259
|
}
|
|
213
260
|
return errors;
|
|
214
261
|
}
|
|
262
|
+
function validateBuiltinBoardEffect(effect, path = "effect") {
|
|
263
|
+
const diagnostics = [];
|
|
264
|
+
if (effect.lifecycle === "on-enter") {
|
|
265
|
+
if (effect.target.type !== "item") diagnostics.push({
|
|
266
|
+
severity: "error",
|
|
267
|
+
code: "INVALID_BOARD_EFFECT",
|
|
268
|
+
message: "on-enter effects must target an item",
|
|
269
|
+
path: `${path}.target`
|
|
270
|
+
});
|
|
271
|
+
if (effect.timeOrigin !== "activation") diagnostics.push({
|
|
272
|
+
severity: "error",
|
|
273
|
+
code: "INVALID_BOARD_EFFECT",
|
|
274
|
+
message: "on-enter effects must use activation time",
|
|
275
|
+
path: `${path}.timeOrigin`
|
|
276
|
+
});
|
|
277
|
+
} else if (EFFECTS.has(effect.kind) && effect.target.type !== "item") diagnostics.push({
|
|
278
|
+
severity: "error",
|
|
279
|
+
code: "INVALID_BOARD_EFFECT",
|
|
280
|
+
message: `${effect.kind} must target an item`,
|
|
281
|
+
path: `${path}.target`
|
|
282
|
+
});
|
|
283
|
+
if (effect.kind === "effects.deal" && effect.kindVersion === 1) {
|
|
284
|
+
if (effect.lifecycle !== "on-enter") diagnostics.push({
|
|
285
|
+
severity: "error",
|
|
286
|
+
code: "INVALID_BOARD_EFFECT",
|
|
287
|
+
message: "effects.deal must use the on-enter lifecycle",
|
|
288
|
+
path: `${path}.lifecycle`
|
|
289
|
+
});
|
|
290
|
+
const parsed = BoardDealParamsSchema.safeParse(effect.params);
|
|
291
|
+
if (!parsed.success) diagnostics.push({
|
|
292
|
+
severity: "error",
|
|
293
|
+
code: "INVALID_BOARD_EFFECT",
|
|
294
|
+
message: parsed.error.issues[0]?.message ?? "invalid effects.deal parameters",
|
|
295
|
+
path: `${path}.params`
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return diagnostics;
|
|
299
|
+
}
|
|
300
|
+
function estimateBuiltinBoardEffectCost(effect) {
|
|
301
|
+
return isBuiltinBoardCapability("effect", effect.kind, effect.kindVersion) ? { drawCalls: 1 } : {};
|
|
302
|
+
}
|
|
215
303
|
function estimateBuiltinBoardClipCost(clip) {
|
|
216
304
|
switch (clip.kind) {
|
|
217
305
|
case "effects.particles": {
|
|
@@ -352,7 +440,18 @@ const BoardViewportSchema = z.object({
|
|
|
352
440
|
zoom: z.number().finite().min(.05).max(8)
|
|
353
441
|
});
|
|
354
442
|
const BoardAppearanceSchema = z.object({
|
|
355
|
-
|
|
443
|
+
/** Legacy field retained when reading older Board documents; not used for rendering. */
|
|
444
|
+
theme: z.string().min(1).optional(),
|
|
445
|
+
/** Board-wide motion policies. Omitted policies mean no motion. */
|
|
446
|
+
motion: BoardMotionSchema.optional(),
|
|
447
|
+
/** Legacy field retained when reading older Board documents; not used for rendering. */
|
|
448
|
+
mood: z.enum([
|
|
449
|
+
"clean",
|
|
450
|
+
"playful",
|
|
451
|
+
"arcane",
|
|
452
|
+
"cyber",
|
|
453
|
+
"natural"
|
|
454
|
+
]).optional(),
|
|
356
455
|
background: z.object({
|
|
357
456
|
kind: z.enum([
|
|
358
457
|
"solid",
|
|
@@ -386,14 +485,7 @@ const BoardAppearanceSchema = z.object({
|
|
|
386
485
|
visible: false,
|
|
387
486
|
size: 24,
|
|
388
487
|
opacity: .12
|
|
389
|
-
})
|
|
390
|
-
mood: z.enum([
|
|
391
|
-
"clean",
|
|
392
|
-
"playful",
|
|
393
|
-
"arcane",
|
|
394
|
-
"cyber",
|
|
395
|
-
"natural"
|
|
396
|
-
]).default("clean")
|
|
488
|
+
})
|
|
397
489
|
});
|
|
398
490
|
/** Optional visual chrome still carried by a few older shapes. */
|
|
399
491
|
const BoardItemStyleSchema = z.object({
|
|
@@ -695,14 +787,12 @@ z.object({
|
|
|
695
787
|
kind: z.literal(BOARD_DOCUMENT_KIND),
|
|
696
788
|
version: z.literal(1),
|
|
697
789
|
appearance: BoardAppearanceSchema.default({
|
|
698
|
-
theme: "clean",
|
|
699
790
|
background: { kind: "solid" },
|
|
700
791
|
grid: {
|
|
701
792
|
visible: false,
|
|
702
793
|
size: 24,
|
|
703
794
|
opacity: .12
|
|
704
|
-
}
|
|
705
|
-
mood: "clean"
|
|
795
|
+
}
|
|
706
796
|
}),
|
|
707
797
|
viewport: BoardViewportSchema,
|
|
708
798
|
items: z.array(BoardItemSchema),
|
|
@@ -891,14 +981,14 @@ var GenerationPolicyError = class extends Error {
|
|
|
891
981
|
this.name = "GenerationPolicyError";
|
|
892
982
|
}
|
|
893
983
|
};
|
|
894
|
-
function isRecord$
|
|
984
|
+
function isRecord$4(value) {
|
|
895
985
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
896
986
|
}
|
|
897
987
|
function isPrimitiveEnumValue(value) {
|
|
898
988
|
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
899
989
|
}
|
|
900
990
|
function normalizeConstraint(value) {
|
|
901
|
-
if (!isRecord$
|
|
991
|
+
if (!isRecord$4(value) || typeof value.kind !== "string") return null;
|
|
902
992
|
if (value.kind === "enum") {
|
|
903
993
|
if (!Array.isArray(value.values) || value.values.length === 0 || !value.values.every(isPrimitiveEnumValue)) return null;
|
|
904
994
|
return {
|
|
@@ -928,7 +1018,7 @@ function normalizeConstraint(value) {
|
|
|
928
1018
|
return null;
|
|
929
1019
|
}
|
|
930
1020
|
function normalizeGenerationPolicy(value) {
|
|
931
|
-
if (!isRecord$
|
|
1021
|
+
if (!isRecord$4(value) || value.version !== 1) return null;
|
|
932
1022
|
if (value.mode === "auto") return {
|
|
933
1023
|
version: 1,
|
|
934
1024
|
mode: "auto"
|
|
@@ -936,10 +1026,10 @@ function normalizeGenerationPolicy(value) {
|
|
|
936
1026
|
if (value.mode !== "limited" || !Array.isArray(value.models) || value.models.length === 0) return null;
|
|
937
1027
|
const models = [];
|
|
938
1028
|
for (const item of value.models) {
|
|
939
|
-
if (!isRecord$
|
|
1029
|
+
if (!isRecord$4(item) || typeof item.model !== "string" || !item.model.trim()) return null;
|
|
940
1030
|
const modelPolicy = { model: item.model.trim() };
|
|
941
1031
|
if (item.parameters !== void 0) {
|
|
942
|
-
if (!isRecord$
|
|
1032
|
+
if (!isRecord$4(item.parameters)) return null;
|
|
943
1033
|
const parameters = {};
|
|
944
1034
|
for (const [key, rawConstraint] of Object.entries(item.parameters)) {
|
|
945
1035
|
if (!key.trim()) return null;
|
|
@@ -1172,8 +1262,8 @@ const APP_SURFACE_REQUEST_TIMEOUT_MS = 15e3;
|
|
|
1172
1262
|
const APP_COMPOSER_CHIP_KEY_MAX_LENGTH = 80;
|
|
1173
1263
|
const APP_COMPOSER_CHIP_LABEL_MAX_LENGTH = 120;
|
|
1174
1264
|
const APP_COMPOSER_CHIP_CONTENT_MAX_BYTES = 32768;
|
|
1175
|
-
const isRecord$
|
|
1176
|
-
const isSurfaceEnvelope = (value) => isRecord$
|
|
1265
|
+
const isRecord$3 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1266
|
+
const isSurfaceEnvelope = (value) => isRecord$3(value) && value.protocol === "cohub.app.surface" && value.version === 1;
|
|
1177
1267
|
const parseAppSurfaceReady = (value) => {
|
|
1178
1268
|
if (!isSurfaceEnvelope(value) || value.type !== "ready") return null;
|
|
1179
1269
|
const methods = Array.isArray(value.methods) ? value.methods.filter((method) => typeof method === "string" && Boolean(method)) : [];
|
|
@@ -1187,7 +1277,7 @@ const parseAppSurfaceReady = (value) => {
|
|
|
1187
1277
|
const parseAppSurfaceResponse = (value) => {
|
|
1188
1278
|
if (!isSurfaceEnvelope(value) || value.type !== "response") return null;
|
|
1189
1279
|
if (typeof value.requestId !== "string" || !value.requestId) return null;
|
|
1190
|
-
const error = isRecord$
|
|
1280
|
+
const error = isRecord$3(value.error) ? {
|
|
1191
1281
|
code: typeof value.error.code === "string" && value.error.code ? value.error.code : "surface_error",
|
|
1192
1282
|
message: typeof value.error.message === "string" ? value.error.message : "App surface call failed"
|
|
1193
1283
|
} : void 0;
|
|
@@ -1207,7 +1297,7 @@ const parseComposerChipKey = (value) => {
|
|
|
1207
1297
|
return key;
|
|
1208
1298
|
};
|
|
1209
1299
|
const parseAppComposerChipSet = (value) => {
|
|
1210
|
-
if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.set" || !isRecord$
|
|
1300
|
+
if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.set" || !isRecord$3(value.chip)) return null;
|
|
1211
1301
|
const key = parseComposerChipKey(value.chip.key);
|
|
1212
1302
|
if (!key || typeof value.chip.label !== "string" || typeof value.chip.content !== "string") return null;
|
|
1213
1303
|
const label = value.chip.label.trim();
|
|
@@ -1289,12 +1379,23 @@ const buildAppRuntimeReady = () => ({
|
|
|
1289
1379
|
version: 1,
|
|
1290
1380
|
type: "ready"
|
|
1291
1381
|
});
|
|
1382
|
+
const buildAppRuntimeCloseRequest = () => ({
|
|
1383
|
+
protocol: APP_RUNTIME_PROTOCOL,
|
|
1384
|
+
version: 1,
|
|
1385
|
+
type: "close.request"
|
|
1386
|
+
});
|
|
1387
|
+
const buildAppRuntimeConfigureRequest = (input) => ({
|
|
1388
|
+
protocol: APP_RUNTIME_PROTOCOL,
|
|
1389
|
+
version: 1,
|
|
1390
|
+
type: "configure.request",
|
|
1391
|
+
...input
|
|
1392
|
+
});
|
|
1292
1393
|
//#endregion
|
|
1293
1394
|
//#region ../protocol/dist/app-navigation.js
|
|
1294
1395
|
const APP_NAVIGATION_PROTOCOL = "cohub.app.navigation";
|
|
1295
1396
|
const APP_NAVIGATION_MAX_ERROR_CODE_LENGTH = 64;
|
|
1296
1397
|
const APP_NAVIGATION_MAX_ERROR_MESSAGE_LENGTH = NAVIGATION_ERROR_MESSAGE_MAX_LENGTH;
|
|
1297
|
-
const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1398
|
+
const isRecord$2 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1298
1399
|
const text = (value, max) => typeof value === "string" && value.trim() && value.length <= max ? value : null;
|
|
1299
1400
|
const buildAppNavigationOpenMessage = (input) => ({
|
|
1300
1401
|
protocol: APP_NAVIGATION_PROTOCOL,
|
|
@@ -1310,7 +1411,7 @@ const NAVIGATION_REASONS = /* @__PURE__ */ new Set([
|
|
|
1310
1411
|
]);
|
|
1311
1412
|
function parseNavigationCall(value) {
|
|
1312
1413
|
if (value === void 0) return void 0;
|
|
1313
|
-
if (!isRecord(value) || typeof value.ok !== "boolean") return null;
|
|
1414
|
+
if (!isRecord$2(value) || typeof value.ok !== "boolean") return null;
|
|
1314
1415
|
if (value.ok) return {
|
|
1315
1416
|
ok: true,
|
|
1316
1417
|
...value.result === void 0 ? {} : { result: value.result }
|
|
@@ -1325,7 +1426,7 @@ function parseNavigationCall(value) {
|
|
|
1325
1426
|
};
|
|
1326
1427
|
}
|
|
1327
1428
|
const parseAppNavigationOpenResponse = (value) => {
|
|
1328
|
-
if (!isRecord(value)) return null;
|
|
1429
|
+
if (!isRecord$2(value)) return null;
|
|
1329
1430
|
if (value.protocol !== "cohub.app.navigation" || value.version !== 1 || value.type !== "open.result" || !text(value.requestId, 128) || typeof value.handled !== "boolean" || value.reason !== void 0 && !NAVIGATION_REASONS.has(value.reason)) return null;
|
|
1330
1431
|
const call = parseNavigationCall(value.call);
|
|
1331
1432
|
if (call === null) return null;
|
|
@@ -1339,6 +1440,39 @@ const parseAppNavigationOpenResponse = (value) => {
|
|
|
1339
1440
|
...call === void 0 ? {} : { call }
|
|
1340
1441
|
};
|
|
1341
1442
|
};
|
|
1443
|
+
const envelope = {
|
|
1444
|
+
protocol: "cohub.app.embed",
|
|
1445
|
+
version: 1
|
|
1446
|
+
};
|
|
1447
|
+
const isRecord$1 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1448
|
+
const isEnvelope = (value) => isRecord$1(value) && value.protocol === "cohub.app.embed" && value.version === 1;
|
|
1449
|
+
const parseEmbedId = (value) => typeof value === "string" && value.trim() && value.length <= 128 ? value : null;
|
|
1450
|
+
function parseAppEmbedAttachRequest(value) {
|
|
1451
|
+
if (!isEnvelope(value) || value.type !== "attach.request") return null;
|
|
1452
|
+
return {
|
|
1453
|
+
...envelope,
|
|
1454
|
+
type: "attach.request"
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
function parseAppEmbedCloseRequest(value) {
|
|
1458
|
+
if (!isEnvelope(value) || value.type !== "close.request") return null;
|
|
1459
|
+
const embedId = parseEmbedId(value.embedId);
|
|
1460
|
+
return embedId ? {
|
|
1461
|
+
...envelope,
|
|
1462
|
+
type: "close.request",
|
|
1463
|
+
embedId
|
|
1464
|
+
} : null;
|
|
1465
|
+
}
|
|
1466
|
+
const buildAppEmbedAttach = (input) => ({
|
|
1467
|
+
...envelope,
|
|
1468
|
+
type: "attach",
|
|
1469
|
+
...input
|
|
1470
|
+
});
|
|
1471
|
+
const buildAppEmbedShellChanged = (input) => ({
|
|
1472
|
+
...envelope,
|
|
1473
|
+
type: "shell.changed",
|
|
1474
|
+
...input
|
|
1475
|
+
});
|
|
1342
1476
|
//#endregion
|
|
1343
1477
|
//#region src/apis/billing.ts
|
|
1344
1478
|
var BillingApi = class {
|
|
@@ -1988,6 +2122,61 @@ var AppSurfaceApi = class {
|
|
|
1988
2122
|
/** @deprecated Use `AppSurfaceApi`. */
|
|
1989
2123
|
var WorkSurfaceApi = class extends AppSurfaceApi {};
|
|
1990
2124
|
//#endregion
|
|
2125
|
+
//#region src/app-embed.ts
|
|
2126
|
+
const generateEmbedId = () => globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
2127
|
+
const frameOrigin = (frame) => {
|
|
2128
|
+
try {
|
|
2129
|
+
return new URL(frame.src, window.location.href).origin;
|
|
2130
|
+
} catch {
|
|
2131
|
+
return null;
|
|
2132
|
+
}
|
|
2133
|
+
};
|
|
2134
|
+
/**
|
|
2135
|
+
* Attaches to an iframe that renders a Cohub public App page. The page keeps
|
|
2136
|
+
* owning the embedded App's runtime; this only forwards navigation hints and
|
|
2137
|
+
* relays the App's close intent back to the embedder. Either side may come up
|
|
2138
|
+
* first: the page asks to be attached, and the embedder also announces itself
|
|
2139
|
+
* on attach and on every frame load.
|
|
2140
|
+
*/
|
|
2141
|
+
function attachAppEmbed(frame, options) {
|
|
2142
|
+
const embedId = generateEmbedId();
|
|
2143
|
+
let shell = options.shell ?? null;
|
|
2144
|
+
const post = (message) => {
|
|
2145
|
+
const origin = frameOrigin(frame);
|
|
2146
|
+
if (!origin) return;
|
|
2147
|
+
try {
|
|
2148
|
+
frame.contentWindow?.postMessage(message, origin);
|
|
2149
|
+
} catch {}
|
|
2150
|
+
};
|
|
2151
|
+
const attach = () => post(buildAppEmbedAttach({
|
|
2152
|
+
embedId,
|
|
2153
|
+
embedder: { appId: options.appId },
|
|
2154
|
+
shell
|
|
2155
|
+
}));
|
|
2156
|
+
const onMessage = (event) => {
|
|
2157
|
+
if (event.source !== frame.contentWindow || event.origin !== frameOrigin(frame)) return;
|
|
2158
|
+
if (parseAppEmbedAttachRequest(event.data)) return attach();
|
|
2159
|
+
if (parseAppEmbedCloseRequest(event.data)?.embedId === embedId) options.onCloseRequest?.();
|
|
2160
|
+
};
|
|
2161
|
+
window.addEventListener("message", onMessage);
|
|
2162
|
+
frame.addEventListener("load", attach);
|
|
2163
|
+
attach();
|
|
2164
|
+
return {
|
|
2165
|
+
embedId,
|
|
2166
|
+
setShell(next) {
|
|
2167
|
+
shell = next;
|
|
2168
|
+
post(buildAppEmbedShellChanged({
|
|
2169
|
+
embedId,
|
|
2170
|
+
shell
|
|
2171
|
+
}));
|
|
2172
|
+
},
|
|
2173
|
+
dispose() {
|
|
2174
|
+
window.removeEventListener("message", onMessage);
|
|
2175
|
+
frame.removeEventListener("load", attach);
|
|
2176
|
+
}
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2179
|
+
//#endregion
|
|
1991
2180
|
//#region src/app-runtime.ts
|
|
1992
2181
|
const isBrowser$1 = () => typeof window !== "undefined" && typeof window.parent !== "undefined";
|
|
1993
2182
|
const hasParent = () => isBrowser$1() && window.parent !== window;
|
|
@@ -2038,6 +2227,12 @@ var ParentBridgeTransport = class {
|
|
|
2038
2227
|
}
|
|
2039
2228
|
};
|
|
2040
2229
|
}
|
|
2230
|
+
notify(message) {
|
|
2231
|
+
if (!hasParent()) return;
|
|
2232
|
+
try {
|
|
2233
|
+
window.parent.postMessage(message, this.trustedParentOrigin ?? getParentOrigin() ?? "*");
|
|
2234
|
+
} catch {}
|
|
2235
|
+
}
|
|
2041
2236
|
request(message, options) {
|
|
2042
2237
|
const timeoutMs = options?.timeoutMs ?? 1200;
|
|
2043
2238
|
const retryIntervalMs = options?.retryIntervalMs;
|
|
@@ -2345,6 +2540,22 @@ var AppRuntimeApi = class {
|
|
|
2345
2540
|
reason: response === null ? "timeout" : "unsupported"
|
|
2346
2541
|
};
|
|
2347
2542
|
}
|
|
2543
|
+
/**
|
|
2544
|
+
* Asks the host to close this App: a workspace tab closes, an embedded page
|
|
2545
|
+
* forwards the request to its embedder, a standalone page closes the tab.
|
|
2546
|
+
* No-op in broker mode, where the App owns its own window.
|
|
2547
|
+
*/
|
|
2548
|
+
requestClose() {
|
|
2549
|
+
this.transport.notify?.(buildAppRuntimeCloseRequest());
|
|
2550
|
+
}
|
|
2551
|
+
/**
|
|
2552
|
+
* Requests the host to update the overlay's geometry or pointer hit regions.
|
|
2553
|
+
* Only meaningful when the App was opened as an `overlay` surface; the host
|
|
2554
|
+
* is free to clamp or ignore values that violate its layout policy.
|
|
2555
|
+
*/
|
|
2556
|
+
requestConfigure(input) {
|
|
2557
|
+
this.transport.notify?.(buildAppRuntimeConfigureRequest(input));
|
|
2558
|
+
}
|
|
2348
2559
|
async getAccessToken(options) {
|
|
2349
2560
|
await this.ensureStorageKeys();
|
|
2350
2561
|
if (this.token && !options?.forceRefresh) return this.token;
|
|
@@ -2442,6 +2653,41 @@ var AppRuntimeApi = class {
|
|
|
2442
2653
|
} : null
|
|
2443
2654
|
};
|
|
2444
2655
|
}
|
|
2656
|
+
/**
|
|
2657
|
+
* One consent: create a viewer-owned Space (full `CreateSpaceInput`, same
|
|
2658
|
+
* as `spaces.create`) and grant the scopes on it. Never silent — each
|
|
2659
|
+
* confirm mints a new Space. The host creates with the viewer's account
|
|
2660
|
+
* token.
|
|
2661
|
+
* `{ granted: false, space }` means the Space was created but not provisioned;
|
|
2662
|
+
* no grant was issued. A viewer deny is `{ granted: false, space: null }`.
|
|
2663
|
+
*/
|
|
2664
|
+
async requestCreateSpaceAuthorization(input) {
|
|
2665
|
+
await this.ensureStorageKeys();
|
|
2666
|
+
const response = await this.transport.request({
|
|
2667
|
+
type: "cohub.app.authorize",
|
|
2668
|
+
scopes: input.scopes,
|
|
2669
|
+
reason: input.reason,
|
|
2670
|
+
createSpace: input.space
|
|
2671
|
+
}, { timeoutMs: 12e4 });
|
|
2672
|
+
const token = response?.token ?? null;
|
|
2673
|
+
const spaceId = typeof response?.space?.id === "string" ? response.space.id : null;
|
|
2674
|
+
const spaceName = typeof response?.space?.name === "string" ? response.space.name : null;
|
|
2675
|
+
if (token) {
|
|
2676
|
+
this.token = token;
|
|
2677
|
+
this.writeStoredToken(token);
|
|
2678
|
+
}
|
|
2679
|
+
if (token && spaceId) {
|
|
2680
|
+
this.authorizedGrants = recordConsent(this.authorizedGrants, spaceId, input.scopes);
|
|
2681
|
+
this.writeStoredGrants(this.authorizedGrants);
|
|
2682
|
+
}
|
|
2683
|
+
return {
|
|
2684
|
+
granted: Boolean(token),
|
|
2685
|
+
space: spaceId ? {
|
|
2686
|
+
id: spaceId,
|
|
2687
|
+
name: spaceName
|
|
2688
|
+
} : null
|
|
2689
|
+
};
|
|
2690
|
+
}
|
|
2445
2691
|
async purchase(input) {
|
|
2446
2692
|
const purchaseAttemptId = input.purchaseAttemptId?.trim() || generateRequestId();
|
|
2447
2693
|
return (await this.transport.request({
|
|
@@ -2613,13 +2859,25 @@ var CohubClient = class {
|
|
|
2613
2859
|
/** Ensure the app holds these scopes. Silent when a grant already covers them; `alwaysAsk` forces the dialog. */
|
|
2614
2860
|
request: (input) => this.appRuntime.requestAuthorization(input),
|
|
2615
2861
|
/** One consent: the viewer picks a Space and grants the scopes on it. `alwaysAsk` re-opens the picker. */
|
|
2616
|
-
requestSpace: (input) => this.appRuntime.requestSpaceAuthorization(input)
|
|
2862
|
+
requestSpace: (input) => this.appRuntime.requestSpaceAuthorization(input),
|
|
2863
|
+
/** One consent: create a viewer-owned Space and grant the scopes on it. `space` is `CreateSpaceInput`. */
|
|
2864
|
+
requestCreateSpace: (input) => this.appRuntime.requestCreateSpaceAuthorization(input)
|
|
2617
2865
|
};
|
|
2618
2866
|
app = {
|
|
2619
2867
|
realtime: null,
|
|
2620
2868
|
/** Expose callable methods from inside a published app. */
|
|
2621
2869
|
surface: new AppSurfaceApi(),
|
|
2622
2870
|
onContextChanged: (listener) => this.appRuntime.onContextChanged(listener),
|
|
2871
|
+
/** Ask the host to close this App's surface. */
|
|
2872
|
+
requestClose: () => this.appRuntime.requestClose(),
|
|
2873
|
+
/**
|
|
2874
|
+
* Request that the host update this overlay's geometry or pointer hit
|
|
2875
|
+
* regions. Only meaningful for `overlay` surfaces; ignored otherwise.
|
|
2876
|
+
*/
|
|
2877
|
+
requestConfigure: (input) => this.appRuntime.requestConfigure(input),
|
|
2878
|
+
embed: {
|
|
2879
|
+
/** Host another App's public page in an iframe and forward the shell location to it. */
|
|
2880
|
+
attach: attachAppEmbed },
|
|
2623
2881
|
composer: {
|
|
2624
2882
|
/** Attach or update context from this app in the Cohub composer. */
|
|
2625
2883
|
setChip: (chip) => this.app.surface.setComposerChip(chip),
|
|
@@ -2987,6 +3245,88 @@ function sanitizeReason(value) {
|
|
|
2987
3245
|
if (!trimmed) return void 0;
|
|
2988
3246
|
return trimmed.length > MAX_REASON_LENGTH ? trimmed.slice(0, MAX_REASON_LENGTH) : trimmed;
|
|
2989
3247
|
}
|
|
3248
|
+
function isRecord(value) {
|
|
3249
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3250
|
+
}
|
|
3251
|
+
function sanitizeBootstrapSource(value) {
|
|
3252
|
+
if (!isRecord(value) || typeof value.type !== "string") return null;
|
|
3253
|
+
if (value.type === "blank") return { type: "blank" };
|
|
3254
|
+
if (value.type === "checkpoint") {
|
|
3255
|
+
const checkpointId = typeof value.checkpointId === "string" ? value.checkpointId.trim() : "";
|
|
3256
|
+
return checkpointId ? {
|
|
3257
|
+
type: "checkpoint",
|
|
3258
|
+
checkpointId
|
|
3259
|
+
} : null;
|
|
3260
|
+
}
|
|
3261
|
+
if (value.type === "git_repo") {
|
|
3262
|
+
const repoUrl = typeof value.repoUrl === "string" ? value.repoUrl.trim() : "";
|
|
3263
|
+
if (!repoUrl) return null;
|
|
3264
|
+
const ref = typeof value.ref === "string" ? value.ref.trim() || null : value.ref === null ? null : void 0;
|
|
3265
|
+
return ref === void 0 ? {
|
|
3266
|
+
type: "git_repo",
|
|
3267
|
+
repoUrl
|
|
3268
|
+
} : {
|
|
3269
|
+
type: "git_repo",
|
|
3270
|
+
repoUrl,
|
|
3271
|
+
ref
|
|
3272
|
+
};
|
|
3273
|
+
}
|
|
3274
|
+
return null;
|
|
3275
|
+
}
|
|
3276
|
+
/**
|
|
3277
|
+
* Untrusted postMessage payload → `CreateSpaceInput`. Unknown keys are
|
|
3278
|
+
* dropped; the create API still validates the rest.
|
|
3279
|
+
*/
|
|
3280
|
+
function sanitizeCreateSpaceInput(value) {
|
|
3281
|
+
if (!isRecord(value)) return null;
|
|
3282
|
+
const name = typeof value.name === "string" ? value.name.trim() : "";
|
|
3283
|
+
if (!name) return null;
|
|
3284
|
+
const input = { name };
|
|
3285
|
+
if (value.slug !== void 0) {
|
|
3286
|
+
if (value.slug !== null && typeof value.slug !== "string") return null;
|
|
3287
|
+
input.slug = value.slug;
|
|
3288
|
+
}
|
|
3289
|
+
if (value.description !== void 0) {
|
|
3290
|
+
if (value.description !== null && typeof value.description !== "string") return null;
|
|
3291
|
+
input.description = value.description;
|
|
3292
|
+
}
|
|
3293
|
+
if (value.source !== void 0) {
|
|
3294
|
+
if (typeof value.source !== "string") return null;
|
|
3295
|
+
input.source = value.source;
|
|
3296
|
+
}
|
|
3297
|
+
if (value.bootstrapSource !== void 0) {
|
|
3298
|
+
const bootstrap = sanitizeBootstrapSource(value.bootstrapSource);
|
|
3299
|
+
if (!bootstrap) return null;
|
|
3300
|
+
input.bootstrapSource = bootstrap;
|
|
3301
|
+
}
|
|
3302
|
+
if (value.extraEnv !== void 0) {
|
|
3303
|
+
if (!Array.isArray(value.extraEnv)) return null;
|
|
3304
|
+
input.extraEnv = value.extraEnv;
|
|
3305
|
+
}
|
|
3306
|
+
if (value.channelBindings !== void 0) {
|
|
3307
|
+
if (!Array.isArray(value.channelBindings)) return null;
|
|
3308
|
+
input.channelBindings = value.channelBindings;
|
|
3309
|
+
}
|
|
3310
|
+
if (value.mods !== void 0) {
|
|
3311
|
+
if (!Array.isArray(value.mods)) return null;
|
|
3312
|
+
input.mods = value.mods;
|
|
3313
|
+
}
|
|
3314
|
+
if (value.config !== void 0) {
|
|
3315
|
+
if (!isRecord(value.config)) return null;
|
|
3316
|
+
input.config = value.config;
|
|
3317
|
+
}
|
|
3318
|
+
return input;
|
|
3319
|
+
}
|
|
3320
|
+
function readCreatedSpace(payload) {
|
|
3321
|
+
if (!isRecord(payload) || !isRecord(payload.space)) return null;
|
|
3322
|
+
const id = payload.space.id;
|
|
3323
|
+
if (typeof id !== "string" || !id) return null;
|
|
3324
|
+
const name = payload.space.name;
|
|
3325
|
+
return {
|
|
3326
|
+
id,
|
|
3327
|
+
name: typeof name === "string" && name ? name : null
|
|
3328
|
+
};
|
|
3329
|
+
}
|
|
2990
3330
|
function normalizePermissionScopes(scopes) {
|
|
2991
3331
|
return Array.from(new Set(clonePermissionScopes(scopes)));
|
|
2992
3332
|
}
|
|
@@ -3108,6 +3448,8 @@ function createAppBridgeCore(config) {
|
|
|
3108
3448
|
const pendingPurchaseStorageKey = `cohub-app-purchase:${app.id}`;
|
|
3109
3449
|
const purchaseInFlight = /* @__PURE__ */ new Map();
|
|
3110
3450
|
let activePurchase = null;
|
|
3451
|
+
/** Space minted during the current create-space consent; retries skip create. */
|
|
3452
|
+
let mintedSpace = null;
|
|
3111
3453
|
async function isCurrentViewerAppOwner() {
|
|
3112
3454
|
const viewerUuid = await getViewerUuid();
|
|
3113
3455
|
return Boolean(viewerUuid && viewerUuid === app.userUuid);
|
|
@@ -3119,7 +3461,7 @@ function createAppBridgeCore(config) {
|
|
|
3119
3461
|
return Array.from(bySpace.values());
|
|
3120
3462
|
}
|
|
3121
3463
|
function allowsOwnerAutoAuthorization() {
|
|
3122
|
-
return authorizationContext.surface === "background" || authorizationContext.surface === "app";
|
|
3464
|
+
return authorizationContext.surface === "background" || authorizationContext.surface === "app" || authorizationContext.surface === "overlay";
|
|
3123
3465
|
}
|
|
3124
3466
|
async function ensureBaseToken(forceRefresh = false) {
|
|
3125
3467
|
if (appToken && !forceRefresh) return appToken;
|
|
@@ -3225,6 +3567,45 @@ function createAppBridgeCore(config) {
|
|
|
3225
3567
|
} catch {}
|
|
3226
3568
|
}
|
|
3227
3569
|
/**
|
|
3570
|
+
* Creates the Space with the viewer's own token — the same `POST /api/spaces`
|
|
3571
|
+
* path as the web New Space flow and the CLI. The app never holds this token.
|
|
3572
|
+
*/
|
|
3573
|
+
async function createViewerSpace(input) {
|
|
3574
|
+
const request = async (forceRefresh = false) => {
|
|
3575
|
+
const userToken = await getAccessToken({ forceRefresh });
|
|
3576
|
+
if (!userToken) {
|
|
3577
|
+
await config.requestSignIn(typeof location !== "undefined" ? location.pathname : "/");
|
|
3578
|
+
throw new Error("Sign in is required to create a Space.");
|
|
3579
|
+
}
|
|
3580
|
+
return fetch(`${apiOrigin}/api/spaces`, {
|
|
3581
|
+
method: "POST",
|
|
3582
|
+
headers: {
|
|
3583
|
+
Authorization: `Bearer ${userToken}`,
|
|
3584
|
+
"Content-Type": "application/json"
|
|
3585
|
+
},
|
|
3586
|
+
body: JSON.stringify(input)
|
|
3587
|
+
});
|
|
3588
|
+
};
|
|
3589
|
+
let response = await request();
|
|
3590
|
+
if (response.status === 401) response = await request(true);
|
|
3591
|
+
const payload = await response.json().catch(() => null);
|
|
3592
|
+
const space = readCreatedSpace(payload);
|
|
3593
|
+
if (response.ok) {
|
|
3594
|
+
if (!space) throw new Error("Invalid space create response.");
|
|
3595
|
+
return {
|
|
3596
|
+
...space,
|
|
3597
|
+
provisioned: true
|
|
3598
|
+
};
|
|
3599
|
+
}
|
|
3600
|
+
const message = isRecord(payload) && typeof payload.message === "string" ? payload.message : "Failed to create Space.";
|
|
3601
|
+
if (space) return {
|
|
3602
|
+
...space,
|
|
3603
|
+
provisioned: false,
|
|
3604
|
+
error: message
|
|
3605
|
+
};
|
|
3606
|
+
throw new Error(message);
|
|
3607
|
+
}
|
|
3608
|
+
/**
|
|
3228
3609
|
* Resolves the target space's name for the consent dialog. The host — not
|
|
3229
3610
|
* the app — resolves it, so the dialog cannot be tricked into labeling a
|
|
3230
3611
|
* grant with the wrong space.
|
|
@@ -3380,6 +3761,7 @@ function createAppBridgeCore(config) {
|
|
|
3380
3761
|
const scopes = sanitizeRequestedScopes(data.scopes);
|
|
3381
3762
|
const spaceId = typeof data.spaceId === "string" && data.spaceId ? data.spaceId : void 0;
|
|
3382
3763
|
const selectSpace = data.selectSpace === true && !spaceId;
|
|
3764
|
+
const createSpace = data.createSpace === void 0 ? void 0 : sanitizeCreateSpaceInput(data.createSpace);
|
|
3383
3765
|
const alwaysAsk = data.alwaysAsk === true;
|
|
3384
3766
|
if (scopes.length === 0) {
|
|
3385
3767
|
replyForRequest(data.requestId, {
|
|
@@ -3388,6 +3770,43 @@ function createAppBridgeCore(config) {
|
|
|
3388
3770
|
}, true);
|
|
3389
3771
|
return;
|
|
3390
3772
|
}
|
|
3773
|
+
if (data.createSpace !== void 0 && !createSpace) {
|
|
3774
|
+
const named = isRecord(data.createSpace) && typeof data.createSpace.name === "string" && Boolean(data.createSpace.name.trim());
|
|
3775
|
+
replyForRequest(data.requestId, {
|
|
3776
|
+
type: "cohub.app.error",
|
|
3777
|
+
message: named ? "Invalid space create input." : "Space name is required."
|
|
3778
|
+
}, true);
|
|
3779
|
+
return;
|
|
3780
|
+
}
|
|
3781
|
+
if (createSpace && (selectSpace || spaceId)) {
|
|
3782
|
+
replyForRequest(data.requestId, {
|
|
3783
|
+
type: "cohub.app.error",
|
|
3784
|
+
message: "createSpace cannot be combined with spaceId or selectSpace."
|
|
3785
|
+
}, true);
|
|
3786
|
+
return;
|
|
3787
|
+
}
|
|
3788
|
+
if (state.authSaving) {
|
|
3789
|
+
replyForRequest(data.requestId, {
|
|
3790
|
+
type: "cohub.app.error",
|
|
3791
|
+
message: "Another authorization is already in progress."
|
|
3792
|
+
}, true);
|
|
3793
|
+
return;
|
|
3794
|
+
}
|
|
3795
|
+
if (state.pendingAuth) dismissPendingAuth();
|
|
3796
|
+
if (createSpace) {
|
|
3797
|
+
mintedSpace = null;
|
|
3798
|
+
state.pendingAuth = {
|
|
3799
|
+
requestId: data.requestId,
|
|
3800
|
+
scopes,
|
|
3801
|
+
reason: sanitizeReason(data.reason),
|
|
3802
|
+
homeSpaceName: app.spaceName ?? null,
|
|
3803
|
+
createSpace
|
|
3804
|
+
};
|
|
3805
|
+
state.authError = null;
|
|
3806
|
+
state.authOpen = true;
|
|
3807
|
+
notify();
|
|
3808
|
+
return;
|
|
3809
|
+
}
|
|
3391
3810
|
if (!selectSpace && !alwaysAsk && allowsOwnerAutoAuthorization() && await isCurrentViewerAppOwner()) try {
|
|
3392
3811
|
const result = await authorize(scopes, spaceId, { silent: true });
|
|
3393
3812
|
replyForRequest(data.requestId, authorizeResult(result.token, result.spaceId, result.spaceId === app.spaceId ? app.spaceName ?? null : null), true);
|
|
@@ -3443,16 +3862,22 @@ function createAppBridgeCore(config) {
|
|
|
3443
3862
|
}, true);
|
|
3444
3863
|
}
|
|
3445
3864
|
}
|
|
3446
|
-
function
|
|
3447
|
-
if (state.authSaving) return;
|
|
3865
|
+
function dismissPendingAuth() {
|
|
3448
3866
|
if (!state.pendingAuth) return;
|
|
3449
3867
|
replyForRequest(state.pendingAuth.requestId, {
|
|
3450
3868
|
type: "cohub.app.authorize.result",
|
|
3451
3869
|
token: null
|
|
3452
3870
|
}, true);
|
|
3453
|
-
state.authOpen = false;
|
|
3454
3871
|
state.pendingAuth = null;
|
|
3872
|
+
state.authOpen = false;
|
|
3455
3873
|
state.authError = null;
|
|
3874
|
+
mintedSpace = null;
|
|
3875
|
+
notify();
|
|
3876
|
+
}
|
|
3877
|
+
function cancelAuth() {
|
|
3878
|
+
if (state.authSaving) return;
|
|
3879
|
+
if (!state.pendingAuth) return;
|
|
3880
|
+
dismissPendingAuth();
|
|
3456
3881
|
state.authSaving = false;
|
|
3457
3882
|
notify();
|
|
3458
3883
|
}
|
|
@@ -3501,14 +3926,27 @@ function createAppBridgeCore(config) {
|
|
|
3501
3926
|
state.authSaving = true;
|
|
3502
3927
|
notify();
|
|
3503
3928
|
try {
|
|
3504
|
-
|
|
3929
|
+
let requestedSpaceId = pending.selectSpace ? pickedSpaceId : pending.spaceId;
|
|
3930
|
+
let spaceName = pending.selectSpace ? pending.spaces?.find((space) => space.id === requestedSpaceId)?.name ?? null : pending.spaceName ?? app.spaceName ?? null;
|
|
3931
|
+
if (pending.createSpace) {
|
|
3932
|
+
mintedSpace ??= await createViewerSpace(pending.createSpace);
|
|
3933
|
+
if (!mintedSpace.provisioned) {
|
|
3934
|
+
replyForRequest(pending.requestId, authorizeResult(null, mintedSpace.id, mintedSpace.name ?? pending.createSpace.name), true);
|
|
3935
|
+
state.authOpen = false;
|
|
3936
|
+
state.pendingAuth = null;
|
|
3937
|
+
mintedSpace = null;
|
|
3938
|
+
return;
|
|
3939
|
+
}
|
|
3940
|
+
requestedSpaceId = mintedSpace.id;
|
|
3941
|
+
spaceName = mintedSpace.name ?? pending.createSpace.name;
|
|
3942
|
+
}
|
|
3505
3943
|
const result = await authorize(pending.scopes, requestedSpaceId);
|
|
3506
3944
|
setGrantedAppScopes(await getViewerUuid(), app.id, result.scopes, result.spaceId);
|
|
3507
|
-
if (pending.selectSpace) writeLastPickedSpace(result.spaceId);
|
|
3508
|
-
const spaceName = pending.selectSpace ? pending.spaces?.find((space) => space.id === result.spaceId)?.name ?? null : pending.spaceName ?? app.spaceName ?? null;
|
|
3945
|
+
if (pending.selectSpace || pending.createSpace) writeLastPickedSpace(result.spaceId);
|
|
3509
3946
|
replyForRequest(pending.requestId, authorizeResult(result.token, result.spaceId, spaceName), true);
|
|
3510
3947
|
state.authOpen = false;
|
|
3511
3948
|
state.pendingAuth = null;
|
|
3949
|
+
mintedSpace = null;
|
|
3512
3950
|
} catch (error) {
|
|
3513
3951
|
state.authError = error instanceof Error ? error.message : "Authorization failed.";
|
|
3514
3952
|
} finally {
|
|
@@ -3704,6 +4142,15 @@ var BoardExtensionRegistry = class {
|
|
|
3704
4142
|
const diagnostics = [];
|
|
3705
4143
|
const composition = parsed.data;
|
|
3706
4144
|
const cost = { ...ZERO_COST };
|
|
4145
|
+
for (const effect of input.effects ?? []) {
|
|
4146
|
+
diagnostics.push(...validateBuiltinBoardEffect(effect, `effects.${effect.id}`));
|
|
4147
|
+
if (!this.#extensions.has(`effect:${effect.kind}@${effect.kindVersion}`)) diagnostics.push({
|
|
4148
|
+
severity: "warning",
|
|
4149
|
+
code: "UNKNOWN_EFFECT",
|
|
4150
|
+
message: `No renderer is registered for ${effect.kind}@${effect.kindVersion}`,
|
|
4151
|
+
path: `effects.${effect.id}`
|
|
4152
|
+
});
|
|
4153
|
+
}
|
|
3707
4154
|
const events = [];
|
|
3708
4155
|
for (const clip of composition.timeline.clips) {
|
|
3709
4156
|
const definition = this.#extensions.get(`clip:${clip.kind}@${clip.kindVersion}`);
|
|
@@ -3737,7 +4184,10 @@ var BoardExtensionRegistry = class {
|
|
|
3737
4184
|
active[key] += event.cost[key] * event.direction;
|
|
3738
4185
|
cost[key] = Math.max(cost[key], active[key]);
|
|
3739
4186
|
}
|
|
3740
|
-
for (const effect of input.effects ?? [])
|
|
4187
|
+
for (const effect of input.effects ?? []) {
|
|
4188
|
+
const estimate = estimateBuiltinBoardEffectCost(effect);
|
|
4189
|
+
for (const key of Object.keys(cost)) cost[key] += estimate[key] ?? 0;
|
|
4190
|
+
}
|
|
3741
4191
|
const limits = input.limits ?? DEFAULT_BOARD_LIMITS;
|
|
3742
4192
|
for (const key of Object.keys(cost)) if (cost[key] > limits[key]) diagnostics.push({
|
|
3743
4193
|
severity: "warning",
|
|
@@ -3758,4 +4208,4 @@ function createBoardExtensionRegistry() {
|
|
|
3758
4208
|
}
|
|
3759
4209
|
const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
|
|
3760
4210
|
//#endregion
|
|
3761
|
-
export { APP_COMPOSER_CHIP_CONTENT_MAX_BYTES, APP_COMPOSER_CHIP_KEY_MAX_LENGTH, APP_COMPOSER_CHIP_LABEL_MAX_LENGTH, APP_SURFACE_READY_TIMEOUT_MS, APP_SURFACE_REQUEST_TIMEOUT_MS, AppCommerceApi, AppRealtimeApi, AppRefParseError, AppRoom, AppRuntimeApi, AppRuntimeApi as WorkRuntimeApi, AppSurfaceApi, AppsApi, BILLING_ACCESS_BLOCKED_ERROR_CODE, BOARD_ANIMATION_CHANNEL_CAPABILITIES, BOARD_CHANNELS, BOARD_COLOR_IDS, BOARD_GEO_KINDS, BillingApi, BoardAuthoringItemSchema, BoardClient, BoardCompositionInputSchema, BoardCompositionSchema, BoardEffectInputSchema, BoardEffectSchema, BoardExtensionRegistry, BoardItemPatchSchema, BoardPlaybackPolicySchema, BoardSemanticCommandSchema, COHUB_ENVIRONMENTS, COHUB_SOURCE_HEADER, COHUB_SOURCE_HEADER_NAMES, CohubClient, CohubHttpClient, DEFAULT_BOARD_LIMITS, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, DESKTOP_COMMAND_MAX_TIMEOUT_MS, DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, DESKTOP_COMMAND_PENDING_TTL_SECONDS, DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, DESKTOP_COMMAND_VERSION, DesktopCommandsApi, FEATURE_NOT_ENTITLED_ERROR_CODE, GenerationPolicyError, HttpError, PERMISSIONS, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, ReferencesApi, ReferralsApi, SessionGenerationStreamClient, SessionPatchReducer, SpacePublicFilesApi, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkSurfaceApi, assertGenerationRequestAllowedByPolicy, buildAppSurfaceRequest, buildSpaceInvitePath, buildSpacePath, clearGrantedAppScopes, compileComposition, composition, createAppBridgeCore, createAppBridgeCore as createWorkBridgeCore, createAppRuntime, createAppRuntime as createWorkRuntime, createBoardExtensionRegistry, createCohubClient, createHttpClient, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugAppIdResolver, createSlugAppIdResolver as createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, decodeGenerationPolicy, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, findGenerationModelPolicy, formatAppRef, formatWorkRef, getAllowedGenerationModelIds, getCohubContext, hasGrantedAppScopes, hasRequestSourceIdentity, isAppId, isBillingAccessBlockedCode, isBillingAccessBlockedError, isDesktopCallMethod, isFeatureNotEntitledError, isGenerationModelHidden, isHttpErrorCode, isRequestSourceClientId, isRequestSourceEmpty, isRequestSourceUuid, isTerminalDesktopCommandStatus, isUuid, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, normalizeBaseUrl, normalizeGenerationPolicy, normalizeRequestSource, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, parseAppRef, parseAppSurfaceReady, parseAppSurfaceResponse, parseAssistantMessageCommit, parseBoardCompositionInput, parseBoardEffectInput, parseBoardPlaybackPolicy, parseDesktopCommand, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseWorkRef, proceduralClip, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveAppTransport, resolveAppTransport as resolveWorkTransport, resolveCohubEnvironment, resolveExecutionAppId, resolveExecutionToken, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebsocketUrl, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, scopeListHasPermission, setGrantedAppScopes, track };
|
|
4211
|
+
export { APP_COMPOSER_CHIP_CONTENT_MAX_BYTES, APP_COMPOSER_CHIP_KEY_MAX_LENGTH, APP_COMPOSER_CHIP_LABEL_MAX_LENGTH, APP_SURFACE_READY_TIMEOUT_MS, APP_SURFACE_REQUEST_TIMEOUT_MS, AppCommerceApi, AppRealtimeApi, AppRefParseError, AppRoom, AppRuntimeApi, AppRuntimeApi as WorkRuntimeApi, AppSurfaceApi, AppsApi, BILLING_ACCESS_BLOCKED_ERROR_CODE, BOARD_ANIMATION_CHANNEL_CAPABILITIES, BOARD_CHANNELS, BOARD_COLOR_IDS, BOARD_GEO_KINDS, BillingApi, BoardAnimationSpecSchema, BoardAuthoringItemSchema, BoardClient, BoardCompositionInputSchema, BoardCompositionSchema, BoardDealParamsSchema, BoardEffectInputSchema, BoardEffectSchema, BoardExtensionRegistry, BoardItemPatchSchema, BoardPlaybackPolicySchema, BoardSemanticCommandSchema, COHUB_ENVIRONMENTS, COHUB_SOURCE_HEADER, COHUB_SOURCE_HEADER_NAMES, CohubClient, CohubHttpClient, DEFAULT_BOARD_LIMITS, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, DESKTOP_COMMAND_MAX_TIMEOUT_MS, DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, DESKTOP_COMMAND_PENDING_TTL_SECONDS, DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, DESKTOP_COMMAND_VERSION, DesktopCommandsApi, FEATURE_NOT_ENTITLED_ERROR_CODE, GenerationPolicyError, HttpError, PERMISSIONS, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, ReferencesApi, ReferralsApi, SessionGenerationStreamClient, SessionPatchReducer, SpacePublicFilesApi, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkSurfaceApi, assertGenerationRequestAllowedByPolicy, attachAppEmbed, buildAppSurfaceRequest, buildSpaceInvitePath, buildSpacePath, clearGrantedAppScopes, compileComposition, composition, createAppBridgeCore, createAppBridgeCore as createWorkBridgeCore, createAppRuntime, createAppRuntime as createWorkRuntime, createBoardExtensionRegistry, createCohubClient, createHttpClient, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugAppIdResolver, createSlugAppIdResolver as createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, decodeGenerationPolicy, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, findGenerationModelPolicy, formatAppRef, formatWorkRef, getAllowedGenerationModelIds, getCohubContext, hasGrantedAppScopes, hasRequestSourceIdentity, isAppId, isBillingAccessBlockedCode, isBillingAccessBlockedError, isDesktopCallMethod, isFeatureNotEntitledError, isGenerationModelHidden, isHttpErrorCode, isRequestSourceClientId, isRequestSourceEmpty, isRequestSourceUuid, isTerminalDesktopCommandStatus, isUuid, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, normalizeBaseUrl, normalizeGenerationPolicy, normalizeRequestSource, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, parseAppRef, parseAppSurfaceReady, parseAppSurfaceResponse, parseAssistantMessageCommit, parseBoardCompositionInput, parseBoardEffectInput, parseBoardPlaybackPolicy, parseDesktopCommand, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseWorkRef, proceduralClip, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveAppTransport, resolveAppTransport as resolveWorkTransport, resolveCohubEnvironment, resolveExecutionAppId, resolveExecutionToken, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebsocketUrl, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, scopeListHasPermission, setGrantedAppScopes, track };
|