@neta-art/cohub 6.0.0 → 8.0.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/README.md +6 -5
- package/dist/board/animation.d.ts +2 -1
- package/dist/board/animation.js +0 -1
- package/dist/board/core/connections.js +4 -4
- package/dist/board/core/export-plan.js +1 -1
- package/dist/board/core/shape-types.js +0 -1
- package/dist/board/index.d.ts +6 -5
- package/dist/board/index.js +7 -7
- package/dist/board/mutation.d.ts +2 -11
- package/dist/board/mutation.js +2 -56
- package/dist/board/render/renderers/text-card-renderer.js +1 -1
- package/dist/board/semantic-document.d.ts +30 -0
- package/dist/board/semantic-document.js +271 -0
- package/dist/board/semantic-mutation.d.ts +10 -0
- package/dist/board/semantic-mutation.js +203 -0
- package/dist/chunks/http.d.ts +340 -317
- package/dist/chunks/http.js +477 -1784
- package/dist/chunks/transport.js +1 -1
- package/dist/chunks/websocket.d.ts +3677 -3151
- package/dist/chunks/websocket.js +1 -1
- package/dist/http.d.ts +3 -3
- package/dist/index.d.ts +165 -119
- package/dist/index.js +1419 -446
- package/dist/protocol/dist/board-authoring.d.ts +1305 -1
- package/dist/protocol/dist/board-authoring.js +80 -6
- package/dist/protocol/dist/board-codec.d.ts +2 -2
- package/dist/protocol/dist/board-codec.js +19 -4
- package/dist/protocol/dist/board-composition.d.ts +126 -1
- package/dist/protocol/dist/board-composition.js +1 -9
- package/dist/protocol/dist/board-connection.d.ts +16 -16
- package/dist/protocol/dist/board-connection.js +16 -16
- package/dist/protocol/dist/board-document.d.ts +34 -3
- package/dist/protocol/dist/board-document.js +3 -1
- package/dist/protocol/dist/board-effect.d.ts +94 -0
- package/dist/protocol/dist/board-effect.js +53 -0
- package/dist/protocol/dist/board-json.js +16 -0
- package/dist/protocol/dist/board.d.ts +34 -164
- package/dist/protocol/dist/board.js +44 -45
- package/dist/protocol/dist/index.d.ts +8 -8
- package/dist/protocol/dist/realtime/types.d.ts +1 -1
- package/dist/types.d.ts +3 -1
- package/package.json +1 -2
- package/dist/board/codec.d.ts +0 -27
- package/dist/board/codec.js +0 -277
- package/dist/protocol/dist/board-content.js +0 -26
- package/dist/protocol/dist/board-upgrade.js +0 -2
- package/dist/protocol/dist/identifiers.js +0 -10
- package/dist/protocol/dist/index.js +0 -19
- package/dist/protocol/dist/provenance.js +0 -15
- package/dist/protocol/dist/public-identifiers.js +0 -38
- package/dist/protocol/dist/realtime/board-awareness.js +0 -119
- package/dist/protocol/dist/ui-command.js +0 -2
- package/dist/protocol/dist/work-promotion-stats.js +0 -11
- package/dist/protocol/dist/work-surface.js +0 -2
- package/dist/protocol/dist/work-view-stats.js +0 -3
- package/dist/protocol/dist/work.d.ts +0 -1
- /package/dist/protocol/dist/{board-upgrade.d.ts → app.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,9 +1,880 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import { _ as requestSourceToHeaders, a as sanitizeAccessToken, b as
|
|
1
|
+
import { $ as BOARD_BUILTIN_EFFECT_KINDS, A as DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, B as BoardEffectSchema, C as createSessionPatchReducer, D as DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, E as DESKTOP_COMMAND_MAX_TIMEOUT_MS, F as parseDesktopCommandId, G as BoardCompositionSchema, H as BOARD_ANIMATION_CHANNELS, I as BoardAuthoringItemSchema, J as parseBoardCompositionInput, K as BoardProceduralClipSchema, L as BoardItemPatchSchema, M as isDesktopCallMethod, N as isTerminalDesktopCommandStatus, O as DESKTOP_COMMAND_PENDING_TTL_SECONDS, P as parseDesktopCommand, Q as BOARD_BUILTIN_CLIP_KINDS, R as BoardSemanticMutationSchema, S as SessionPatchReducer, T as DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, U as BOARD_ANIMATION_CHANNEL_CAPABILITIES, V as parseBoardEffectInput, W as BoardCompositionInputSchema, X as BOARD_ARROW_STROKE_SIZE, Y as BoardConnectionSchema, Z as BOARD_BUILTIN_CAPABILITIES, _ as buildSpaceInvitePath, a as DesktopCommandsApi, at as SkillsApi, b as createSessionGenerationStreamClient, c as ReferralsApi, ct as GenerationsApi, d as TasksApi, et as DEFAULT_BOARD_RENDER_LIMITS, f as BoardClient, g as PublicInviteApi, h as SpacesApi, i as WorkCommerceApi, it as PublicAssetsApi, j as DESKTOP_COMMAND_VERSION, k as DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, l as UsersApi, lt as CronJobsApi, m as SpacePublicFilesApi, n as createHttpClient, nt as ReferencesApi, o as UiCommandsApi, ot as PromptsApi, p as SpaceClient, q as BoardTrackSchema, r as AppCommerceApi, rt as SearchApi, s as AppsApi, st as ModelsApi, t as CohubHttpClient, tt as SessionAccessApi, u as UserApi, ut as ChannelsApi, v as buildSpacePath, w as ensureRealtimeConnected, x as parseAssistantMessageCommit, y as SessionGenerationStreamClient, z as BoardEffectInputSchema } from "./chunks/http.js";
|
|
2
|
+
import { _ as requestSourceToHeaders, a as sanitizeAccessToken, b as REALTIME_ROOM_EVENT_NAME_PATTERN, c as REQUEST_SOURCE_VIA_MAX_LENGTH, d as isRequestSourceEmpty, f as isRequestSourceUuid, g as readRequestSourceFromEnv, h as parseRequestSourceFromHeaders, i as matchesUnauthorizedErrorToken, l as hasRequestSourceIdentity, m as normalizeRequestSource, n as HttpTransport, o as COHUB_SOURCE_HEADER, p as mergeRequestSourceIntoMeta, r as joinApiUrl, s as COHUB_SOURCE_HEADER_NAMES, t as HttpError, u as isRequestSourceClientId, v as resolveRequestSourceChannel, y as isUuid } from "./chunks/transport.js";
|
|
3
3
|
import { a as resolveApiBaseUrl, c as resolveWebsocketUrl, i as normalizeWebsocketUrl, n as normalizeBaseUrl, o as resolveCohubEnvironment, r as normalizeVoiceInputWebsocketUrl, s as resolveVoiceInputWebsocketUrl, t as COHUB_ENVIRONMENTS } from "./chunks/environment.js";
|
|
4
4
|
import { a as extractBillingPayload, c as isFeatureNotEntitledError, i as FEATURE_NOT_ENTITLED_ERROR_CODE, l as isHttpErrorCode, n as createWebsocketClient, o as isBillingAccessBlockedCode, r as BILLING_ACCESS_BLOCKED_ERROR_CODE, s as isBillingAccessBlockedError, t as WebsocketClient } from "./chunks/websocket.js";
|
|
5
5
|
import { VoiceApi, VoiceInputClient, createVoiceInputClient } from "./voice-input.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
+
//#region ../protocol/dist/board.js
|
|
8
|
+
const BOARD_DOCUMENT_KIND = "cohub.board";
|
|
9
|
+
const BOARD_MANIFEST_KIND = "cohub.board.manifest";
|
|
10
|
+
const idSchema = z.string().min(1).max(160);
|
|
11
|
+
const jsonObjectSchema = z.record(z.string(), z.unknown());
|
|
12
|
+
const finiteSchema = z.number().finite();
|
|
13
|
+
z.object({
|
|
14
|
+
kind: z.literal(BOARD_MANIFEST_KIND),
|
|
15
|
+
version: z.literal(1),
|
|
16
|
+
boardId: z.string().uuid(),
|
|
17
|
+
title: z.string().min(1).max(255)
|
|
18
|
+
});
|
|
19
|
+
z.object({
|
|
20
|
+
centerX: finiteSchema,
|
|
21
|
+
centerY: finiteSchema,
|
|
22
|
+
zoom: finiteSchema.positive()
|
|
23
|
+
});
|
|
24
|
+
const BoardCameraFocusSchema = z.discriminatedUnion("type", [
|
|
25
|
+
z.object({
|
|
26
|
+
type: z.literal("rect"),
|
|
27
|
+
rect: z.object({
|
|
28
|
+
x: finiteSchema,
|
|
29
|
+
y: finiteSchema,
|
|
30
|
+
width: finiteSchema.positive(),
|
|
31
|
+
height: finiteSchema.positive()
|
|
32
|
+
})
|
|
33
|
+
}),
|
|
34
|
+
z.object({
|
|
35
|
+
type: z.literal("item"),
|
|
36
|
+
itemId: idSchema
|
|
37
|
+
}),
|
|
38
|
+
z.object({
|
|
39
|
+
type: z.literal("items"),
|
|
40
|
+
itemIds: z.array(idSchema).min(1).max(1e3)
|
|
41
|
+
}),
|
|
42
|
+
z.object({
|
|
43
|
+
type: z.literal("frame"),
|
|
44
|
+
frameId: idSchema
|
|
45
|
+
})
|
|
46
|
+
]);
|
|
47
|
+
const BoardCameraFocusParamsSchema = z.object({
|
|
48
|
+
focus: BoardCameraFocusSchema,
|
|
49
|
+
fit: z.enum(["contain", "cover"]).default("contain"),
|
|
50
|
+
padding: finiteSchema.nonnegative().default(32),
|
|
51
|
+
minZoom: finiteSchema.positive().optional(),
|
|
52
|
+
maxZoom: finiteSchema.positive().optional()
|
|
53
|
+
}).superRefine((value, context) => {
|
|
54
|
+
if (value.minZoom !== void 0 && value.maxZoom !== void 0 && value.minZoom > value.maxZoom) context.addIssue({
|
|
55
|
+
code: "custom",
|
|
56
|
+
message: "minZoom must not exceed maxZoom",
|
|
57
|
+
path: ["minZoom"]
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
z.object({
|
|
61
|
+
nodeId: idSchema,
|
|
62
|
+
type: z.string().min(1).max(40),
|
|
63
|
+
parentId: idSchema.nullable(),
|
|
64
|
+
orderKey: z.string().max(4096).nullable(),
|
|
65
|
+
x: finiteSchema,
|
|
66
|
+
y: finiteSchema,
|
|
67
|
+
width: finiteSchema.positive(),
|
|
68
|
+
height: finiteSchema.positive(),
|
|
69
|
+
rotation: finiteSchema,
|
|
70
|
+
refKind: z.string().max(40).nullable(),
|
|
71
|
+
refPath: z.string().max(4096).nullable(),
|
|
72
|
+
refUrl: z.string().max(4096).nullable(),
|
|
73
|
+
view: jsonObjectSchema,
|
|
74
|
+
style: jsonObjectSchema,
|
|
75
|
+
data: jsonObjectSchema
|
|
76
|
+
});
|
|
77
|
+
const stripBoardServerFields = (value) => {
|
|
78
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
79
|
+
const { boardId: _boardId, revision: _revision, ...input } = value;
|
|
80
|
+
return input;
|
|
81
|
+
};
|
|
82
|
+
const BoardCreateEffectInputSchema = z.preprocess(stripBoardServerFields, BoardEffectInputSchema);
|
|
83
|
+
const BoardCreateCompositionInputSchema = z.preprocess(stripBoardServerFields, BoardCompositionInputSchema);
|
|
84
|
+
const BoardCreateInputSchema = z.object({
|
|
85
|
+
path: z.string().min(1),
|
|
86
|
+
/** Reused by clients when board creation is interrupted and retried. */
|
|
87
|
+
mutationId: z.string().max(128).optional(),
|
|
88
|
+
title: z.string().min(1).max(255).optional(),
|
|
89
|
+
metadata: jsonObjectSchema.optional(),
|
|
90
|
+
items: z.array(BoardAuthoringItemSchema).max(5e4).optional(),
|
|
91
|
+
connections: z.array(BoardConnectionSchema).max(5e4).optional(),
|
|
92
|
+
effects: z.array(BoardCreateEffectInputSchema).max(5e4).optional(),
|
|
93
|
+
compositions: z.array(BoardCreateCompositionInputSchema).max(5e4).optional()
|
|
94
|
+
});
|
|
95
|
+
const jsonSchema$1 = (schema) => z.toJSONSchema(schema);
|
|
96
|
+
const authoringSchemaFactories = {
|
|
97
|
+
item: () => jsonSchema$1(BoardAuthoringItemSchema),
|
|
98
|
+
itemPatch: () => jsonSchema$1(BoardItemPatchSchema),
|
|
99
|
+
mutation: () => jsonSchema$1(BoardSemanticMutationSchema),
|
|
100
|
+
composition: () => jsonSchema$1(BoardCompositionInputSchema),
|
|
101
|
+
effect: () => jsonSchema$1(BoardEffectInputSchema),
|
|
102
|
+
create: () => jsonSchema$1(BoardCreateInputSchema)
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* JSON Schemas for the semantic authoring surface. Each property is enumerable
|
|
106
|
+
* for Object.keys/JSON.stringify, but generated only when that property is
|
|
107
|
+
* requested. This keeps protocol imports cheap while capabilities still returns
|
|
108
|
+
* the complete machine-readable schema object.
|
|
109
|
+
*/
|
|
110
|
+
const BOARD_AUTHORING_SCHEMAS = {};
|
|
111
|
+
for (const key of Object.keys(authoringSchemaFactories)) {
|
|
112
|
+
let cached;
|
|
113
|
+
Object.defineProperty(BOARD_AUTHORING_SCHEMAS, key, {
|
|
114
|
+
enumerable: true,
|
|
115
|
+
configurable: false,
|
|
116
|
+
get() {
|
|
117
|
+
cached ??= authoringSchemaFactories[key]();
|
|
118
|
+
return cached;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
z.object({
|
|
123
|
+
include: z.array(z.enum([
|
|
124
|
+
"nodes",
|
|
125
|
+
"connections",
|
|
126
|
+
"effects",
|
|
127
|
+
"compositions",
|
|
128
|
+
"playback"
|
|
129
|
+
])).optional(),
|
|
130
|
+
nodeIds: z.array(idSchema).max(5e4).optional(),
|
|
131
|
+
connectionIds: z.array(idSchema).max(5e4).optional(),
|
|
132
|
+
effectIds: z.array(idSchema).max(5e4).optional(),
|
|
133
|
+
compositionIds: z.array(idSchema).max(5e4).optional(),
|
|
134
|
+
viewport: z.object({
|
|
135
|
+
x: finiteSchema,
|
|
136
|
+
y: finiteSchema,
|
|
137
|
+
width: finiteSchema.positive(),
|
|
138
|
+
height: finiteSchema.positive()
|
|
139
|
+
}).optional()
|
|
140
|
+
});
|
|
141
|
+
/** Persisted on `boards.metadata.playback`: how a Board plays when opened. */
|
|
142
|
+
const BoardPlaybackPolicySchema = z.object({
|
|
143
|
+
compositionId: idSchema,
|
|
144
|
+
/** Delay before the first local playback after opening the Board, in milliseconds. */
|
|
145
|
+
delayMs: finiteSchema.nonnegative().default(0)
|
|
146
|
+
});
|
|
147
|
+
function parseBoardPlaybackPolicy(metadata) {
|
|
148
|
+
const parsed = BoardPlaybackPolicySchema.safeParse(metadata.playback);
|
|
149
|
+
return parsed.success ? parsed.data : null;
|
|
150
|
+
}
|
|
151
|
+
z.discriminatedUnion("type", [
|
|
152
|
+
z.object({
|
|
153
|
+
commandId: idSchema,
|
|
154
|
+
type: z.literal("play"),
|
|
155
|
+
compositionId: idSchema,
|
|
156
|
+
position: finiteSchema.nonnegative().optional(),
|
|
157
|
+
timeScale: finiteSchema.positive().max(4).optional(),
|
|
158
|
+
shared: z.boolean().optional(),
|
|
159
|
+
seed: idSchema.optional()
|
|
160
|
+
}),
|
|
161
|
+
z.object({
|
|
162
|
+
commandId: idSchema,
|
|
163
|
+
type: z.literal("pause"),
|
|
164
|
+
playbackId: z.string().uuid()
|
|
165
|
+
}),
|
|
166
|
+
z.object({
|
|
167
|
+
commandId: idSchema,
|
|
168
|
+
type: z.literal("seek"),
|
|
169
|
+
playbackId: z.string().uuid(),
|
|
170
|
+
position: finiteSchema.nonnegative()
|
|
171
|
+
}),
|
|
172
|
+
z.object({
|
|
173
|
+
commandId: idSchema,
|
|
174
|
+
type: z.literal("stop"),
|
|
175
|
+
playbackId: z.string().uuid()
|
|
176
|
+
})
|
|
177
|
+
]);
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region ../protocol/dist/board-capability-registry.js
|
|
180
|
+
const CLIPS = new Set(BOARD_BUILTIN_CLIP_KINDS);
|
|
181
|
+
new Set(BOARD_BUILTIN_EFFECT_KINDS);
|
|
182
|
+
const record = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
183
|
+
const finite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
184
|
+
function validateBuiltinBoardClip(clip, path = "clip") {
|
|
185
|
+
if (!CLIPS.has(clip.kind)) return [];
|
|
186
|
+
const errors = [];
|
|
187
|
+
const error = (message, suffix, coordinateSpace, code = "INVALID_BOARD_CLIP") => {
|
|
188
|
+
errors.push({
|
|
189
|
+
severity: "error",
|
|
190
|
+
code,
|
|
191
|
+
message,
|
|
192
|
+
path: `${path}.${suffix}`,
|
|
193
|
+
...coordinateSpace ? { coordinateSpace } : {}
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
if ((clip.kind.startsWith("motion.") || clip.kind.startsWith("draw.") || clip.kind === "text.reveal" || clip.kind === "effects.trail") && clip.target.type !== "item") error(`${clip.kind} must target an item`, "target");
|
|
197
|
+
if (clip.kind.startsWith("camera.") && clip.target.type !== "camera") error(`${clip.kind} must target the camera`, "target");
|
|
198
|
+
if (clip.kind === "motion.path") {
|
|
199
|
+
const points = clip.params.points;
|
|
200
|
+
if (!Array.isArray(points) || points.length < 2 || points.length > 1e4) error("motion path must contain 2 to 10000 world-offset points", "params.points", "world-offset");
|
|
201
|
+
else for (const [index, point] of points.entries()) if (!record(point) || !finite(point.x) || !finite(point.y)) error("motion path point must contain finite x and y", `params.points.${index}`, "world-offset");
|
|
202
|
+
}
|
|
203
|
+
if (clip.kind === "effects.particles") {
|
|
204
|
+
const count = clip.params.count;
|
|
205
|
+
if (!Number.isSafeInteger(count) || count < 1 || count > DEFAULT_BOARD_RENDER_LIMITS.particles) error(`particle count must be an integer from 1 to ${DEFAULT_BOARD_RENDER_LIMITS.particles}`, "params.count", void 0, "INVALID_PARTICLE_COUNT");
|
|
206
|
+
const bounds = clip.params.bounds;
|
|
207
|
+
if (!record(bounds) || !finite(bounds.x) || !finite(bounds.y) || !finite(bounds.width) || !finite(bounds.height) || bounds.width <= 0 || bounds.height <= 0) error("particles require positive finite world bounds", "params.bounds", "world", "PARTICLE_BOUNDS_REQUIRED");
|
|
208
|
+
}
|
|
209
|
+
if (clip.kind === "camera.focus") {
|
|
210
|
+
const parsed = BoardCameraFocusParamsSchema.safeParse(clip.params);
|
|
211
|
+
if (!parsed.success) error(parsed.error.issues[0]?.message ?? "invalid camera focus", "params", "world");
|
|
212
|
+
}
|
|
213
|
+
return errors;
|
|
214
|
+
}
|
|
215
|
+
function estimateBuiltinBoardClipCost(clip) {
|
|
216
|
+
switch (clip.kind) {
|
|
217
|
+
case "effects.particles": {
|
|
218
|
+
const count = Number.isSafeInteger(clip.params.count) ? Math.max(0, clip.params.count) : 0;
|
|
219
|
+
return {
|
|
220
|
+
particles: count,
|
|
221
|
+
vertices: count * 4,
|
|
222
|
+
dynamicVertices: count * 4,
|
|
223
|
+
drawCalls: 1,
|
|
224
|
+
bufferBytes: count * 48,
|
|
225
|
+
simulationSteps: count
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
case "effects.trail": return {
|
|
229
|
+
vertices: 32,
|
|
230
|
+
dynamicVertices: 32,
|
|
231
|
+
drawCalls: 1,
|
|
232
|
+
bufferBytes: 1024,
|
|
233
|
+
simulationSteps: 16
|
|
234
|
+
};
|
|
235
|
+
case "effects.impact":
|
|
236
|
+
case "effects.flash": return {
|
|
237
|
+
vertices: 64,
|
|
238
|
+
drawCalls: 1
|
|
239
|
+
};
|
|
240
|
+
case "draw.reveal":
|
|
241
|
+
case "draw.handwrite": return {
|
|
242
|
+
drawCalls: 1,
|
|
243
|
+
dynamicVertices: 1
|
|
244
|
+
};
|
|
245
|
+
case "motion.path": return { simulationSteps: Array.isArray(clip.params.points) ? clip.params.points.length : 0 };
|
|
246
|
+
default: return {};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
//#endregion
|
|
250
|
+
//#region ../protocol/dist/board-url.js
|
|
251
|
+
const BOARD_REMOTE_URL_MAX_LENGTH = 4096;
|
|
252
|
+
function parseIpv4(host) {
|
|
253
|
+
const parts = host.split(".").map(Number);
|
|
254
|
+
return parts.length === 4 && parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255) ? parts : null;
|
|
255
|
+
}
|
|
256
|
+
function isBlockedIpv4(host) {
|
|
257
|
+
const parts = parseIpv4(host);
|
|
258
|
+
if (!parts) return false;
|
|
259
|
+
const [first, second, third] = parts;
|
|
260
|
+
if (first === 0 || first === 10 || first === 127) return true;
|
|
261
|
+
if (first === 100 && second >= 64 && second <= 127) return true;
|
|
262
|
+
if (first === 169 && second === 254) return true;
|
|
263
|
+
if (first === 172 && second >= 16 && second <= 31) return true;
|
|
264
|
+
if (first === 192 && second === 168) return true;
|
|
265
|
+
if (first === 192 && second === 0 && (third === 0 || third === 2)) return true;
|
|
266
|
+
if (first === 192 && second === 88 && third === 99) return true;
|
|
267
|
+
if (first === 198 && (second === 18 || second === 19)) return true;
|
|
268
|
+
if (first === 198 && second === 51 && third === 100) return true;
|
|
269
|
+
if (first === 203 && second === 0 && third === 113) return true;
|
|
270
|
+
return first >= 224;
|
|
271
|
+
}
|
|
272
|
+
function expandIpv6(host) {
|
|
273
|
+
const [head, tail, extra] = host.toLowerCase().split("::");
|
|
274
|
+
if (extra !== void 0) return null;
|
|
275
|
+
const headParts = head ? head.split(":").filter(Boolean) : [];
|
|
276
|
+
const tailParts = tail ? tail.split(":").filter(Boolean) : [];
|
|
277
|
+
const missing = 8 - headParts.length - tailParts.length;
|
|
278
|
+
if (missing < 0 || tail === void 0 && missing !== 0) return null;
|
|
279
|
+
const parts = [
|
|
280
|
+
...headParts,
|
|
281
|
+
...Array.from({ length: missing }, () => "0"),
|
|
282
|
+
...tailParts
|
|
283
|
+
];
|
|
284
|
+
if (parts.length !== 8 || parts.some((part) => !/^[0-9a-f]{1,4}$/.test(part))) return null;
|
|
285
|
+
return parts.map((part) => part.padStart(4, "0"));
|
|
286
|
+
}
|
|
287
|
+
function isBlockedIpv6(host) {
|
|
288
|
+
const parts = expandIpv6(host);
|
|
289
|
+
if (!parts) return true;
|
|
290
|
+
if (parts.every((part) => part === "0000")) return true;
|
|
291
|
+
if (parts.slice(0, 7).every((part) => part === "0000") && parts[7] === "0001") return true;
|
|
292
|
+
if (parts.slice(0, 5).every((part) => part === "0000") && parts[5] === "ffff") {
|
|
293
|
+
const high = Number.parseInt(parts[6] ?? "0", 16);
|
|
294
|
+
const low = Number.parseInt(parts[7] ?? "0", 16);
|
|
295
|
+
return isBlockedIpv4(`${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`);
|
|
296
|
+
}
|
|
297
|
+
if (parts.slice(0, 6).every((part) => part === "0000")) return true;
|
|
298
|
+
const first = Number.parseInt(parts[0] ?? "0", 16);
|
|
299
|
+
if ((first & 65024) === 64512) return true;
|
|
300
|
+
if ((first & 65472) === 65152 || (first & 65472) === 65216) return true;
|
|
301
|
+
if ((first & 65280) === 65280) return true;
|
|
302
|
+
return parts[0] === "2001" && parts[1] === "0db8";
|
|
303
|
+
}
|
|
304
|
+
function isPublicBoardRemoteAddress(value) {
|
|
305
|
+
const address = value.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
306
|
+
if (parseIpv4(address)) return !isBlockedIpv4(address);
|
|
307
|
+
return address.includes(":") && !isBlockedIpv6(address);
|
|
308
|
+
}
|
|
309
|
+
function isBlockedHost(hostname) {
|
|
310
|
+
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
311
|
+
if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal")) return true;
|
|
312
|
+
if (parseIpv4(host) || host.includes(":")) return !isPublicBoardRemoteAddress(host);
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Normalize a browser-loadable public HTTP(S) URL. This blocks explicit local
|
|
317
|
+
* addresses; any future server-side fetcher must additionally validate DNS
|
|
318
|
+
* resolution to defend against rebinding.
|
|
319
|
+
*/
|
|
320
|
+
function normalizeBoardRemoteUrl(value) {
|
|
321
|
+
if (typeof value !== "string") return void 0;
|
|
322
|
+
const input = value.trim();
|
|
323
|
+
if (!input || input.length > 4096) return void 0;
|
|
324
|
+
try {
|
|
325
|
+
const url = new URL(input);
|
|
326
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return void 0;
|
|
327
|
+
if (url.username || url.password || isBlockedHost(url.hostname)) return;
|
|
328
|
+
const normalized = url.toString();
|
|
329
|
+
return normalized.length <= 4096 ? normalized : void 0;
|
|
330
|
+
} catch {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const BoardRemoteUrlSchema = z.string().max(BOARD_REMOTE_URL_MAX_LENGTH).refine((value) => normalizeBoardRemoteUrl(value) !== void 0, { message: "URL must be a public HTTP(S) URL without credentials" });
|
|
335
|
+
//#endregion
|
|
336
|
+
//#region ../protocol/dist/board-document.js
|
|
337
|
+
const BoardFrameSchema = z.object({
|
|
338
|
+
x: z.number().finite(),
|
|
339
|
+
y: z.number().finite(),
|
|
340
|
+
width: z.number().finite().positive(),
|
|
341
|
+
height: z.number().finite().positive(),
|
|
342
|
+
rotation: z.number().finite().default(0)
|
|
343
|
+
});
|
|
344
|
+
/**
|
|
345
|
+
* The board camera. This is local UI state, not synced content: semantic ops
|
|
346
|
+
* (see diffBoardDocuments) never describe the viewport, and the editor holds
|
|
347
|
+
* the live camera separately from the persisted document. Here it only serves
|
|
348
|
+
* as an initial camera hint when a document is first loaded.
|
|
349
|
+
*/
|
|
350
|
+
const BoardViewportSchema = z.object({
|
|
351
|
+
x: z.number().finite(),
|
|
352
|
+
y: z.number().finite(),
|
|
353
|
+
zoom: z.number().finite().min(.05).max(8)
|
|
354
|
+
});
|
|
355
|
+
const BoardAppearanceSchema = z.object({
|
|
356
|
+
theme: z.string().min(1).default("clean"),
|
|
357
|
+
background: z.object({
|
|
358
|
+
kind: z.enum([
|
|
359
|
+
"solid",
|
|
360
|
+
"dots",
|
|
361
|
+
"grid",
|
|
362
|
+
"image",
|
|
363
|
+
"shader",
|
|
364
|
+
"custom"
|
|
365
|
+
]).default("dots"),
|
|
366
|
+
color: z.string().optional(),
|
|
367
|
+
imageUrl: BoardRemoteUrlSchema.optional(),
|
|
368
|
+
fit: z.enum([
|
|
369
|
+
"cover",
|
|
370
|
+
"contain",
|
|
371
|
+
"repeat"
|
|
372
|
+
]).optional(),
|
|
373
|
+
position: z.enum([
|
|
374
|
+
"center",
|
|
375
|
+
"top",
|
|
376
|
+
"bottom",
|
|
377
|
+
"left",
|
|
378
|
+
"right"
|
|
379
|
+
]).optional(),
|
|
380
|
+
opacity: z.number().finite().min(0).max(1).optional()
|
|
381
|
+
}).default({ kind: "solid" }),
|
|
382
|
+
grid: z.object({
|
|
383
|
+
visible: z.boolean().default(false),
|
|
384
|
+
size: z.number().finite().min(4).default(24),
|
|
385
|
+
opacity: z.number().finite().min(0).max(1).default(.12)
|
|
386
|
+
}).default({
|
|
387
|
+
visible: false,
|
|
388
|
+
size: 24,
|
|
389
|
+
opacity: .12
|
|
390
|
+
}),
|
|
391
|
+
mood: z.enum([
|
|
392
|
+
"clean",
|
|
393
|
+
"playful",
|
|
394
|
+
"arcane",
|
|
395
|
+
"cyber",
|
|
396
|
+
"natural"
|
|
397
|
+
]).default("clean")
|
|
398
|
+
});
|
|
399
|
+
/** Optional visual chrome still carried by a few older shapes. */
|
|
400
|
+
const BoardItemStyleSchema = z.object({
|
|
401
|
+
variant: z.string().min(1).default("default"),
|
|
402
|
+
theme: z.string().min(1).optional(),
|
|
403
|
+
accentColor: z.string().optional(),
|
|
404
|
+
size: z.enum([
|
|
405
|
+
"sm",
|
|
406
|
+
"md",
|
|
407
|
+
"lg"
|
|
408
|
+
]).default("md"),
|
|
409
|
+
emphasis: z.enum([
|
|
410
|
+
"normal",
|
|
411
|
+
"rare",
|
|
412
|
+
"epic",
|
|
413
|
+
"legendary"
|
|
414
|
+
]).default("normal"),
|
|
415
|
+
effects: z.array(z.string().min(1)).default([])
|
|
416
|
+
});
|
|
417
|
+
const SpaceFileRefSchema = z.object({
|
|
418
|
+
kind: z.literal("space-file"),
|
|
419
|
+
path: z.string().min(1)
|
|
420
|
+
});
|
|
421
|
+
const BoardMediaSnapshotSchema = z.object({
|
|
422
|
+
title: z.string().optional(),
|
|
423
|
+
mimeType: z.string().optional(),
|
|
424
|
+
size: z.number().finite().nonnegative().optional(),
|
|
425
|
+
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
426
|
+
/** Intrinsic pixel size once known. */
|
|
427
|
+
naturalWidth: z.number().finite().positive().optional(),
|
|
428
|
+
naturalHeight: z.number().finite().positive().optional()
|
|
429
|
+
});
|
|
430
|
+
const BoardItemBaseSchema = z.object({
|
|
431
|
+
id: z.string().min(1),
|
|
432
|
+
frame: BoardFrameSchema,
|
|
433
|
+
parentId: z.string().min(1).nullable().optional(),
|
|
434
|
+
/** When true the shape cannot be moved, resized, or deleted. */
|
|
435
|
+
locked: z.boolean().optional(),
|
|
436
|
+
style: BoardItemStyleSchema.optional(),
|
|
437
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
438
|
+
});
|
|
439
|
+
/** Freestanding text — no card chrome; its frame always follows the glyphs. */
|
|
440
|
+
const BoardTextItemSchema = BoardItemBaseSchema.extend({
|
|
441
|
+
type: z.literal("text"),
|
|
442
|
+
text: z.string().default(""),
|
|
443
|
+
color: z.string().min(1).default("neutral"),
|
|
444
|
+
fontSize: z.number().finite().min(2).max(512).default(24)
|
|
445
|
+
});
|
|
446
|
+
const BoardGeoItemSchema = BoardItemBaseSchema.extend({
|
|
447
|
+
type: z.literal("geo"),
|
|
448
|
+
geo: z.string().min(1).default("rectangle"),
|
|
449
|
+
text: z.string().default(""),
|
|
450
|
+
color: z.string().min(1).default("brand"),
|
|
451
|
+
fillOpacity: z.number().finite().min(0).max(1).default(0)
|
|
452
|
+
});
|
|
453
|
+
/** A raw freehand sample. Pressure defaults to 0.5 (mouse) when absent. */
|
|
454
|
+
const DrawPointSchema = z.object({
|
|
455
|
+
x: z.number().finite(),
|
|
456
|
+
y: z.number().finite(),
|
|
457
|
+
p: z.number().finite().min(0).max(1).default(.5)
|
|
458
|
+
});
|
|
459
|
+
/** A world-space point. */
|
|
460
|
+
const BoardPointSchema = z.object({
|
|
461
|
+
x: z.number().finite(),
|
|
462
|
+
y: z.number().finite()
|
|
463
|
+
});
|
|
464
|
+
const BoardDrawItemSchema = BoardItemBaseSchema.extend({
|
|
465
|
+
type: z.literal("draw"),
|
|
466
|
+
points: z.array(DrawPointSchema).default([]),
|
|
467
|
+
color: z.string().min(1).default("brand"),
|
|
468
|
+
size: z.number().finite().positive().default(4)
|
|
469
|
+
});
|
|
470
|
+
/**
|
|
471
|
+
* A free arrow — a standalone annotation stroke between two world points.
|
|
472
|
+
*
|
|
473
|
+
* Arrows do not relate nodes. A relation between two nodes is a
|
|
474
|
+
* `BoardConnection`, which is stored separately and resolves its geometry from
|
|
475
|
+
* the live node frames. Keeping the two apart is what lets an arrow be a plain
|
|
476
|
+
* shape (its own frame, freely movable) while a connection stays purely
|
|
477
|
+
* semantic — neither has to pretend to be the other.
|
|
478
|
+
*/
|
|
479
|
+
const BoardArrowItemSchema = BoardItemBaseSchema.extend({
|
|
480
|
+
type: z.literal("arrow"),
|
|
481
|
+
start: BoardPointSchema,
|
|
482
|
+
end: BoardPointSchema,
|
|
483
|
+
bend: z.number().finite().min(-.85).max(.85).default(0),
|
|
484
|
+
color: z.string().min(1).default("brand"),
|
|
485
|
+
size: z.number().finite().positive().default(BOARD_ARROW_STROKE_SIZE),
|
|
486
|
+
arrowStart: z.boolean().default(false),
|
|
487
|
+
arrowEnd: z.boolean().default(true),
|
|
488
|
+
label: z.string().default("")
|
|
489
|
+
});
|
|
490
|
+
/** A frame container for organising shapes. */
|
|
491
|
+
const BoardFrameItemSchema = BoardItemBaseSchema.extend({
|
|
492
|
+
type: z.literal("frame"),
|
|
493
|
+
label: z.string().default("Frame"),
|
|
494
|
+
color: z.string().min(1).default("neutral")
|
|
495
|
+
});
|
|
496
|
+
/** Image node — space file only, natural aspect, no chrome. */
|
|
497
|
+
const BoardImageItemSchema = BoardItemBaseSchema.extend({
|
|
498
|
+
type: z.literal("image"),
|
|
499
|
+
ref: SpaceFileRefSchema,
|
|
500
|
+
snapshot: BoardMediaSnapshotSchema.optional(),
|
|
501
|
+
/** Optional normalized crop in source image space (0..1). */
|
|
502
|
+
crop: z.object({
|
|
503
|
+
x: z.number().finite().min(0).max(1).default(0),
|
|
504
|
+
y: z.number().finite().min(0).max(1).default(0),
|
|
505
|
+
w: z.number().finite().min(0).max(1).default(1),
|
|
506
|
+
h: z.number().finite().min(0).max(1).default(1)
|
|
507
|
+
}).optional()
|
|
508
|
+
});
|
|
509
|
+
/** Video node — space file only. Playback state is local UI, never synced. */
|
|
510
|
+
const BoardVideoItemSchema = BoardItemBaseSchema.extend({
|
|
511
|
+
type: z.literal("video"),
|
|
512
|
+
ref: SpaceFileRefSchema,
|
|
513
|
+
snapshot: BoardMediaSnapshotSchema.optional()
|
|
514
|
+
});
|
|
515
|
+
/** Audio node — space file only. Playback state is local UI, never synced. */
|
|
516
|
+
const BoardAudioItemSchema = BoardItemBaseSchema.extend({
|
|
517
|
+
type: z.literal("audio"),
|
|
518
|
+
ref: SpaceFileRefSchema,
|
|
519
|
+
snapshot: BoardMediaSnapshotSchema.extend({ durationMs: z.number().finite().nonnegative().optional() }).optional()
|
|
520
|
+
});
|
|
521
|
+
/**
|
|
522
|
+
* Cached display facts for a file card.
|
|
523
|
+
*
|
|
524
|
+
* Purely derived from the referenced file and versioned by `mtimeMs`, so it is a
|
|
525
|
+
* cache and never a second source of truth: the workspace file remains
|
|
526
|
+
* authoritative, and a stale snapshot is detectable rather than silently wrong.
|
|
527
|
+
* It is stored so that opening a board renders complete cards immediately, with
|
|
528
|
+
* no per-node file read on the first paint.
|
|
529
|
+
*/
|
|
530
|
+
const BoardFileSnapshotSchema = z.object({
|
|
531
|
+
title: z.string().optional(),
|
|
532
|
+
mimeType: z.string().optional(),
|
|
533
|
+
size: z.number().finite().nonnegative().optional(),
|
|
534
|
+
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
535
|
+
/** Cleaned leading prose. Capped when written (see FILE_EXCERPT_MAX_CHARS). */
|
|
536
|
+
excerpt: z.string().optional(),
|
|
537
|
+
/** Cover image inside the space, already resolved to a workspace path. */
|
|
538
|
+
coverPath: z.string().optional(),
|
|
539
|
+
/** Cover image at an absolute https URL, as declared by the file itself. */
|
|
540
|
+
coverUrl: z.string().optional()
|
|
541
|
+
});
|
|
542
|
+
/**
|
|
543
|
+
* File node — a thumbnail entry point to any workspace file.
|
|
544
|
+
*
|
|
545
|
+
* This is the fallback for every dropped file that is not natively an image or
|
|
546
|
+
* video, including binaries and unknown extensions: a board should never refuse
|
|
547
|
+
* a file, only present it with less detail. Presentation tiers are derived from
|
|
548
|
+
* the snapshot (see filePreviewKind), not stored, so there is no display state
|
|
549
|
+
* to drift from the facts.
|
|
550
|
+
*/
|
|
551
|
+
const BoardFileItemSchema = BoardItemBaseSchema.extend({
|
|
552
|
+
type: z.literal("file"),
|
|
553
|
+
ref: SpaceFileRefSchema,
|
|
554
|
+
snapshot: BoardFileSnapshotSchema.optional()
|
|
555
|
+
});
|
|
556
|
+
const BoardTaskMediaArtifactFields = {
|
|
557
|
+
id: z.string().min(1).max(240),
|
|
558
|
+
title: z.string().max(240).optional(),
|
|
559
|
+
url: BoardRemoteUrlSchema,
|
|
560
|
+
mimeType: z.string().max(160).optional()
|
|
561
|
+
};
|
|
562
|
+
const BoardTaskArtifactSchema = z.discriminatedUnion("type", [
|
|
563
|
+
z.object({
|
|
564
|
+
...BoardTaskMediaArtifactFields,
|
|
565
|
+
type: z.literal("image"),
|
|
566
|
+
naturalWidth: z.number().positive().optional(),
|
|
567
|
+
naturalHeight: z.number().positive().optional()
|
|
568
|
+
}).strict(),
|
|
569
|
+
z.object({
|
|
570
|
+
...BoardTaskMediaArtifactFields,
|
|
571
|
+
type: z.literal("video"),
|
|
572
|
+
previewUrl: BoardRemoteUrlSchema.optional(),
|
|
573
|
+
durationMs: z.number().int().positive().optional(),
|
|
574
|
+
naturalWidth: z.number().positive().optional(),
|
|
575
|
+
naturalHeight: z.number().positive().optional()
|
|
576
|
+
}).strict(),
|
|
577
|
+
z.object({
|
|
578
|
+
...BoardTaskMediaArtifactFields,
|
|
579
|
+
type: z.literal("audio"),
|
|
580
|
+
previewUrl: BoardRemoteUrlSchema.optional(),
|
|
581
|
+
durationMs: z.number().int().positive().optional()
|
|
582
|
+
}).strict(),
|
|
583
|
+
z.object({
|
|
584
|
+
id: z.string().min(1).max(240),
|
|
585
|
+
type: z.literal("text"),
|
|
586
|
+
title: z.string().max(240).optional(),
|
|
587
|
+
textExcerpt: z.string().min(1).max(480)
|
|
588
|
+
}).strict()
|
|
589
|
+
]);
|
|
590
|
+
/**
|
|
591
|
+
* Cached task facts used for an immediate first paint. The task run remains the
|
|
592
|
+
* source of truth and live clients refresh this projection by `taskRunId`.
|
|
593
|
+
*/
|
|
594
|
+
const BoardTaskSnapshotSchema = z.object({
|
|
595
|
+
taskType: z.string().min(1).max(120),
|
|
596
|
+
status: z.enum([
|
|
597
|
+
"pending",
|
|
598
|
+
"running",
|
|
599
|
+
"completed",
|
|
600
|
+
"failed"
|
|
601
|
+
]),
|
|
602
|
+
title: z.string().min(1).max(240),
|
|
603
|
+
model: z.string().max(160).optional(),
|
|
604
|
+
promptExcerpt: z.string().max(480).optional(),
|
|
605
|
+
artifactCount: z.number().int().nonnegative(),
|
|
606
|
+
artifacts: z.array(BoardTaskArtifactSchema).max(6).default([]),
|
|
607
|
+
updatedAt: z.string().optional()
|
|
608
|
+
}).strict();
|
|
609
|
+
/** A stable reference to a task run with a small, replaceable display cache. */
|
|
610
|
+
const BoardTaskItemSchema = BoardItemBaseSchema.extend({
|
|
611
|
+
type: z.literal("task"),
|
|
612
|
+
taskRunId: z.string().min(1),
|
|
613
|
+
snapshot: BoardTaskSnapshotSchema
|
|
614
|
+
});
|
|
615
|
+
/**
|
|
616
|
+
* A forward-compatible carrier for shape types this client does not recognise.
|
|
617
|
+
* Its discriminant is the literal `"unknown"` so the item union still narrows
|
|
618
|
+
* cleanly on `type`. The real type string and every original field are preserved
|
|
619
|
+
* verbatim in `raw`.
|
|
620
|
+
*/
|
|
621
|
+
const UNKNOWN_BOARD_ITEM_TYPE = "unknown";
|
|
622
|
+
const BoardItemSchema = z.any().transform((raw) => parseBoardItemLoose(raw));
|
|
623
|
+
/**
|
|
624
|
+
* Parse a single item leniently: known types are validated and normalised;
|
|
625
|
+
* anything else becomes a lossless unknown item. Never throws on shape data —
|
|
626
|
+
* a malformed known item degrades to unknown rather than failing the document.
|
|
627
|
+
*/
|
|
628
|
+
function parseBoardItemLoose(raw) {
|
|
629
|
+
if (!raw || typeof raw !== "object") return makeUnknownItem(raw);
|
|
630
|
+
switch (raw.type) {
|
|
631
|
+
case "image": {
|
|
632
|
+
const parsed = BoardImageItemSchema.safeParse(raw);
|
|
633
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
634
|
+
}
|
|
635
|
+
case "video": {
|
|
636
|
+
const parsed = BoardVideoItemSchema.safeParse(raw);
|
|
637
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
638
|
+
}
|
|
639
|
+
case "audio": {
|
|
640
|
+
const parsed = BoardAudioItemSchema.safeParse(raw);
|
|
641
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
642
|
+
}
|
|
643
|
+
case "file": {
|
|
644
|
+
const parsed = BoardFileItemSchema.safeParse(raw);
|
|
645
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
646
|
+
}
|
|
647
|
+
case "task": {
|
|
648
|
+
const parsed = BoardTaskItemSchema.safeParse(raw);
|
|
649
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
650
|
+
}
|
|
651
|
+
case "text": {
|
|
652
|
+
const parsed = BoardTextItemSchema.safeParse(raw);
|
|
653
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
654
|
+
}
|
|
655
|
+
case "geo": {
|
|
656
|
+
const parsed = BoardGeoItemSchema.safeParse(raw);
|
|
657
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
658
|
+
}
|
|
659
|
+
case "draw": {
|
|
660
|
+
const parsed = BoardDrawItemSchema.safeParse(raw);
|
|
661
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
662
|
+
}
|
|
663
|
+
case "arrow": {
|
|
664
|
+
const parsed = BoardArrowItemSchema.safeParse(raw);
|
|
665
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
666
|
+
}
|
|
667
|
+
case "frame": {
|
|
668
|
+
const parsed = BoardFrameItemSchema.safeParse(raw);
|
|
669
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
670
|
+
}
|
|
671
|
+
default: return makeUnknownItem(raw);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
function makeUnknownItem(raw) {
|
|
675
|
+
const record = raw && typeof raw === "object" ? raw : {};
|
|
676
|
+
const frameParsed = BoardFrameSchema.safeParse(record.frame);
|
|
677
|
+
const styleParsed = BoardItemStyleSchema.safeParse(record.style);
|
|
678
|
+
return {
|
|
679
|
+
id: typeof record.id === "string" && record.id ? record.id : "unknown",
|
|
680
|
+
type: UNKNOWN_BOARD_ITEM_TYPE,
|
|
681
|
+
frame: frameParsed.success ? frameParsed.data : {
|
|
682
|
+
x: 0,
|
|
683
|
+
y: 0,
|
|
684
|
+
width: 120,
|
|
685
|
+
height: 80,
|
|
686
|
+
rotation: 0
|
|
687
|
+
},
|
|
688
|
+
...typeof record.parentId === "string" || record.parentId === null ? { parentId: record.parentId } : {},
|
|
689
|
+
...record.locked === true ? { locked: true } : {},
|
|
690
|
+
...styleParsed.success && record.style ? { style: styleParsed.data } : {},
|
|
691
|
+
...record.metadata && typeof record.metadata === "object" ? { metadata: record.metadata } : {},
|
|
692
|
+
raw: record
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
z.object({
|
|
696
|
+
kind: z.literal(BOARD_DOCUMENT_KIND),
|
|
697
|
+
version: z.literal(1),
|
|
698
|
+
appearance: BoardAppearanceSchema.default({
|
|
699
|
+
theme: "clean",
|
|
700
|
+
background: { kind: "solid" },
|
|
701
|
+
grid: {
|
|
702
|
+
visible: false,
|
|
703
|
+
size: 24,
|
|
704
|
+
opacity: .12
|
|
705
|
+
},
|
|
706
|
+
mood: "clean"
|
|
707
|
+
}),
|
|
708
|
+
viewport: BoardViewportSchema,
|
|
709
|
+
items: z.array(BoardItemSchema),
|
|
710
|
+
/**
|
|
711
|
+
* Node relations. Separate from `items` because a connection has no frame of
|
|
712
|
+
* its own — its geometry is derived from the nodes it joins, so it is a
|
|
713
|
+
* relation over the item set rather than a member of it.
|
|
714
|
+
*
|
|
715
|
+
* Connections referencing a missing node are dropped on parse: a relation to
|
|
716
|
+
* nothing is not a relation, and keeping one would let an invisible dangling
|
|
717
|
+
* edge accumulate silently. Callers that need to know write through the
|
|
718
|
+
* transaction API, which reports the reference error instead.
|
|
719
|
+
*/
|
|
720
|
+
connections: z.array(BoardConnectionSchema).default([])
|
|
721
|
+
});
|
|
722
|
+
//#endregion
|
|
723
|
+
//#region ../protocol/dist/board-node.js
|
|
724
|
+
const BOARD_COLOR_IDS = [
|
|
725
|
+
"brand",
|
|
726
|
+
"neutral",
|
|
727
|
+
"black",
|
|
728
|
+
"white",
|
|
729
|
+
"blue",
|
|
730
|
+
"green",
|
|
731
|
+
"amber",
|
|
732
|
+
"violet",
|
|
733
|
+
"rose"
|
|
734
|
+
];
|
|
735
|
+
const BoardColorIdSchema = z.enum(BOARD_COLOR_IDS);
|
|
736
|
+
const BOARD_GEO_KINDS = [
|
|
737
|
+
"rectangle",
|
|
738
|
+
"rounded",
|
|
739
|
+
"ellipse",
|
|
740
|
+
"diamond",
|
|
741
|
+
"triangle"
|
|
742
|
+
];
|
|
743
|
+
const BoardGeoKindSchema = z.enum(BOARD_GEO_KINDS);
|
|
744
|
+
const BOARD_NATIVE_NODE_TYPES = [
|
|
745
|
+
"image",
|
|
746
|
+
"video",
|
|
747
|
+
"audio",
|
|
748
|
+
"file",
|
|
749
|
+
"task",
|
|
750
|
+
"text",
|
|
751
|
+
"geo",
|
|
752
|
+
"draw",
|
|
753
|
+
"arrow",
|
|
754
|
+
"frame"
|
|
755
|
+
];
|
|
756
|
+
const metadataSchema = z.record(z.string(), z.unknown());
|
|
757
|
+
const commonData = {
|
|
758
|
+
locked: z.boolean().optional(),
|
|
759
|
+
metadata: metadataSchema.optional()
|
|
760
|
+
};
|
|
761
|
+
const pointSchema = z.object({
|
|
762
|
+
x: z.number().finite(),
|
|
763
|
+
y: z.number().finite(),
|
|
764
|
+
p: z.number().finite().min(0).max(1).default(.5)
|
|
765
|
+
}).strict();
|
|
766
|
+
const worldPointSchema = z.object({
|
|
767
|
+
x: z.number().finite(),
|
|
768
|
+
y: z.number().finite()
|
|
769
|
+
}).strict();
|
|
770
|
+
const strokeSizeSchema = z.number().finite().min(1).max(64);
|
|
771
|
+
const mediaViewSchema = z.object({
|
|
772
|
+
title: z.string().optional(),
|
|
773
|
+
mimeType: z.string().optional(),
|
|
774
|
+
size: z.number().finite().nonnegative().optional(),
|
|
775
|
+
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
776
|
+
naturalWidth: z.number().finite().positive().optional(),
|
|
777
|
+
naturalHeight: z.number().finite().positive().optional()
|
|
778
|
+
}).strict();
|
|
779
|
+
const audioViewSchema = mediaViewSchema.extend({ durationMs: z.number().finite().nonnegative().optional() });
|
|
780
|
+
const dataSchemas = {
|
|
781
|
+
text: z.object({
|
|
782
|
+
...commonData,
|
|
783
|
+
text: z.string().default(""),
|
|
784
|
+
color: BoardColorIdSchema.default("neutral"),
|
|
785
|
+
fontSize: z.number().finite().min(2).max(512).default(24)
|
|
786
|
+
}).strict(),
|
|
787
|
+
geo: z.object({
|
|
788
|
+
...commonData,
|
|
789
|
+
geo: BoardGeoKindSchema.default("rectangle"),
|
|
790
|
+
text: z.string().default(""),
|
|
791
|
+
color: BoardColorIdSchema.default("brand"),
|
|
792
|
+
fillOpacity: z.number().finite().min(0).max(1).default(0)
|
|
793
|
+
}).strict(),
|
|
794
|
+
draw: z.object({
|
|
795
|
+
...commonData,
|
|
796
|
+
points: z.array(pointSchema).min(1),
|
|
797
|
+
color: BoardColorIdSchema.default("brand"),
|
|
798
|
+
size: strokeSizeSchema.default(4)
|
|
799
|
+
}).strict(),
|
|
800
|
+
arrow: z.object({
|
|
801
|
+
...commonData,
|
|
802
|
+
start: worldPointSchema,
|
|
803
|
+
end: worldPointSchema,
|
|
804
|
+
bend: z.number().finite().min(-.85).max(.85).default(0),
|
|
805
|
+
color: BoardColorIdSchema.default("brand"),
|
|
806
|
+
size: strokeSizeSchema.default(BOARD_ARROW_STROKE_SIZE),
|
|
807
|
+
arrowStart: z.boolean().default(false),
|
|
808
|
+
arrowEnd: z.boolean().default(true),
|
|
809
|
+
label: z.string().default("")
|
|
810
|
+
}).strict(),
|
|
811
|
+
frame: z.object({
|
|
812
|
+
...commonData,
|
|
813
|
+
label: z.string().default("Frame"),
|
|
814
|
+
color: BoardColorIdSchema.default("neutral")
|
|
815
|
+
}).strict(),
|
|
816
|
+
image: z.object({
|
|
817
|
+
...commonData,
|
|
818
|
+
crop: z.object({
|
|
819
|
+
x: z.number().finite().min(0).max(1),
|
|
820
|
+
y: z.number().finite().min(0).max(1),
|
|
821
|
+
w: z.number().finite().min(0).max(1),
|
|
822
|
+
h: z.number().finite().min(0).max(1)
|
|
823
|
+
}).strict().optional()
|
|
824
|
+
}).strict(),
|
|
825
|
+
video: z.object(commonData).strict(),
|
|
826
|
+
audio: z.object(commonData).strict(),
|
|
827
|
+
file: z.object(commonData).strict(),
|
|
828
|
+
task: z.object({
|
|
829
|
+
...commonData,
|
|
830
|
+
taskRunId: z.string().min(1)
|
|
831
|
+
}).strict()
|
|
832
|
+
};
|
|
833
|
+
const fileViewSchema = z.object({
|
|
834
|
+
title: z.string().optional(),
|
|
835
|
+
mimeType: z.string().optional(),
|
|
836
|
+
size: z.number().finite().nonnegative().optional(),
|
|
837
|
+
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
838
|
+
excerpt: z.string().optional(),
|
|
839
|
+
coverPath: z.string().optional(),
|
|
840
|
+
coverUrl: z.string().url().optional()
|
|
841
|
+
}).strict();
|
|
842
|
+
const taskViewSchema = BoardTaskSnapshotSchema;
|
|
843
|
+
const emptyViewSchema = z.object({}).strict();
|
|
844
|
+
const viewSchemas = {
|
|
845
|
+
image: mediaViewSchema,
|
|
846
|
+
video: mediaViewSchema,
|
|
847
|
+
audio: audioViewSchema,
|
|
848
|
+
file: fileViewSchema,
|
|
849
|
+
task: taskViewSchema,
|
|
850
|
+
text: emptyViewSchema,
|
|
851
|
+
geo: emptyViewSchema,
|
|
852
|
+
draw: emptyViewSchema,
|
|
853
|
+
arrow: emptyViewSchema,
|
|
854
|
+
frame: emptyViewSchema
|
|
855
|
+
};
|
|
856
|
+
const nodeEnvelopeSchema = z.object({
|
|
857
|
+
nodeId: z.string().min(1).max(160),
|
|
858
|
+
type: z.string().min(1).max(40),
|
|
859
|
+
parentId: z.string().min(1).max(160).nullable(),
|
|
860
|
+
orderKey: z.string().max(4096).nullable(),
|
|
861
|
+
x: z.number().finite(),
|
|
862
|
+
y: z.number().finite(),
|
|
863
|
+
width: z.number().finite().positive(),
|
|
864
|
+
height: z.number().finite().positive(),
|
|
865
|
+
rotation: z.number().finite(),
|
|
866
|
+
refKind: z.string().max(40).nullable(),
|
|
867
|
+
refPath: z.string().max(4096).nullable(),
|
|
868
|
+
refUrl: z.string().max(4096).nullable(),
|
|
869
|
+
view: z.record(z.string(), z.unknown()),
|
|
870
|
+
style: z.record(z.string(), z.unknown()),
|
|
871
|
+
data: z.record(z.string(), z.unknown())
|
|
872
|
+
}).strict();
|
|
873
|
+
function jsonSchema(schema) {
|
|
874
|
+
return z.toJSONSchema(schema);
|
|
875
|
+
}
|
|
876
|
+
jsonSchema(nodeEnvelopeSchema), Object.fromEntries(BOARD_NATIVE_NODE_TYPES.map((type) => [type, jsonSchema(dataSchemas[type])])), Object.fromEntries(BOARD_NATIVE_NODE_TYPES.map((type) => [type, jsonSchema(viewSchemas[type])]));
|
|
877
|
+
//#endregion
|
|
7
878
|
//#region ../protocol/dist/generation/catalog.js
|
|
8
879
|
function isGenerationModelHidden(model) {
|
|
9
880
|
return model.hidden === true;
|
|
@@ -21,14 +892,14 @@ var GenerationPolicyError = class extends Error {
|
|
|
21
892
|
this.name = "GenerationPolicyError";
|
|
22
893
|
}
|
|
23
894
|
};
|
|
24
|
-
function isRecord(value) {
|
|
895
|
+
function isRecord$1(value) {
|
|
25
896
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
26
897
|
}
|
|
27
898
|
function isPrimitiveEnumValue(value) {
|
|
28
899
|
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
29
900
|
}
|
|
30
901
|
function normalizeConstraint(value) {
|
|
31
|
-
if (!isRecord(value) || typeof value.kind !== "string") return null;
|
|
902
|
+
if (!isRecord$1(value) || typeof value.kind !== "string") return null;
|
|
32
903
|
if (value.kind === "enum") {
|
|
33
904
|
if (!Array.isArray(value.values) || value.values.length === 0 || !value.values.every(isPrimitiveEnumValue)) return null;
|
|
34
905
|
return {
|
|
@@ -58,7 +929,7 @@ function normalizeConstraint(value) {
|
|
|
58
929
|
return null;
|
|
59
930
|
}
|
|
60
931
|
function normalizeGenerationPolicy(value) {
|
|
61
|
-
if (!isRecord(value) || value.version !== 1) return null;
|
|
932
|
+
if (!isRecord$1(value) || value.version !== 1) return null;
|
|
62
933
|
if (value.mode === "auto") return {
|
|
63
934
|
version: 1,
|
|
64
935
|
mode: "auto"
|
|
@@ -66,10 +937,10 @@ function normalizeGenerationPolicy(value) {
|
|
|
66
937
|
if (value.mode !== "limited" || !Array.isArray(value.models) || value.models.length === 0) return null;
|
|
67
938
|
const models = [];
|
|
68
939
|
for (const item of value.models) {
|
|
69
|
-
if (!isRecord(item) || typeof item.model !== "string" || !item.model.trim()) return null;
|
|
940
|
+
if (!isRecord$1(item) || typeof item.model !== "string" || !item.model.trim()) return null;
|
|
70
941
|
const modelPolicy = { model: item.model.trim() };
|
|
71
942
|
if (item.parameters !== void 0) {
|
|
72
|
-
if (!isRecord(item.parameters)) return null;
|
|
943
|
+
if (!isRecord$1(item.parameters)) return null;
|
|
73
944
|
const parameters = {};
|
|
74
945
|
for (const [key, rawConstraint] of Object.entries(item.parameters)) {
|
|
75
946
|
if (!key.trim()) return null;
|
|
@@ -244,6 +1115,181 @@ function filterGenerationDeclarationsByPolicy(declarations, policy) {
|
|
|
244
1115
|
});
|
|
245
1116
|
}
|
|
246
1117
|
//#endregion
|
|
1118
|
+
//#region ../protocol/dist/public-identifiers.js
|
|
1119
|
+
const USERNAME_PATTERN = /^(?!-)(?!.*--)[a-z0-9-]{1,39}(?<!-)$/;
|
|
1120
|
+
const SPACE_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9_-]{0,78}[a-z0-9])?$/;
|
|
1121
|
+
/**
|
|
1122
|
+
* Platform-owned path segments that must not be newly assigned to public
|
|
1123
|
+
* identities. Existing stored values remain readable through parse helpers.
|
|
1124
|
+
*/
|
|
1125
|
+
const RESERVED_PLATFORM_PATH_SEGMENTS = Object.freeze([
|
|
1126
|
+
"admin",
|
|
1127
|
+
"api",
|
|
1128
|
+
"assets",
|
|
1129
|
+
"auth",
|
|
1130
|
+
"callback",
|
|
1131
|
+
"changelog",
|
|
1132
|
+
"docs",
|
|
1133
|
+
"explore",
|
|
1134
|
+
"invite",
|
|
1135
|
+
"landing",
|
|
1136
|
+
"login",
|
|
1137
|
+
"logout",
|
|
1138
|
+
"new",
|
|
1139
|
+
"org",
|
|
1140
|
+
"pricing",
|
|
1141
|
+
"pwa",
|
|
1142
|
+
"referrals",
|
|
1143
|
+
"sessions",
|
|
1144
|
+
"settings",
|
|
1145
|
+
"spaces",
|
|
1146
|
+
"static",
|
|
1147
|
+
"teams",
|
|
1148
|
+
"trending",
|
|
1149
|
+
"u",
|
|
1150
|
+
"user",
|
|
1151
|
+
"users",
|
|
1152
|
+
"work-auth",
|
|
1153
|
+
"app-auth"
|
|
1154
|
+
]);
|
|
1155
|
+
new Set(RESERVED_PLATFORM_PATH_SEGMENTS);
|
|
1156
|
+
[...RESERVED_PLATFORM_PATH_SEGMENTS];
|
|
1157
|
+
function parseUsername(value) {
|
|
1158
|
+
if (value === null || value === void 0) return null;
|
|
1159
|
+
const normalized = value.trim().toLowerCase();
|
|
1160
|
+
return USERNAME_PATTERN.test(normalized) ? normalized : null;
|
|
1161
|
+
}
|
|
1162
|
+
function parseSpaceSlug(value) {
|
|
1163
|
+
if (value === null || value === void 0) return null;
|
|
1164
|
+
const normalized = value.trim();
|
|
1165
|
+
return SPACE_SLUG_PATTERN.test(normalized) ? normalized : null;
|
|
1166
|
+
}
|
|
1167
|
+
//#endregion
|
|
1168
|
+
//#region ../protocol/dist/app-surface.js
|
|
1169
|
+
const APP_SURFACE_PROTOCOL = "cohub.app.surface";
|
|
1170
|
+
const APP_SURFACE_READY_TIMEOUT_MS = 1e4;
|
|
1171
|
+
const APP_SURFACE_REQUEST_TIMEOUT_MS = 15e3;
|
|
1172
|
+
const APP_COMPOSER_CHIP_KEY_MAX_LENGTH = 80;
|
|
1173
|
+
const APP_COMPOSER_CHIP_LABEL_MAX_LENGTH = 120;
|
|
1174
|
+
const APP_COMPOSER_CHIP_CONTENT_MAX_BYTES = 32 * 1024;
|
|
1175
|
+
const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1176
|
+
const isSurfaceEnvelope = (value) => isRecord(value) && value.protocol === "cohub.app.surface" && value.version === 1;
|
|
1177
|
+
const parseAppSurfaceReady = (value) => {
|
|
1178
|
+
if (!isSurfaceEnvelope(value) || value.type !== "ready") return null;
|
|
1179
|
+
const methods = Array.isArray(value.methods) ? value.methods.filter((method) => typeof method === "string" && Boolean(method)) : [];
|
|
1180
|
+
return {
|
|
1181
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1182
|
+
version: 1,
|
|
1183
|
+
type: "ready",
|
|
1184
|
+
methods
|
|
1185
|
+
};
|
|
1186
|
+
};
|
|
1187
|
+
const parseAppSurfaceResponse = (value) => {
|
|
1188
|
+
if (!isSurfaceEnvelope(value) || value.type !== "response") return null;
|
|
1189
|
+
if (typeof value.requestId !== "string" || !value.requestId) return null;
|
|
1190
|
+
const error = isRecord(value.error) ? {
|
|
1191
|
+
code: typeof value.error.code === "string" && value.error.code ? value.error.code : "surface_error",
|
|
1192
|
+
message: typeof value.error.message === "string" ? value.error.message : "App surface call failed"
|
|
1193
|
+
} : void 0;
|
|
1194
|
+
return {
|
|
1195
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1196
|
+
version: 1,
|
|
1197
|
+
type: "response",
|
|
1198
|
+
requestId: value.requestId,
|
|
1199
|
+
ok: value.ok === true,
|
|
1200
|
+
...error ? { error } : {}
|
|
1201
|
+
};
|
|
1202
|
+
};
|
|
1203
|
+
const parseComposerChipKey = (value) => {
|
|
1204
|
+
if (typeof value !== "string") return null;
|
|
1205
|
+
const key = value.trim();
|
|
1206
|
+
if (!key || key.length > 80) return null;
|
|
1207
|
+
return key;
|
|
1208
|
+
};
|
|
1209
|
+
const parseAppComposerChipSet = (value) => {
|
|
1210
|
+
if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.set" || !isRecord(value.chip)) return null;
|
|
1211
|
+
const key = parseComposerChipKey(value.chip.key);
|
|
1212
|
+
if (!key || typeof value.chip.label !== "string" || typeof value.chip.content !== "string") return null;
|
|
1213
|
+
const label = value.chip.label.trim();
|
|
1214
|
+
if (!label || label.length > 120) return null;
|
|
1215
|
+
if (!value.chip.content.trim()) return null;
|
|
1216
|
+
if (new TextEncoder().encode(value.chip.content).length > 32768) return null;
|
|
1217
|
+
return {
|
|
1218
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1219
|
+
version: 1,
|
|
1220
|
+
type: "composer.chip.set",
|
|
1221
|
+
chip: {
|
|
1222
|
+
key,
|
|
1223
|
+
label,
|
|
1224
|
+
content: value.chip.content
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1227
|
+
};
|
|
1228
|
+
const parseAppComposerChipClear = (value) => {
|
|
1229
|
+
if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.clear") return null;
|
|
1230
|
+
const key = parseComposerChipKey(value.key);
|
|
1231
|
+
return key ? {
|
|
1232
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1233
|
+
version: 1,
|
|
1234
|
+
type: "composer.chip.clear",
|
|
1235
|
+
key
|
|
1236
|
+
} : null;
|
|
1237
|
+
};
|
|
1238
|
+
const parseAppSurfaceRequest = (value) => {
|
|
1239
|
+
if (!isSurfaceEnvelope(value) || value.type !== "request") return null;
|
|
1240
|
+
if (typeof value.requestId !== "string" || !value.requestId) return null;
|
|
1241
|
+
if (typeof value.method !== "string" || !value.method) return null;
|
|
1242
|
+
const commandId = parseDesktopCommandId(value.commandId);
|
|
1243
|
+
if (!commandId) return null;
|
|
1244
|
+
return {
|
|
1245
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1246
|
+
version: 1,
|
|
1247
|
+
type: "request",
|
|
1248
|
+
requestId: value.requestId,
|
|
1249
|
+
method: value.method,
|
|
1250
|
+
...value.input === void 0 ? {} : { input: value.input },
|
|
1251
|
+
commandId
|
|
1252
|
+
};
|
|
1253
|
+
};
|
|
1254
|
+
const buildAppSurfaceReady = (methods) => ({
|
|
1255
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1256
|
+
version: 1,
|
|
1257
|
+
type: "ready",
|
|
1258
|
+
methods: [...methods]
|
|
1259
|
+
});
|
|
1260
|
+
const buildAppSurfaceRequest = (input) => ({
|
|
1261
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1262
|
+
version: 1,
|
|
1263
|
+
type: "request",
|
|
1264
|
+
...input
|
|
1265
|
+
});
|
|
1266
|
+
const buildAppSurfaceResponse = (input) => ({
|
|
1267
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1268
|
+
version: 1,
|
|
1269
|
+
type: "response",
|
|
1270
|
+
...input
|
|
1271
|
+
});
|
|
1272
|
+
const buildAppComposerChipSet = (chip) => ({
|
|
1273
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1274
|
+
version: 1,
|
|
1275
|
+
type: "composer.chip.set",
|
|
1276
|
+
chip
|
|
1277
|
+
});
|
|
1278
|
+
const buildAppComposerChipClear = (key) => ({
|
|
1279
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1280
|
+
version: 1,
|
|
1281
|
+
type: "composer.chip.clear",
|
|
1282
|
+
key
|
|
1283
|
+
});
|
|
1284
|
+
//#endregion
|
|
1285
|
+
//#region ../protocol/dist/app-runtime.js
|
|
1286
|
+
const APP_RUNTIME_PROTOCOL = "cohub.app.runtime";
|
|
1287
|
+
const buildAppRuntimeReady = () => ({
|
|
1288
|
+
protocol: APP_RUNTIME_PROTOCOL,
|
|
1289
|
+
version: 1,
|
|
1290
|
+
type: "ready"
|
|
1291
|
+
});
|
|
1292
|
+
//#endregion
|
|
247
1293
|
//#region src/apis/billing.ts
|
|
248
1294
|
var BillingApi = class {
|
|
249
1295
|
transport;
|
|
@@ -316,194 +1362,7 @@ var BillingApi = class {
|
|
|
316
1362
|
}
|
|
317
1363
|
};
|
|
318
1364
|
//#endregion
|
|
319
|
-
//#region
|
|
320
|
-
const contentBlockMetaSchema = z.record(z.string(), z.unknown());
|
|
321
|
-
const realtimeRoomSchema = z.string().regex(/^(space|user|board|room):[^:]+$/);
|
|
322
|
-
const contentBlockSchema = z.discriminatedUnion("type", [
|
|
323
|
-
z.object({
|
|
324
|
-
type: z.literal("text"),
|
|
325
|
-
text: z.string(),
|
|
326
|
-
_meta: contentBlockMetaSchema.optional()
|
|
327
|
-
}),
|
|
328
|
-
z.object({
|
|
329
|
-
type: z.literal("thinking"),
|
|
330
|
-
thinking: z.string(),
|
|
331
|
-
signature: z.string().optional(),
|
|
332
|
-
_meta: contentBlockMetaSchema.optional()
|
|
333
|
-
}),
|
|
334
|
-
z.object({
|
|
335
|
-
type: z.literal("image"),
|
|
336
|
-
source: z.union([z.object({
|
|
337
|
-
type: z.literal("url"),
|
|
338
|
-
url: z.string().url()
|
|
339
|
-
}), z.object({
|
|
340
|
-
type: z.literal("base64"),
|
|
341
|
-
media_type: z.string(),
|
|
342
|
-
data: z.string()
|
|
343
|
-
})]),
|
|
344
|
-
_meta: contentBlockMetaSchema.optional()
|
|
345
|
-
}),
|
|
346
|
-
z.object({
|
|
347
|
-
type: z.literal("shell_command"),
|
|
348
|
-
command: z.string(),
|
|
349
|
-
rawText: z.string(),
|
|
350
|
-
_meta: contentBlockMetaSchema.optional()
|
|
351
|
-
}),
|
|
352
|
-
z.object({
|
|
353
|
-
type: z.literal("tool_use"),
|
|
354
|
-
id: z.string(),
|
|
355
|
-
name: z.string(),
|
|
356
|
-
input: z.record(z.string(), z.unknown()),
|
|
357
|
-
_meta: contentBlockMetaSchema.optional()
|
|
358
|
-
}),
|
|
359
|
-
z.object({
|
|
360
|
-
type: z.literal("tool_result"),
|
|
361
|
-
tool_use_id: z.string(),
|
|
362
|
-
content: z.union([z.string(), z.array(z.unknown())]),
|
|
363
|
-
is_error: z.boolean().optional(),
|
|
364
|
-
_meta: contentBlockMetaSchema.optional()
|
|
365
|
-
}),
|
|
366
|
-
z.object({
|
|
367
|
-
type: z.literal("system_note"),
|
|
368
|
-
note_type: z.enum([
|
|
369
|
-
"session_created",
|
|
370
|
-
"forked",
|
|
371
|
-
"compacted",
|
|
372
|
-
"info"
|
|
373
|
-
]),
|
|
374
|
-
text: z.string(),
|
|
375
|
-
_meta: contentBlockMetaSchema.optional()
|
|
376
|
-
})
|
|
377
|
-
]);
|
|
378
|
-
z.discriminatedUnion("type", [
|
|
379
|
-
z.object({
|
|
380
|
-
type: z.literal("auth"),
|
|
381
|
-
requestId: z.string().optional(),
|
|
382
|
-
payload: z.object({
|
|
383
|
-
token: z.string().min(1),
|
|
384
|
-
capabilities: z.array(z.string().min(1)).optional()
|
|
385
|
-
})
|
|
386
|
-
}),
|
|
387
|
-
z.object({
|
|
388
|
-
type: z.literal("subscribe"),
|
|
389
|
-
requestId: z.string().optional(),
|
|
390
|
-
payload: z.object({ rooms: z.array(realtimeRoomSchema).min(1) })
|
|
391
|
-
}),
|
|
392
|
-
z.object({
|
|
393
|
-
type: z.literal("unsubscribe"),
|
|
394
|
-
requestId: z.string().optional(),
|
|
395
|
-
payload: z.object({ rooms: z.array(realtimeRoomSchema).min(1) })
|
|
396
|
-
}),
|
|
397
|
-
z.object({
|
|
398
|
-
type: z.literal("session.message.create"),
|
|
399
|
-
requestId: z.string().optional(),
|
|
400
|
-
payload: z.object({
|
|
401
|
-
spaceId: z.string().uuid(),
|
|
402
|
-
sessionId: z.string().uuid(),
|
|
403
|
-
clientMessageId: z.string().optional(),
|
|
404
|
-
content: z.array(contentBlockSchema).min(1),
|
|
405
|
-
model: z.string().optional(),
|
|
406
|
-
provider: z.string().optional(),
|
|
407
|
-
thinkingLevel: z.enum([
|
|
408
|
-
"off",
|
|
409
|
-
"minimal",
|
|
410
|
-
"low",
|
|
411
|
-
"medium",
|
|
412
|
-
"high",
|
|
413
|
-
"xhigh",
|
|
414
|
-
"max"
|
|
415
|
-
]).optional()
|
|
416
|
-
})
|
|
417
|
-
}),
|
|
418
|
-
z.object({
|
|
419
|
-
type: z.literal("presence.update"),
|
|
420
|
-
requestId: z.string().optional(),
|
|
421
|
-
payload: z.object({
|
|
422
|
-
spaceId: z.string().uuid(),
|
|
423
|
-
meta: z.record(z.string(), z.unknown()).nullable().optional()
|
|
424
|
-
})
|
|
425
|
-
}),
|
|
426
|
-
z.object({
|
|
427
|
-
type: z.literal("board.awareness.update"),
|
|
428
|
-
requestId: z.string().optional(),
|
|
429
|
-
payload: BoardAwarenessClientPayloadSchema
|
|
430
|
-
}),
|
|
431
|
-
z.object({
|
|
432
|
-
type: z.literal("realtime.room.join"),
|
|
433
|
-
requestId: z.string().optional(),
|
|
434
|
-
payload: z.object({
|
|
435
|
-
roomId: z.string().uuid(),
|
|
436
|
-
ticket: z.string().min(1)
|
|
437
|
-
})
|
|
438
|
-
}),
|
|
439
|
-
z.object({
|
|
440
|
-
type: z.literal("realtime.room.publish"),
|
|
441
|
-
requestId: z.string().optional(),
|
|
442
|
-
payload: z.object({
|
|
443
|
-
roomId: z.string().uuid(),
|
|
444
|
-
event: z.string().regex(REALTIME_ROOM_EVENT_NAME_PATTERN),
|
|
445
|
-
data: z.unknown(),
|
|
446
|
-
clientEventId: z.string().max(128).optional()
|
|
447
|
-
})
|
|
448
|
-
}),
|
|
449
|
-
z.object({
|
|
450
|
-
type: z.literal("realtime.room.leave"),
|
|
451
|
-
requestId: z.string().optional(),
|
|
452
|
-
payload: z.object({ roomId: z.string().uuid() })
|
|
453
|
-
}),
|
|
454
|
-
z.object({
|
|
455
|
-
type: z.literal("realtime.room.presence.update"),
|
|
456
|
-
requestId: z.string().optional(),
|
|
457
|
-
payload: z.object({
|
|
458
|
-
roomId: z.string().uuid(),
|
|
459
|
-
presence: z.record(z.string(), z.unknown()).nullable()
|
|
460
|
-
})
|
|
461
|
-
}),
|
|
462
|
-
z.object({
|
|
463
|
-
type: z.literal("ping"),
|
|
464
|
-
requestId: z.string().optional(),
|
|
465
|
-
payload: z.record(z.string(), z.unknown()).optional()
|
|
466
|
-
}),
|
|
467
|
-
z.object({
|
|
468
|
-
type: z.literal("ack"),
|
|
469
|
-
requestId: z.string().optional(),
|
|
470
|
-
payload: z.object({ eventId: z.string().optional() }).optional()
|
|
471
|
-
})
|
|
472
|
-
]);
|
|
473
|
-
z.object({
|
|
474
|
-
id: z.string(),
|
|
475
|
-
timestamp: z.number(),
|
|
476
|
-
domain: z.enum(REALTIME_DOMAINS),
|
|
477
|
-
type: z.string(),
|
|
478
|
-
requestId: z.string().nullable().optional(),
|
|
479
|
-
spaceId: z.string().nullable().optional(),
|
|
480
|
-
sessionId: z.string().nullable().optional(),
|
|
481
|
-
rooms: z.array(realtimeRoomSchema).optional(),
|
|
482
|
-
payload: z.record(z.string(), z.unknown())
|
|
483
|
-
});
|
|
484
|
-
z.discriminatedUnion("t", [z.object({
|
|
485
|
-
t: z.literal("d"),
|
|
486
|
-
sid: z.string().min(1),
|
|
487
|
-
s: z.number().int().nonnegative(),
|
|
488
|
-
b: z.number().int().nonnegative(),
|
|
489
|
-
v: z.unknown()
|
|
490
|
-
}), z.object({
|
|
491
|
-
t: z.literal("p"),
|
|
492
|
-
sid: z.string().min(1),
|
|
493
|
-
s: z.number().int().nonnegative(),
|
|
494
|
-
b: z.number().int().nonnegative(),
|
|
495
|
-
o: z.enum([
|
|
496
|
-
"append",
|
|
497
|
-
"replace",
|
|
498
|
-
"add",
|
|
499
|
-
"merge",
|
|
500
|
-
"remove"
|
|
501
|
-
]),
|
|
502
|
-
p: z.string().min(1),
|
|
503
|
-
v: z.unknown().optional()
|
|
504
|
-
})]);
|
|
505
|
-
//#endregion
|
|
506
|
-
//#region src/apis/work-realtime.ts
|
|
1365
|
+
//#region src/apis/app-realtime.ts
|
|
507
1366
|
const randomId = () => globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
508
1367
|
const textEncoder = new TextEncoder();
|
|
509
1368
|
const byteLength = (text) => textEncoder.encode(text).byteLength;
|
|
@@ -527,7 +1386,7 @@ const isRoomEvent = (event, roomId) => {
|
|
|
527
1386
|
if (event.domain !== "room") return false;
|
|
528
1387
|
return event.payload.roomId === roomId || event.rooms?.includes(`room:${roomId}`);
|
|
529
1388
|
};
|
|
530
|
-
var
|
|
1389
|
+
var AppRoom = class {
|
|
531
1390
|
websocket;
|
|
532
1391
|
admission;
|
|
533
1392
|
id;
|
|
@@ -719,7 +1578,7 @@ var WorkRoom = class {
|
|
|
719
1578
|
if (this.state === "expired") return;
|
|
720
1579
|
this.setState("closed");
|
|
721
1580
|
this.dispose();
|
|
722
|
-
console.warn("[Cohub
|
|
1581
|
+
console.warn("[Cohub AppRoom] failed to rejoin room", error);
|
|
723
1582
|
}
|
|
724
1583
|
}
|
|
725
1584
|
async joinOverWebsocket(isReconnect) {
|
|
@@ -884,7 +1743,7 @@ var WorkRoom = class {
|
|
|
884
1743
|
this.expiryTimer = null;
|
|
885
1744
|
}
|
|
886
1745
|
};
|
|
887
|
-
var
|
|
1746
|
+
var AppRealtimeApi = class {
|
|
888
1747
|
transport;
|
|
889
1748
|
websocket;
|
|
890
1749
|
getContext;
|
|
@@ -894,7 +1753,7 @@ var WorkRealtimeApi = class {
|
|
|
894
1753
|
this.getContext = getContext;
|
|
895
1754
|
}
|
|
896
1755
|
async createRoom(input = {}) {
|
|
897
|
-
const admission = await this.transport.request(`/api/
|
|
1756
|
+
const admission = await this.transport.request(`/api/apps/${encodeURIComponent(await this.requireAppId())}/realtime/rooms`, {
|
|
898
1757
|
method: "POST",
|
|
899
1758
|
headers: { "Content-Type": "application/json" },
|
|
900
1759
|
body: JSON.stringify(input)
|
|
@@ -902,20 +1761,20 @@ var WorkRealtimeApi = class {
|
|
|
902
1761
|
return this.openRoom(admission);
|
|
903
1762
|
}
|
|
904
1763
|
async joinRoom(input) {
|
|
905
|
-
const admission = await this.transport.request(`/api/
|
|
1764
|
+
const admission = await this.transport.request(`/api/apps/${encodeURIComponent(await this.requireAppId())}/realtime/rooms/join`, {
|
|
906
1765
|
method: "POST",
|
|
907
1766
|
headers: { "Content-Type": "application/json" },
|
|
908
1767
|
body: JSON.stringify(input)
|
|
909
1768
|
});
|
|
910
1769
|
return this.openRoom(admission);
|
|
911
1770
|
}
|
|
912
|
-
async
|
|
1771
|
+
async requireAppId() {
|
|
913
1772
|
const context = await this.getContext();
|
|
914
|
-
if (!context?.
|
|
915
|
-
return context.
|
|
1773
|
+
if (!context?.app?.id) throw new Error("App context is unavailable — not running inside a published app runtime.");
|
|
1774
|
+
return context.app.id;
|
|
916
1775
|
}
|
|
917
1776
|
async openRoom(admission) {
|
|
918
|
-
const room = new
|
|
1777
|
+
const room = new AppRoom(this.websocket, admission);
|
|
919
1778
|
try {
|
|
920
1779
|
await room.connect();
|
|
921
1780
|
return room;
|
|
@@ -925,11 +1784,15 @@ var WorkRealtimeApi = class {
|
|
|
925
1784
|
}
|
|
926
1785
|
}
|
|
927
1786
|
};
|
|
1787
|
+
/** @deprecated Use `AppRealtimeApi`. */
|
|
1788
|
+
var WorkRealtimeApi = class extends AppRealtimeApi {};
|
|
1789
|
+
/** @deprecated Use `AppRoom`. */
|
|
1790
|
+
var WorkRoom = class extends AppRoom {};
|
|
928
1791
|
//#endregion
|
|
929
|
-
//#region src/
|
|
1792
|
+
//#region src/app-surface.ts
|
|
930
1793
|
/**
|
|
931
|
-
* Explicit app origins, not a Cohub suffix match:
|
|
932
|
-
* served from Cohub subdomains, so a suffix match would let one
|
|
1794
|
+
* Explicit app origins, not a Cohub suffix match: apps themselves are
|
|
1795
|
+
* served from Cohub subdomains, so a suffix match would let one app call into
|
|
933
1796
|
* another and turn a subdomain takeover into surface access.
|
|
934
1797
|
*/
|
|
935
1798
|
const COHUB_APP_ORIGINS = [
|
|
@@ -952,7 +1815,7 @@ const resolveEmbedderOrigin = () => {
|
|
|
952
1815
|
return null;
|
|
953
1816
|
}
|
|
954
1817
|
};
|
|
955
|
-
var
|
|
1818
|
+
var AppSurfaceApi = class {
|
|
956
1819
|
handlers = /* @__PURE__ */ new Map();
|
|
957
1820
|
listening = false;
|
|
958
1821
|
allowedOrigins = null;
|
|
@@ -970,7 +1833,7 @@ var WorkSurfaceApi = class {
|
|
|
970
1833
|
}
|
|
971
1834
|
handle(method, handler) {
|
|
972
1835
|
const name = method.trim();
|
|
973
|
-
if (!name) throw new Error("
|
|
1836
|
+
if (!name) throw new Error("App surface method name is required");
|
|
974
1837
|
this.handlers.set(name, handler);
|
|
975
1838
|
this.start();
|
|
976
1839
|
this.announce();
|
|
@@ -985,17 +1848,17 @@ var WorkSurfaceApi = class {
|
|
|
985
1848
|
return [...this.handlers.keys()];
|
|
986
1849
|
}
|
|
987
1850
|
setComposerChip(chip) {
|
|
988
|
-
const message =
|
|
989
|
-
if (!message) throw new Error("Invalid
|
|
1851
|
+
const message = parseAppComposerChipSet(buildAppComposerChipSet(chip));
|
|
1852
|
+
if (!message) throw new Error("Invalid App composer chip");
|
|
990
1853
|
this.post(message);
|
|
991
1854
|
}
|
|
992
1855
|
clearComposerChip(key) {
|
|
993
|
-
const message =
|
|
994
|
-
if (!message) throw new Error("Invalid
|
|
1856
|
+
const message = parseAppComposerChipClear(buildAppComposerChipClear(key));
|
|
1857
|
+
if (!message) throw new Error("Invalid App composer chip key");
|
|
995
1858
|
this.post(message);
|
|
996
1859
|
}
|
|
997
1860
|
announce() {
|
|
998
|
-
this.post(
|
|
1861
|
+
this.post(buildAppSurfaceReady(this.methods));
|
|
999
1862
|
}
|
|
1000
1863
|
isTrusted(origin) {
|
|
1001
1864
|
if (!origin || origin === "null") return false;
|
|
@@ -1016,7 +1879,7 @@ var WorkSurfaceApi = class {
|
|
|
1016
1879
|
onMessage = (event) => {
|
|
1017
1880
|
if (typeof window === "undefined" || event.source !== window.parent) return;
|
|
1018
1881
|
if (!this.isTrusted(event.origin)) return;
|
|
1019
|
-
const request =
|
|
1882
|
+
const request = parseAppSurfaceRequest(event.data);
|
|
1020
1883
|
if (!request) return;
|
|
1021
1884
|
const commandId = request.commandId;
|
|
1022
1885
|
if (!commandId) return;
|
|
@@ -1026,24 +1889,24 @@ var WorkSurfaceApi = class {
|
|
|
1026
1889
|
async dispatch(requestId, method, input, commandId) {
|
|
1027
1890
|
const handler = this.handlers.get(method);
|
|
1028
1891
|
if (!handler) {
|
|
1029
|
-
this.post(
|
|
1892
|
+
this.post(buildAppSurfaceResponse({
|
|
1030
1893
|
requestId,
|
|
1031
1894
|
ok: false,
|
|
1032
1895
|
error: {
|
|
1033
1896
|
code: "method_not_found",
|
|
1034
|
-
message: `This
|
|
1897
|
+
message: `This app does not expose "${method}".`
|
|
1035
1898
|
}
|
|
1036
1899
|
}));
|
|
1037
1900
|
return;
|
|
1038
1901
|
}
|
|
1039
1902
|
try {
|
|
1040
1903
|
await handler(input, { commandId });
|
|
1041
|
-
this.post(
|
|
1904
|
+
this.post(buildAppSurfaceResponse({
|
|
1042
1905
|
requestId,
|
|
1043
1906
|
ok: true
|
|
1044
1907
|
}));
|
|
1045
1908
|
} catch (error) {
|
|
1046
|
-
this.post(
|
|
1909
|
+
this.post(buildAppSurfaceResponse({
|
|
1047
1910
|
requestId,
|
|
1048
1911
|
ok: false,
|
|
1049
1912
|
error: {
|
|
@@ -1062,8 +1925,10 @@ var WorkSurfaceApi = class {
|
|
|
1062
1925
|
} catch {}
|
|
1063
1926
|
}
|
|
1064
1927
|
};
|
|
1928
|
+
/** @deprecated Use `AppSurfaceApi`. */
|
|
1929
|
+
var WorkSurfaceApi = class extends AppSurfaceApi {};
|
|
1065
1930
|
//#endregion
|
|
1066
|
-
//#region src/
|
|
1931
|
+
//#region src/app-runtime.ts
|
|
1067
1932
|
const isBrowser$1 = () => typeof window !== "undefined" && typeof window.parent !== "undefined";
|
|
1068
1933
|
const hasParent = () => isBrowser$1() && window.parent !== window;
|
|
1069
1934
|
const getParentOrigin = () => {
|
|
@@ -1079,11 +1944,39 @@ const getParentOrigin = () => {
|
|
|
1079
1944
|
const generateRequestId = () => globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1080
1945
|
/**
|
|
1081
1946
|
* Bridge-mode transport: posts messages to `window.parent` (the Cohub host
|
|
1082
|
-
* embedding the
|
|
1947
|
+
* embedding the app in an iframe) and listens for the matching reply.
|
|
1083
1948
|
* Behaviorally identical to the previous module-level `request()` helper.
|
|
1084
1949
|
*/
|
|
1085
1950
|
var ParentBridgeTransport = class {
|
|
1086
1951
|
trustedParentOrigin = null;
|
|
1952
|
+
contextListeners = /* @__PURE__ */ new Set();
|
|
1953
|
+
contextListener = null;
|
|
1954
|
+
subscribeContextChanged(listener) {
|
|
1955
|
+
if (!hasParent()) return () => {};
|
|
1956
|
+
this.contextListeners.add(listener);
|
|
1957
|
+
if (!this.contextListener) {
|
|
1958
|
+
this.contextListener = (event) => {
|
|
1959
|
+
if (event.source !== window.parent) return;
|
|
1960
|
+
const parentOrigin = this.trustedParentOrigin ?? getParentOrigin();
|
|
1961
|
+
if (parentOrigin && event.origin !== parentOrigin) return;
|
|
1962
|
+
const data = event.data;
|
|
1963
|
+
if (data?.type !== "cohub.app.context.changed" || !data.context) return;
|
|
1964
|
+
for (const current of this.contextListeners) current(data.context);
|
|
1965
|
+
};
|
|
1966
|
+
window.addEventListener("message", this.contextListener);
|
|
1967
|
+
const parentOrigin = this.trustedParentOrigin ?? getParentOrigin();
|
|
1968
|
+
if (parentOrigin) try {
|
|
1969
|
+
window.parent.postMessage(buildAppRuntimeReady(), parentOrigin);
|
|
1970
|
+
} catch {}
|
|
1971
|
+
}
|
|
1972
|
+
return () => {
|
|
1973
|
+
this.contextListeners.delete(listener);
|
|
1974
|
+
if (this.contextListeners.size === 0 && this.contextListener) {
|
|
1975
|
+
window.removeEventListener("message", this.contextListener);
|
|
1976
|
+
this.contextListener = null;
|
|
1977
|
+
}
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1087
1980
|
request(message, options) {
|
|
1088
1981
|
const timeoutMs = options?.timeoutMs ?? 1200;
|
|
1089
1982
|
const retryIntervalMs = options?.retryIntervalMs;
|
|
@@ -1118,7 +2011,7 @@ var ParentBridgeTransport = class {
|
|
|
1118
2011
|
if (!data || data.requestId !== requestId) return;
|
|
1119
2012
|
cleanup();
|
|
1120
2013
|
this.trustedParentOrigin = event.origin;
|
|
1121
|
-
if (data.type === "cohub.
|
|
2014
|
+
if (data.type === "cohub.app.error") {
|
|
1122
2015
|
reject(new Error(data.message));
|
|
1123
2016
|
return;
|
|
1124
2017
|
}
|
|
@@ -1131,64 +2024,64 @@ var ParentBridgeTransport = class {
|
|
|
1131
2024
|
}
|
|
1132
2025
|
};
|
|
1133
2026
|
/**
|
|
1134
|
-
* Broker-mode transport for standalone-deployed
|
|
2027
|
+
* Broker-mode transport for standalone-deployed apps. Opens a popup window to
|
|
1135
2028
|
* the Cohub auth broker page, performs a ready-handshake, sends the request via
|
|
1136
2029
|
* postMessage, and resolves with the broker's response. The popup is closed
|
|
1137
2030
|
* after a single request is fulfilled (one-shot, per §7.2 of the plan).
|
|
1138
2031
|
*
|
|
1139
2032
|
* Non-interactive messages (`context`, `checkout-state`) are answered locally
|
|
1140
|
-
* without opening a popup — the
|
|
1141
|
-
* checkout state is not available on the
|
|
2033
|
+
* without opening a popup — the app already knows its own appId, and
|
|
2034
|
+
* checkout state is not available on the app's own origin in broker mode.
|
|
1142
2035
|
*/
|
|
1143
2036
|
var PopupBrokerTransport = class {
|
|
1144
2037
|
brokerOrigin;
|
|
1145
|
-
|
|
1146
|
-
|
|
2038
|
+
appId;
|
|
2039
|
+
getAppId;
|
|
1147
2040
|
constructor(config) {
|
|
1148
2041
|
this.brokerOrigin = config.brokerOrigin;
|
|
1149
|
-
this.
|
|
1150
|
-
this.
|
|
1151
|
-
if (!this.
|
|
2042
|
+
this.appId = config.appId;
|
|
2043
|
+
this.getAppId = config.getAppId;
|
|
2044
|
+
if (!this.appId && this.getAppId) this.getAppId();
|
|
1152
2045
|
}
|
|
1153
|
-
|
|
1154
|
-
if (this.
|
|
1155
|
-
if (this.
|
|
2046
|
+
resolveAppId() {
|
|
2047
|
+
if (this.appId) return Promise.resolve(this.appId);
|
|
2048
|
+
if (this.getAppId) return this.getAppId();
|
|
1156
2049
|
return Promise.resolve(null);
|
|
1157
2050
|
}
|
|
1158
2051
|
async request(message, options) {
|
|
1159
|
-
if (message.type === "cohub.
|
|
1160
|
-
type: "cohub.
|
|
2052
|
+
if (message.type === "cohub.app.context") return {
|
|
2053
|
+
type: "cohub.app.context.result",
|
|
1161
2054
|
context: {
|
|
1162
|
-
|
|
1163
|
-
id: await this.
|
|
2055
|
+
app: {
|
|
2056
|
+
id: await this.resolveAppId() ?? "",
|
|
1164
2057
|
slug: "",
|
|
1165
2058
|
url: null
|
|
1166
2059
|
},
|
|
1167
2060
|
space: { id: "" },
|
|
1168
2061
|
permissions: {
|
|
1169
2062
|
scopes: [],
|
|
1170
|
-
|
|
2063
|
+
appScopes: [],
|
|
1171
2064
|
viewerScopes: []
|
|
1172
2065
|
}
|
|
1173
2066
|
}
|
|
1174
2067
|
};
|
|
1175
|
-
if (message.type === "cohub.
|
|
1176
|
-
type: "cohub.
|
|
2068
|
+
if (message.type === "cohub.app.checkout-state") return {
|
|
2069
|
+
type: "cohub.app.checkout-state.result",
|
|
1177
2070
|
status: null,
|
|
1178
2071
|
orderId: null
|
|
1179
2072
|
};
|
|
1180
2073
|
const timeoutMs = options?.timeoutMs ?? 12e4;
|
|
1181
2074
|
const requestId = generateRequestId();
|
|
1182
|
-
const
|
|
1183
|
-
if (!
|
|
2075
|
+
const appId = await this.resolveAppId();
|
|
2076
|
+
if (!appId) throw new Error("Unable to resolve the app id for broker mode. Provide `appId` or a valid slug triple (ownerUsername, spaceSlug, appSlug).");
|
|
1184
2077
|
return new Promise((resolve, reject) => {
|
|
1185
2078
|
if (typeof window === "undefined" || typeof window.open !== "function") {
|
|
1186
2079
|
resolve(null);
|
|
1187
2080
|
return;
|
|
1188
2081
|
}
|
|
1189
|
-
const
|
|
1190
|
-
const brokerUrl = `${this.brokerOrigin}/
|
|
1191
|
-
const popup = window.open(brokerUrl, "cohub-
|
|
2082
|
+
const appOrigin = window.location.origin;
|
|
2083
|
+
const brokerUrl = `${this.brokerOrigin}/app-auth?app=${encodeURIComponent(appId)}&origin=${encodeURIComponent(appOrigin)}`;
|
|
2084
|
+
const popup = window.open(brokerUrl, "cohub-app-auth", "popup,width=480,height=640");
|
|
1192
2085
|
if (!popup) {
|
|
1193
2086
|
reject(/* @__PURE__ */ new Error("Failed to open authorization window. Please allow popups for this site."));
|
|
1194
2087
|
return;
|
|
@@ -1219,7 +2112,7 @@ var PopupBrokerTransport = class {
|
|
|
1219
2112
|
if (event.origin !== this.brokerOrigin) return;
|
|
1220
2113
|
const data = event.data;
|
|
1221
2114
|
if (!data) return;
|
|
1222
|
-
if (data.type === "cohub.
|
|
2115
|
+
if (data.type === "cohub.app.broker.ready" && !ready) {
|
|
1223
2116
|
ready = true;
|
|
1224
2117
|
try {
|
|
1225
2118
|
popup.postMessage({
|
|
@@ -1233,7 +2126,7 @@ var PopupBrokerTransport = class {
|
|
|
1233
2126
|
}
|
|
1234
2127
|
if (data.requestId !== requestId) return;
|
|
1235
2128
|
finish(() => {
|
|
1236
|
-
if (data.type === "cohub.
|
|
2129
|
+
if (data.type === "cohub.app.error") {
|
|
1237
2130
|
reject(new Error(data.message));
|
|
1238
2131
|
return;
|
|
1239
2132
|
}
|
|
@@ -1250,30 +2143,30 @@ var PopupBrokerTransport = class {
|
|
|
1250
2143
|
});
|
|
1251
2144
|
}
|
|
1252
2145
|
};
|
|
1253
|
-
const TOKEN_STORAGE_PREFIX = "cohub:
|
|
1254
|
-
const AUTHORIZED_SCOPES_STORAGE_PREFIX = "cohub:
|
|
1255
|
-
var
|
|
2146
|
+
const TOKEN_STORAGE_PREFIX = "cohub:app-token";
|
|
2147
|
+
const AUTHORIZED_SCOPES_STORAGE_PREFIX = "cohub:app-auth-scopes";
|
|
2148
|
+
var AppRuntimeApi = class {
|
|
1256
2149
|
token = null;
|
|
1257
2150
|
transport;
|
|
1258
2151
|
tokenStorageKey;
|
|
1259
2152
|
scopesStorageKey;
|
|
1260
|
-
|
|
2153
|
+
appIdResolver;
|
|
1261
2154
|
/** Ensures storage keys are resolved (via slug lookup) at most once. */
|
|
1262
2155
|
storageKeysReady;
|
|
1263
2156
|
/** Scopes previously granted via requestAuthorization, retained so token
|
|
1264
2157
|
* refreshes can re-authorize (preserving viewerScopes) instead of falling
|
|
1265
|
-
* back to a base session token that only carries
|
|
2158
|
+
* back to a base session token that only carries appScopes. */
|
|
1266
2159
|
authorizedScopes = null;
|
|
1267
|
-
constructor(transport = new ParentBridgeTransport(),
|
|
2160
|
+
constructor(transport = new ParentBridgeTransport(), appId, appIdResolver) {
|
|
1268
2161
|
this.transport = transport;
|
|
1269
|
-
this.
|
|
1270
|
-
if (
|
|
1271
|
-
this.tokenStorageKey = `${TOKEN_STORAGE_PREFIX}:${
|
|
1272
|
-
this.scopesStorageKey = `${AUTHORIZED_SCOPES_STORAGE_PREFIX}:${
|
|
2162
|
+
this.appIdResolver = appIdResolver;
|
|
2163
|
+
if (appId) {
|
|
2164
|
+
this.tokenStorageKey = `${TOKEN_STORAGE_PREFIX}:${appId}`;
|
|
2165
|
+
this.scopesStorageKey = `${AUTHORIZED_SCOPES_STORAGE_PREFIX}:${appId}`;
|
|
1273
2166
|
this.storageKeysReady = Promise.resolve();
|
|
1274
2167
|
this.token = this.readStoredToken();
|
|
1275
2168
|
this.authorizedScopes = this.readStoredScopes();
|
|
1276
|
-
} else if (
|
|
2169
|
+
} else if (appIdResolver) {
|
|
1277
2170
|
this.tokenStorageKey = null;
|
|
1278
2171
|
this.scopesStorageKey = null;
|
|
1279
2172
|
this.storageKeysReady = null;
|
|
@@ -1284,17 +2177,17 @@ var WorkRuntimeApi = class {
|
|
|
1284
2177
|
}
|
|
1285
2178
|
}
|
|
1286
2179
|
/**
|
|
1287
|
-
* Resolves the localStorage keys once the
|
|
2180
|
+
* Resolves the localStorage keys once the appId is known. When the appId is
|
|
1288
2181
|
* only available via slug reverse lookup, this performs the lookup on first
|
|
1289
|
-
* use and then restores any cached token/scopes for that
|
|
2182
|
+
* use and then restores any cached token/scopes for that appId.
|
|
1290
2183
|
*/
|
|
1291
2184
|
ensureStorageKeys() {
|
|
1292
2185
|
if (this.storageKeysReady) return this.storageKeysReady;
|
|
1293
2186
|
this.storageKeysReady = (async () => {
|
|
1294
|
-
const
|
|
1295
|
-
if (
|
|
1296
|
-
this.tokenStorageKey = `${TOKEN_STORAGE_PREFIX}:${
|
|
1297
|
-
this.scopesStorageKey = `${AUTHORIZED_SCOPES_STORAGE_PREFIX}:${
|
|
2187
|
+
const appId = this.appIdResolver ? await this.appIdResolver() : null;
|
|
2188
|
+
if (appId) {
|
|
2189
|
+
this.tokenStorageKey = `${TOKEN_STORAGE_PREFIX}:${appId}`;
|
|
2190
|
+
this.scopesStorageKey = `${AUTHORIZED_SCOPES_STORAGE_PREFIX}:${appId}`;
|
|
1298
2191
|
const stored = this.readStoredToken();
|
|
1299
2192
|
if (stored && !this.token) this.token = stored;
|
|
1300
2193
|
const storedScopes = this.readStoredScopes();
|
|
@@ -1337,11 +2230,14 @@ var WorkRuntimeApi = class {
|
|
|
1337
2230
|
} catch {}
|
|
1338
2231
|
}
|
|
1339
2232
|
async context() {
|
|
1340
|
-
return (await this.transport.request({ type: "cohub.
|
|
2233
|
+
return (await this.transport.request({ type: "cohub.app.context" }, {
|
|
1341
2234
|
timeoutMs: 8e3,
|
|
1342
2235
|
retryIntervalMs: 250
|
|
1343
2236
|
}))?.context ?? null;
|
|
1344
2237
|
}
|
|
2238
|
+
onContextChanged(listener) {
|
|
2239
|
+
return this.transport.subscribeContextChanged?.(listener) ?? (() => {});
|
|
2240
|
+
}
|
|
1345
2241
|
async getAccessToken(options) {
|
|
1346
2242
|
await this.ensureStorageKeys();
|
|
1347
2243
|
if (this.token && !options?.forceRefresh) return this.token;
|
|
@@ -1351,7 +2247,7 @@ var WorkRuntimeApi = class {
|
|
|
1351
2247
|
}
|
|
1352
2248
|
if (options?.forceRefresh && this.authorizedScopes && this.authorizedScopes.length > 0) {
|
|
1353
2249
|
const response = await this.transport.request({
|
|
1354
|
-
type: "cohub.
|
|
2250
|
+
type: "cohub.app.authorize",
|
|
1355
2251
|
scopes: this.authorizedScopes
|
|
1356
2252
|
}, { timeoutMs: 12e4 });
|
|
1357
2253
|
this.token = response?.token ?? null;
|
|
@@ -1359,7 +2255,7 @@ var WorkRuntimeApi = class {
|
|
|
1359
2255
|
return this.token;
|
|
1360
2256
|
}
|
|
1361
2257
|
const response = await this.transport.request({
|
|
1362
|
-
type: "cohub.
|
|
2258
|
+
type: "cohub.app.token",
|
|
1363
2259
|
forceRefresh: Boolean(options?.forceRefresh)
|
|
1364
2260
|
}, { timeoutMs: 2e4 });
|
|
1365
2261
|
this.token = response?.token ?? null;
|
|
@@ -1369,7 +2265,7 @@ var WorkRuntimeApi = class {
|
|
|
1369
2265
|
async requestAuthorization(input) {
|
|
1370
2266
|
await this.ensureStorageKeys();
|
|
1371
2267
|
const response = await this.transport.request({
|
|
1372
|
-
type: "cohub.
|
|
2268
|
+
type: "cohub.app.authorize",
|
|
1373
2269
|
scopes: input.scopes,
|
|
1374
2270
|
reason: input.reason
|
|
1375
2271
|
}, { timeoutMs: 12e4 });
|
|
@@ -1384,32 +2280,34 @@ var WorkRuntimeApi = class {
|
|
|
1384
2280
|
async purchase(input) {
|
|
1385
2281
|
const purchaseAttemptId = input.purchaseAttemptId?.trim() || generateRequestId();
|
|
1386
2282
|
return (await this.transport.request({
|
|
1387
|
-
type: "cohub.
|
|
2283
|
+
type: "cohub.app.purchase",
|
|
1388
2284
|
productKey: input.productKey,
|
|
1389
2285
|
purchaseAttemptId
|
|
1390
2286
|
}, { timeoutMs: 12e4 }))?.checkout ?? null;
|
|
1391
2287
|
}
|
|
1392
2288
|
async checkoutState() {
|
|
1393
|
-
return await this.transport.request({ type: "cohub.
|
|
2289
|
+
return await this.transport.request({ type: "cohub.app.checkout-state" }, {
|
|
1394
2290
|
timeoutMs: 8e3,
|
|
1395
2291
|
retryIntervalMs: 250
|
|
1396
2292
|
}) ?? null;
|
|
1397
2293
|
}
|
|
1398
2294
|
};
|
|
1399
2295
|
/**
|
|
1400
|
-
* Builds a memoized
|
|
1401
|
-
* public slug triple via `GET /api/
|
|
1402
|
-
*
|
|
1403
|
-
*
|
|
2296
|
+
* Builds a memoized appId resolver that reverse-looks-up the appId from the
|
|
2297
|
+
* public slug triple via `GET /api/apps/by-slug/:username/:spaceSlug/:appSlug`
|
|
2298
|
+
* (the works REST routes are dual-mounted; `/api/works` keeps serving older
|
|
2299
|
+
* consumers with identical payloads). The endpoint is
|
|
2300
|
+
* anonymous (no auth) for public apps, so no token is needed. The result is
|
|
2301
|
+
* cached; a failed lookup is not cached so it can be retried.
|
|
1404
2302
|
*/
|
|
1405
|
-
function
|
|
2303
|
+
function createSlugAppIdResolver(deps) {
|
|
1406
2304
|
let cached = null;
|
|
1407
2305
|
return () => {
|
|
1408
2306
|
if (cached) return cached;
|
|
1409
2307
|
const run = (async () => {
|
|
1410
2308
|
const doFetch = deps.fetch ?? globalThis.fetch;
|
|
1411
2309
|
if (typeof doFetch !== "function") return null;
|
|
1412
|
-
const response = await doFetch(`${deps.apiBaseUrl}/api/
|
|
2310
|
+
const response = await doFetch(`${deps.apiBaseUrl}/api/apps/by-slug/${encodeURIComponent(deps.ownerUsername)}/${encodeURIComponent(deps.spaceSlug)}/${encodeURIComponent(deps.appSlug)}`);
|
|
1413
2311
|
if (!response.ok) throw new Error(`getBySlug failed: ${response.status}`);
|
|
1414
2312
|
return (await response.json())?.work?.id ?? null;
|
|
1415
2313
|
})().catch(() => {
|
|
@@ -1421,27 +2319,27 @@ function createSlugWorkIdResolver(deps) {
|
|
|
1421
2319
|
};
|
|
1422
2320
|
}
|
|
1423
2321
|
/**
|
|
1424
|
-
* Resolves the appropriate transport based on the
|
|
2322
|
+
* Resolves the appropriate transport based on the app mode configuration.
|
|
1425
2323
|
* Auto-detection: inside an iframe → bridge; standalone with broker config →
|
|
1426
|
-
* broker; otherwise → bridge (returns null for non-
|
|
2324
|
+
* broker; otherwise → bridge (returns null for non-app contexts).
|
|
1427
2325
|
*/
|
|
1428
|
-
function
|
|
2326
|
+
function resolveAppTransport(config, appIdResolver) {
|
|
1429
2327
|
const explicitMode = config?.mode;
|
|
1430
2328
|
const brokerOrigin = config?.brokerOrigin;
|
|
1431
|
-
const
|
|
1432
|
-
const
|
|
1433
|
-
const hasBrokerConfig = Boolean(brokerOrigin &&
|
|
1434
|
-
const createBroker = () => brokerOrigin &&
|
|
2329
|
+
const appId = config?.appId;
|
|
2330
|
+
const canResolveAppId = Boolean(appId || appIdResolver);
|
|
2331
|
+
const hasBrokerConfig = Boolean(brokerOrigin && canResolveAppId);
|
|
2332
|
+
const createBroker = () => brokerOrigin && canResolveAppId ? new PopupBrokerTransport({
|
|
1435
2333
|
brokerOrigin,
|
|
1436
|
-
|
|
1437
|
-
|
|
2334
|
+
appId,
|
|
2335
|
+
getAppId: appIdResolver
|
|
1438
2336
|
}) : new ParentBridgeTransport();
|
|
1439
2337
|
if (explicitMode === "bridge") return new ParentBridgeTransport();
|
|
1440
2338
|
if (explicitMode === "broker") return createBroker();
|
|
1441
2339
|
if (typeof window !== "undefined" && window.parent !== window) return new ParentBridgeTransport();
|
|
1442
2340
|
return hasBrokerConfig ? createBroker() : new ParentBridgeTransport();
|
|
1443
2341
|
}
|
|
1444
|
-
const
|
|
2342
|
+
const createAppRuntime = (transport, appId, appIdResolver) => new AppRuntimeApi(transport, appId, appIdResolver);
|
|
1445
2343
|
//#endregion
|
|
1446
2344
|
//#region src/client.ts
|
|
1447
2345
|
var CohubClient = class {
|
|
@@ -1460,27 +2358,40 @@ var CohubClient = class {
|
|
|
1460
2358
|
references;
|
|
1461
2359
|
tasks;
|
|
1462
2360
|
cronJobs;
|
|
1463
|
-
|
|
2361
|
+
desktop;
|
|
1464
2362
|
invite;
|
|
1465
2363
|
referrals;
|
|
1466
2364
|
voice;
|
|
1467
|
-
|
|
1468
|
-
|
|
2365
|
+
apps;
|
|
2366
|
+
appCommerce;
|
|
2367
|
+
/** @deprecated Use `client.desktop`. */
|
|
2368
|
+
get ui() {
|
|
2369
|
+
return this.desktop;
|
|
2370
|
+
}
|
|
2371
|
+
/** @deprecated Use `client.apps`. */
|
|
2372
|
+
get works() {
|
|
2373
|
+
return this.apps;
|
|
2374
|
+
}
|
|
2375
|
+
/** @deprecated Use `client.appCommerce`. */
|
|
2376
|
+
get workCommerce() {
|
|
2377
|
+
return this.appCommerce;
|
|
2378
|
+
}
|
|
1469
2379
|
transport;
|
|
1470
2380
|
websocketClient;
|
|
1471
|
-
|
|
2381
|
+
appRuntime;
|
|
1472
2382
|
constructor(options = {}) {
|
|
1473
2383
|
const apiBaseUrl = resolveApiBaseUrl(options);
|
|
1474
|
-
const
|
|
2384
|
+
const appRuntime = options.app ?? options.work;
|
|
2385
|
+
const appIdResolver = !appRuntime?.appId && appRuntime?.ownerUsername && appRuntime?.spaceSlug && appRuntime?.appSlug ? createSlugAppIdResolver({
|
|
1475
2386
|
apiBaseUrl,
|
|
1476
2387
|
fetch: options.fetch,
|
|
1477
|
-
ownerUsername:
|
|
1478
|
-
spaceSlug:
|
|
1479
|
-
|
|
2388
|
+
ownerUsername: appRuntime.ownerUsername,
|
|
2389
|
+
spaceSlug: appRuntime.spaceSlug,
|
|
2390
|
+
appSlug: appRuntime.appSlug
|
|
1480
2391
|
}) : void 0;
|
|
1481
|
-
const
|
|
1482
|
-
this.
|
|
1483
|
-
const getAccessToken = options.getAccessToken ?? ((tokenOptions) => this.
|
|
2392
|
+
const appTransport = resolveAppTransport(appRuntime, appIdResolver);
|
|
2393
|
+
this.appRuntime = createAppRuntime(appTransport, appRuntime?.appId, appIdResolver);
|
|
2394
|
+
const getAccessToken = options.getAccessToken ?? ((tokenOptions) => this.appRuntime.getAccessToken(tokenOptions));
|
|
1484
2395
|
const resolvedOptions = {
|
|
1485
2396
|
...options,
|
|
1486
2397
|
getAccessToken
|
|
@@ -1517,55 +2428,56 @@ var CohubClient = class {
|
|
|
1517
2428
|
this.references = new ReferencesApi(this.transport);
|
|
1518
2429
|
this.tasks = new TasksApi(this.transport);
|
|
1519
2430
|
this.cronJobs = new CronJobsApi(this.transport);
|
|
1520
|
-
this.
|
|
2431
|
+
this.desktop = new DesktopCommandsApi(this.transport);
|
|
1521
2432
|
this.invite = new PublicInviteApi(this.transport);
|
|
1522
2433
|
this.referrals = new ReferralsApi(this.transport);
|
|
1523
|
-
this.
|
|
1524
|
-
this.
|
|
1525
|
-
this.
|
|
2434
|
+
this.apps = new AppsApi(this.transport);
|
|
2435
|
+
this.appCommerce = new AppCommerceApi(this.transport);
|
|
2436
|
+
this.app.realtime = new AppRealtimeApi(this.transport, this.websocketClient, () => this.appRuntime.context());
|
|
1526
2437
|
}
|
|
1527
2438
|
context() {
|
|
1528
|
-
return this.
|
|
2439
|
+
return this.appRuntime.context();
|
|
1529
2440
|
}
|
|
1530
|
-
auth = { request: (input) => this.
|
|
1531
|
-
|
|
2441
|
+
auth = { request: (input) => this.appRuntime.requestAuthorization(input) };
|
|
2442
|
+
app = {
|
|
1532
2443
|
realtime: null,
|
|
1533
|
-
/** Expose callable methods from inside a published
|
|
1534
|
-
surface: new
|
|
2444
|
+
/** Expose callable methods from inside a published app. */
|
|
2445
|
+
surface: new AppSurfaceApi(),
|
|
2446
|
+
onContextChanged: (listener) => this.appRuntime.onContextChanged(listener),
|
|
1535
2447
|
composer: {
|
|
1536
|
-
/** Attach or update context from this
|
|
1537
|
-
setChip: (chip) => this.
|
|
1538
|
-
/** Remove context previously attached by this
|
|
1539
|
-
clearChip: (key) => this.
|
|
2448
|
+
/** Attach or update context from this app in the Cohub composer. */
|
|
2449
|
+
setChip: (chip) => this.app.surface.setComposerChip(chip),
|
|
2450
|
+
/** Remove context previously attached by this app. */
|
|
2451
|
+
clearChip: (key) => this.app.surface.clearComposerChip(key)
|
|
1540
2452
|
},
|
|
1541
2453
|
commerce: {
|
|
1542
2454
|
resolveProducts: async (input) => {
|
|
1543
|
-
const context = await this.
|
|
1544
|
-
if (!context?.
|
|
1545
|
-
return this.
|
|
2455
|
+
const context = await this.appRuntime.context();
|
|
2456
|
+
if (!context?.app?.id) throw new Error("App context is unavailable — not running inside a published app runtime.");
|
|
2457
|
+
return this.appCommerce.resolveProducts(context.app.id, input);
|
|
1546
2458
|
},
|
|
1547
2459
|
getEntitlements: async () => {
|
|
1548
|
-
const context = await this.
|
|
1549
|
-
if (!context?.
|
|
1550
|
-
return this.
|
|
2460
|
+
const context = await this.appRuntime.context();
|
|
2461
|
+
if (!context?.app?.id) throw new Error("App context is unavailable — not running inside a published app runtime.");
|
|
2462
|
+
return this.appCommerce.getEntitlements(context.app.id);
|
|
1551
2463
|
},
|
|
1552
2464
|
consumeCredits: async (input) => {
|
|
1553
|
-
const context = await this.
|
|
1554
|
-
if (!context?.
|
|
1555
|
-
return this.
|
|
2465
|
+
const context = await this.appRuntime.context();
|
|
2466
|
+
if (!context?.app?.id) throw new Error("App context is unavailable — not running inside a published app runtime.");
|
|
2467
|
+
return this.appCommerce.consumeCredits(context.app.id, input);
|
|
1556
2468
|
},
|
|
1557
|
-
purchase: async (input) => this.
|
|
2469
|
+
purchase: async (input) => this.appRuntime.purchase(input),
|
|
1558
2470
|
getCheckoutState: async () => {
|
|
1559
|
-
const result = await this.
|
|
2471
|
+
const result = await this.appRuntime.checkoutState();
|
|
1560
2472
|
return {
|
|
1561
2473
|
status: result?.status ?? null,
|
|
1562
2474
|
orderId: result?.orderId ?? null
|
|
1563
2475
|
};
|
|
1564
2476
|
},
|
|
1565
2477
|
getOrder: async (orderId) => {
|
|
1566
|
-
const context = await this.
|
|
1567
|
-
if (!context?.
|
|
1568
|
-
return this.
|
|
2478
|
+
const context = await this.appRuntime.context();
|
|
2479
|
+
if (!context?.app?.id) throw new Error("App context is unavailable — not running inside a published app runtime.");
|
|
2480
|
+
return this.appCommerce.getOrder(context.app.id, orderId);
|
|
1569
2481
|
}
|
|
1570
2482
|
}
|
|
1571
2483
|
};
|
|
@@ -1623,8 +2535,8 @@ var CohubClient = class {
|
|
|
1623
2535
|
};
|
|
1624
2536
|
const createCohubClient = (options) => new CohubClient(options);
|
|
1625
2537
|
//#endregion
|
|
1626
|
-
//#region src/
|
|
1627
|
-
const
|
|
2538
|
+
//#region src/app-ref.ts
|
|
2539
|
+
const isAppId = (value) => isUuid(value.trim());
|
|
1628
2540
|
function decodePart(value) {
|
|
1629
2541
|
try {
|
|
1630
2542
|
return decodeURIComponent(value).trim();
|
|
@@ -1634,14 +2546,14 @@ function decodePart(value) {
|
|
|
1634
2546
|
}
|
|
1635
2547
|
function publicRef(parts) {
|
|
1636
2548
|
if (parts.length !== 3) return null;
|
|
1637
|
-
const [usernameRaw = "", spaceSlugRaw = "",
|
|
2549
|
+
const [usernameRaw = "", spaceSlugRaw = "", appSlugRaw = ""] = parts.map(decodePart);
|
|
1638
2550
|
const username = parseUsername(usernameRaw);
|
|
1639
2551
|
const spaceSlug = parseSpaceSlug(spaceSlugRaw);
|
|
1640
|
-
const
|
|
1641
|
-
return username && spaceSlug &&
|
|
2552
|
+
const appSlug = parseSpaceSlug(appSlugRaw);
|
|
2553
|
+
return username && spaceSlug && appSlug ? {
|
|
1642
2554
|
username,
|
|
1643
2555
|
spaceSlug,
|
|
1644
|
-
|
|
2556
|
+
appSlug
|
|
1645
2557
|
} : null;
|
|
1646
2558
|
}
|
|
1647
2559
|
function launchState(url) {
|
|
@@ -1658,15 +2570,22 @@ function parseUrlRef(value) {
|
|
|
1658
2570
|
return null;
|
|
1659
2571
|
}
|
|
1660
2572
|
const parts = url.pathname.split("/").filter(Boolean);
|
|
1661
|
-
if (url.protocol === "cohub:" && url.hostname === "works") {
|
|
2573
|
+
if (url.protocol === "cohub:" && (url.hostname === "apps" || url.hostname === "works")) {
|
|
1662
2574
|
const ref = publicRef(parts);
|
|
1663
2575
|
return ref ? {
|
|
1664
2576
|
...ref,
|
|
1665
2577
|
...launchState(url)
|
|
1666
2578
|
} : null;
|
|
1667
2579
|
}
|
|
2580
|
+
if (url.protocol === "app:" || url.protocol === "work:") {
|
|
2581
|
+
const ref = publicRef([url.hostname, ...parts]);
|
|
2582
|
+
return ref ? {
|
|
2583
|
+
...ref,
|
|
2584
|
+
...launchState(url)
|
|
2585
|
+
} : null;
|
|
2586
|
+
}
|
|
1668
2587
|
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
1669
|
-
if (parts.length === 4 && parts[0] === "spaces" && isUuid(parts[1] ?? "") && parts[2] === "works" && isUuid(parts[3] ?? "")) return { id: parts[3] };
|
|
2588
|
+
if (parts.length === 4 && parts[0] === "spaces" && isUuid(parts[1] ?? "") && (parts[2] === "apps" || parts[2] === "works") && isUuid(parts[3] ?? "")) return { id: parts[3] };
|
|
1670
2589
|
if (parts.length === 4 && parts[2] === "w") {
|
|
1671
2590
|
const ref = publicRef([
|
|
1672
2591
|
parts[0],
|
|
@@ -1680,13 +2599,13 @@ function parseUrlRef(value) {
|
|
|
1680
2599
|
}
|
|
1681
2600
|
return null;
|
|
1682
2601
|
}
|
|
1683
|
-
var
|
|
2602
|
+
var AppRefParseError = class extends Error {
|
|
1684
2603
|
constructor() {
|
|
1685
|
-
super("
|
|
1686
|
-
this.name = "
|
|
2604
|
+
super("App must be an id, public URL, cohub://apps URI, or username/space/app reference");
|
|
2605
|
+
this.name = "AppRefParseError";
|
|
1687
2606
|
}
|
|
1688
2607
|
};
|
|
1689
|
-
function
|
|
2608
|
+
function parseAppRef(input) {
|
|
1690
2609
|
const value = input.trim();
|
|
1691
2610
|
if (isUuid(value)) return { id: value };
|
|
1692
2611
|
const parsedUrl = parseUrlRef(value.includes("://") ? value : value.startsWith("/") ? `https://cohub.invalid${value}` : value);
|
|
@@ -1694,21 +2613,29 @@ function parseWorkRef(input) {
|
|
|
1694
2613
|
const parts = value.split("/").filter(Boolean);
|
|
1695
2614
|
const parsedPublic = parts.length === 3 ? publicRef(parts) : null;
|
|
1696
2615
|
if (parsedPublic) return parsedPublic;
|
|
1697
|
-
throw new
|
|
2616
|
+
throw new AppRefParseError();
|
|
1698
2617
|
}
|
|
1699
|
-
function
|
|
1700
|
-
return "id" in ref ? ref.id : `${ref.username}/${ref.spaceSlug}/${ref.
|
|
2618
|
+
function formatAppRef(ref) {
|
|
2619
|
+
return "id" in ref ? ref.id : `${ref.username}/${ref.spaceSlug}/${ref.appSlug}`;
|
|
1701
2620
|
}
|
|
2621
|
+
/** @deprecated Use `isAppId`. */
|
|
2622
|
+
const isWorkId = isAppId;
|
|
2623
|
+
/** @deprecated Use `AppRefParseError`. */
|
|
2624
|
+
const WorkRefParseError = AppRefParseError;
|
|
2625
|
+
/** @deprecated Use `parseAppRef`. */
|
|
2626
|
+
const parseWorkRef = parseAppRef;
|
|
2627
|
+
/** @deprecated Use `formatAppRef`. */
|
|
2628
|
+
const formatWorkRef = formatAppRef;
|
|
1702
2629
|
//#endregion
|
|
1703
|
-
//#region src/
|
|
2630
|
+
//#region src/app-grant-cache.ts
|
|
1704
2631
|
const STORAGE_PREFIX = "cohub:work-grants";
|
|
1705
2632
|
const CACHE_VERSION = 1;
|
|
1706
2633
|
const MAX_AGE_MS = 336 * 60 * 60 * 1e3;
|
|
1707
2634
|
function isBrowser() {
|
|
1708
2635
|
return typeof localStorage !== "undefined";
|
|
1709
2636
|
}
|
|
1710
|
-
function storageKey(userUuid,
|
|
1711
|
-
return `${STORAGE_PREFIX}:${encodeURIComponent(userUuid)}:${encodeURIComponent(
|
|
2637
|
+
function storageKey(userUuid, appId) {
|
|
2638
|
+
return `${STORAGE_PREFIX}:${encodeURIComponent(userUuid)}:${encodeURIComponent(appId)}:v${CACHE_VERSION}`;
|
|
1712
2639
|
}
|
|
1713
2640
|
function userPrefix(userUuid) {
|
|
1714
2641
|
return `${STORAGE_PREFIX}:${encodeURIComponent(userUuid)}:`;
|
|
@@ -1719,16 +2646,16 @@ function isPermissionArray(value) {
|
|
|
1719
2646
|
function isCachedWorkGrant(value) {
|
|
1720
2647
|
if (!value || typeof value !== "object") return false;
|
|
1721
2648
|
const record = value;
|
|
1722
|
-
return record.version === CACHE_VERSION && typeof record.userUuid === "string" && typeof record.
|
|
2649
|
+
return record.version === CACHE_VERSION && typeof record.userUuid === "string" && typeof record.appId === "string" && isPermissionArray(record.scopes) && typeof record.updatedAt === "number";
|
|
1723
2650
|
}
|
|
1724
|
-
function readEntry(userUuid,
|
|
2651
|
+
function readEntry(userUuid, appId) {
|
|
1725
2652
|
if (!isBrowser()) return null;
|
|
1726
|
-
const key = storageKey(userUuid,
|
|
2653
|
+
const key = storageKey(userUuid, appId);
|
|
1727
2654
|
try {
|
|
1728
2655
|
const raw = localStorage.getItem(key);
|
|
1729
2656
|
if (!raw) return null;
|
|
1730
2657
|
const parsed = JSON.parse(raw);
|
|
1731
|
-
if (!isCachedWorkGrant(parsed) || parsed.userUuid !== userUuid || parsed.
|
|
2658
|
+
if (!isCachedWorkGrant(parsed) || parsed.userUuid !== userUuid || parsed.appId !== appId) {
|
|
1732
2659
|
localStorage.removeItem(key);
|
|
1733
2660
|
return null;
|
|
1734
2661
|
}
|
|
@@ -1748,9 +2675,9 @@ function readEntry(userUuid, workId) {
|
|
|
1748
2675
|
* Returns true when the viewer has previously granted every requested scope
|
|
1749
2676
|
* for this work, allowing a silent re-authorization.
|
|
1750
2677
|
*/
|
|
1751
|
-
function
|
|
1752
|
-
if (!userUuid || !
|
|
1753
|
-
const entry = readEntry(userUuid,
|
|
2678
|
+
function hasGrantedAppScopes(userUuid, appId, scopes) {
|
|
2679
|
+
if (!userUuid || !appId || scopes.length === 0) return false;
|
|
2680
|
+
const entry = readEntry(userUuid, appId);
|
|
1754
2681
|
if (!entry) return false;
|
|
1755
2682
|
const granted = new Set(entry.scopes);
|
|
1756
2683
|
return scopes.every((scope) => granted.has(scope));
|
|
@@ -1759,31 +2686,31 @@ function hasGrantedWorkScopes(userUuid, workId, scopes) {
|
|
|
1759
2686
|
* Records the granted scopes for a work, merged with any previously granted
|
|
1760
2687
|
* scopes so a growing permission set stays covered.
|
|
1761
2688
|
*/
|
|
1762
|
-
function
|
|
1763
|
-
if (!userUuid || !
|
|
1764
|
-
const existing = readEntry(userUuid,
|
|
2689
|
+
function setGrantedAppScopes(userUuid, appId, scopes) {
|
|
2690
|
+
if (!userUuid || !appId || scopes.length === 0) return;
|
|
2691
|
+
const existing = readEntry(userUuid, appId);
|
|
1765
2692
|
const merged = Array.from(/* @__PURE__ */ new Set([...existing?.scopes ?? [], ...scopes]));
|
|
1766
2693
|
const entry = {
|
|
1767
2694
|
version: CACHE_VERSION,
|
|
1768
2695
|
userUuid,
|
|
1769
|
-
|
|
2696
|
+
appId,
|
|
1770
2697
|
scopes: merged,
|
|
1771
2698
|
updatedAt: Date.now()
|
|
1772
2699
|
};
|
|
1773
2700
|
if (!isBrowser()) return;
|
|
1774
2701
|
try {
|
|
1775
|
-
localStorage.setItem(storageKey(userUuid,
|
|
2702
|
+
localStorage.setItem(storageKey(userUuid, appId), JSON.stringify(entry));
|
|
1776
2703
|
} catch {}
|
|
1777
2704
|
}
|
|
1778
2705
|
/**
|
|
1779
|
-
* Clears cached grants. Pass a
|
|
2706
|
+
* Clears cached grants. Pass a appId to clear a single work, or omit it to
|
|
1780
2707
|
* clear every cached grant for the user (used on sign-out).
|
|
1781
2708
|
*/
|
|
1782
|
-
function
|
|
2709
|
+
function clearGrantedAppScopes(userUuid, appId) {
|
|
1783
2710
|
if (!isBrowser() || !userUuid) return;
|
|
1784
2711
|
try {
|
|
1785
|
-
if (
|
|
1786
|
-
localStorage.removeItem(storageKey(userUuid,
|
|
2712
|
+
if (appId) {
|
|
2713
|
+
localStorage.removeItem(storageKey(userUuid, appId));
|
|
1787
2714
|
return;
|
|
1788
2715
|
}
|
|
1789
2716
|
const prefix = userPrefix(userUuid);
|
|
@@ -1794,7 +2721,7 @@ function clearGrantedWorkScopes(userUuid, workId) {
|
|
|
1794
2721
|
} catch {}
|
|
1795
2722
|
}
|
|
1796
2723
|
//#endregion
|
|
1797
|
-
//#region src/
|
|
2724
|
+
//#region src/app-bridge-core.ts
|
|
1798
2725
|
function readTokenResponse(value) {
|
|
1799
2726
|
if (!value || typeof value !== "object") return null;
|
|
1800
2727
|
const token = value.token;
|
|
@@ -1804,20 +2731,78 @@ function clonePermissionScopes(scopes) {
|
|
|
1804
2731
|
return Array.from(scopes ?? []).filter((scope) => typeof scope === "string");
|
|
1805
2732
|
}
|
|
1806
2733
|
/**
|
|
1807
|
-
* Framework-agnostic
|
|
2734
|
+
* Framework-agnostic app bridge host core — message handling, app session
|
|
1808
2735
|
* token minting, authorization (with silent re-grant cache), and
|
|
1809
2736
|
* purchase/checkout flow — without any rendering or reactive primitives.
|
|
1810
2737
|
*
|
|
1811
|
-
* Both the Cohub iframe host (
|
|
2738
|
+
* Both the Cohub iframe host (AppSurface, Svelte) and the standalone broker
|
|
1812
2739
|
* page compose this with their own transport-specific reply and auth
|
|
1813
2740
|
* dependencies. External hosts (e.g. Neta-Studio in React) can do the same.
|
|
1814
2741
|
*/
|
|
1815
|
-
function
|
|
1816
|
-
const {
|
|
2742
|
+
function createAppBridgeCore(config) {
|
|
2743
|
+
const { app, reply, getCheckoutState, getAccessToken, getViewerUuid } = config;
|
|
1817
2744
|
const apiOrigin = config.apiOrigin;
|
|
1818
2745
|
const authorizationContext = config.authorizationContext ?? (config.isBackground ? { surface: "background" } : { surface: "page" });
|
|
1819
2746
|
const onStateChange = config.onStateChange;
|
|
1820
|
-
let
|
|
2747
|
+
let appToken = null;
|
|
2748
|
+
let activeInvocation;
|
|
2749
|
+
let contextChangeVersion = 0;
|
|
2750
|
+
const legacyRequestIds = /* @__PURE__ */ new Set();
|
|
2751
|
+
async function getContext() {
|
|
2752
|
+
const invocation = activeInvocation !== void 0 ? activeInvocation : config.getInvocation?.() ?? config.invocation;
|
|
2753
|
+
const appScopes = clonePermissionScopes(app.appScopes);
|
|
2754
|
+
const viewerUuid = await getViewerUuid();
|
|
2755
|
+
return {
|
|
2756
|
+
app: {
|
|
2757
|
+
id: app.id,
|
|
2758
|
+
slug: app.slug,
|
|
2759
|
+
url: typeof location !== "undefined" ? location.href : ""
|
|
2760
|
+
},
|
|
2761
|
+
space: { id: app.spaceId },
|
|
2762
|
+
viewer: viewerUuid ? { userUuid: viewerUuid } : null,
|
|
2763
|
+
...invocation ? { invocation: { ...invocation } } : {},
|
|
2764
|
+
permissions: {
|
|
2765
|
+
scopes: appScopes,
|
|
2766
|
+
appScopes,
|
|
2767
|
+
viewerScopes: []
|
|
2768
|
+
}
|
|
2769
|
+
};
|
|
2770
|
+
}
|
|
2771
|
+
function replyForRequest(requestId, payload, complete = false) {
|
|
2772
|
+
const namespace = legacyRequestIds.has(requestId) ? "work" : "app";
|
|
2773
|
+
const type = payload.type;
|
|
2774
|
+
reply(requestId, {
|
|
2775
|
+
...payload,
|
|
2776
|
+
...typeof type === "string" && type.startsWith("cohub.app.") ? { type: type.replace("cohub.app.", `cohub.${namespace}.`) } : {}
|
|
2777
|
+
});
|
|
2778
|
+
if (complete) legacyRequestIds.delete(requestId);
|
|
2779
|
+
}
|
|
2780
|
+
function toLegacyWorkContext(context) {
|
|
2781
|
+
const permissions = context.permissions ? {
|
|
2782
|
+
scopes: context.permissions.scopes,
|
|
2783
|
+
workScopes: context.permissions.appScopes,
|
|
2784
|
+
appScopes: context.permissions.appScopes,
|
|
2785
|
+
viewerScopes: context.permissions.viewerScopes
|
|
2786
|
+
} : void 0;
|
|
2787
|
+
return {
|
|
2788
|
+
work: context.app,
|
|
2789
|
+
space: context.space,
|
|
2790
|
+
...context.viewer !== void 0 ? { viewer: context.viewer } : {},
|
|
2791
|
+
...context.invocation ? { invocation: context.invocation } : {},
|
|
2792
|
+
...permissions ? { permissions } : {}
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
async function notifyContextChanged(invocation) {
|
|
2796
|
+
activeInvocation = invocation;
|
|
2797
|
+
const version = ++contextChangeVersion;
|
|
2798
|
+
if (!config.notify) return;
|
|
2799
|
+
const context = await getContext();
|
|
2800
|
+
if (version !== contextChangeVersion) return;
|
|
2801
|
+
config.notify({
|
|
2802
|
+
type: "cohub.app.context.changed",
|
|
2803
|
+
context
|
|
2804
|
+
});
|
|
2805
|
+
}
|
|
1821
2806
|
const state = {
|
|
1822
2807
|
authOpen: false,
|
|
1823
2808
|
pendingAuth: null,
|
|
@@ -1831,30 +2816,30 @@ function createWorkBridgeCore(config) {
|
|
|
1831
2816
|
function notify() {
|
|
1832
2817
|
onStateChange?.({ ...state });
|
|
1833
2818
|
}
|
|
1834
|
-
const pendingPurchaseStorageKey = `cohub-
|
|
1835
|
-
async function
|
|
2819
|
+
const pendingPurchaseStorageKey = `cohub-app-purchase:${app.id}`;
|
|
2820
|
+
async function isCurrentViewerAppOwner() {
|
|
1836
2821
|
const viewerUuid = await getViewerUuid();
|
|
1837
|
-
return Boolean(viewerUuid && viewerUuid ===
|
|
2822
|
+
return Boolean(viewerUuid && viewerUuid === app.userUuid);
|
|
1838
2823
|
}
|
|
1839
2824
|
function allowsOwnerAutoAuthorization() {
|
|
1840
|
-
return authorizationContext.surface === "background" || authorizationContext.surface === "
|
|
2825
|
+
return authorizationContext.surface === "background" || authorizationContext.surface === "app";
|
|
1841
2826
|
}
|
|
1842
2827
|
async function ensureBaseToken(forceRefresh = false) {
|
|
1843
|
-
if (
|
|
2828
|
+
if (appToken && !forceRefresh) return appToken;
|
|
1844
2829
|
const userToken = await getAccessToken({ forceRefresh });
|
|
1845
2830
|
if (!userToken) {
|
|
1846
2831
|
await config.requestSignIn(typeof location !== "undefined" ? location.pathname : "/");
|
|
1847
2832
|
return null;
|
|
1848
2833
|
}
|
|
1849
|
-
const response = await fetch(`${apiOrigin}/api/
|
|
2834
|
+
const response = await fetch(`${apiOrigin}/api/apps/${app.id}/session`, {
|
|
1850
2835
|
method: "POST",
|
|
1851
2836
|
headers: { Authorization: `Bearer ${userToken}` }
|
|
1852
2837
|
});
|
|
1853
|
-
if (!response.ok) throw new Error("Failed to create
|
|
2838
|
+
if (!response.ok) throw new Error("Failed to create app session.");
|
|
1854
2839
|
const token = readTokenResponse(await response.json());
|
|
1855
|
-
if (!token) throw new Error("Invalid
|
|
1856
|
-
|
|
1857
|
-
return
|
|
2840
|
+
if (!token) throw new Error("Invalid app session response.");
|
|
2841
|
+
appToken = token;
|
|
2842
|
+
return appToken;
|
|
1858
2843
|
}
|
|
1859
2844
|
async function authorize(scopes) {
|
|
1860
2845
|
const userToken = await getAccessToken();
|
|
@@ -1862,7 +2847,7 @@ function createWorkBridgeCore(config) {
|
|
|
1862
2847
|
await config.requestSignIn(typeof location !== "undefined" ? location.pathname : "/");
|
|
1863
2848
|
return null;
|
|
1864
2849
|
}
|
|
1865
|
-
const response = await fetch(`${apiOrigin}/api/
|
|
2850
|
+
const response = await fetch(`${apiOrigin}/api/apps/${app.id}/authorize`, {
|
|
1866
2851
|
method: "POST",
|
|
1867
2852
|
headers: {
|
|
1868
2853
|
Authorization: `Bearer ${userToken}`,
|
|
@@ -1872,9 +2857,9 @@ function createWorkBridgeCore(config) {
|
|
|
1872
2857
|
});
|
|
1873
2858
|
if (!response.ok) throw new Error((await response.json().catch(() => null))?.message ?? "Authorization failed.");
|
|
1874
2859
|
const token = readTokenResponse(await response.json());
|
|
1875
|
-
if (!token) throw new Error("Invalid
|
|
1876
|
-
|
|
1877
|
-
return
|
|
2860
|
+
if (!token) throw new Error("Invalid app authorization response.");
|
|
2861
|
+
appToken = token;
|
|
2862
|
+
return appToken;
|
|
1878
2863
|
}
|
|
1879
2864
|
function writePendingPurchase(input) {
|
|
1880
2865
|
if (typeof sessionStorage === "undefined") return;
|
|
@@ -1913,7 +2898,7 @@ function createWorkBridgeCore(config) {
|
|
|
1913
2898
|
return null;
|
|
1914
2899
|
}
|
|
1915
2900
|
const promotionAttribution = config.getPromotionAttribution?.() ?? null;
|
|
1916
|
-
const response = await fetch(`${apiOrigin}/api/
|
|
2901
|
+
const response = await fetch(`${apiOrigin}/api/apps/${app.id}/commerce/purchase`, {
|
|
1917
2902
|
method: "POST",
|
|
1918
2903
|
headers: {
|
|
1919
2904
|
Authorization: `Bearer ${userToken}`,
|
|
@@ -1931,62 +2916,49 @@ function createWorkBridgeCore(config) {
|
|
|
1931
2916
|
async function handleMessage(event) {
|
|
1932
2917
|
const data = event.data;
|
|
1933
2918
|
if (!data?.requestId) return;
|
|
2919
|
+
const isLegacyWork = data.type?.startsWith("cohub.work.") === true;
|
|
2920
|
+
if (isLegacyWork) legacyRequestIds.add(data.requestId);
|
|
1934
2921
|
try {
|
|
1935
|
-
if (data.type === "cohub.work.context") {
|
|
1936
|
-
const
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
work: {
|
|
1942
|
-
id: work.id,
|
|
1943
|
-
slug: work.slug,
|
|
1944
|
-
url: typeof location !== "undefined" ? location.href : ""
|
|
1945
|
-
},
|
|
1946
|
-
space: { id: work.spaceId },
|
|
1947
|
-
viewer: viewerUuid ? { userUuid: viewerUuid } : null,
|
|
1948
|
-
...config.invocation ? { invocation: { ...config.invocation } } : {},
|
|
1949
|
-
permissions: {
|
|
1950
|
-
scopes: workScopes,
|
|
1951
|
-
workScopes,
|
|
1952
|
-
viewerScopes: []
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
});
|
|
2922
|
+
if (data.type === "cohub.app.context" || data.type === "cohub.work.context") {
|
|
2923
|
+
const context = await getContext();
|
|
2924
|
+
replyForRequest(data.requestId, {
|
|
2925
|
+
type: "cohub.app.context.result",
|
|
2926
|
+
context: isLegacyWork ? toLegacyWorkContext(context) : context
|
|
2927
|
+
}, true);
|
|
1956
2928
|
}
|
|
1957
|
-
if (data.type === "cohub.work.token") {
|
|
2929
|
+
if (data.type === "cohub.app.token" || data.type === "cohub.work.token") {
|
|
1958
2930
|
const token = await ensureBaseToken(Boolean(data.forceRefresh));
|
|
1959
|
-
|
|
1960
|
-
type: "cohub.
|
|
2931
|
+
replyForRequest(data.requestId, {
|
|
2932
|
+
type: "cohub.app.token.result",
|
|
1961
2933
|
token
|
|
1962
|
-
});
|
|
2934
|
+
}, true);
|
|
1963
2935
|
}
|
|
1964
|
-
if (data.type === "cohub.work.checkout-state") {
|
|
2936
|
+
if (data.type === "cohub.app.checkout-state" || data.type === "cohub.work.checkout-state") {
|
|
1965
2937
|
const pending = readPendingPurchase();
|
|
1966
2938
|
const checkoutState = getCheckoutState();
|
|
1967
2939
|
const orderId = checkoutState.orderId ?? pending?.orderId ?? null;
|
|
1968
2940
|
if (checkoutState.status && checkoutState.orderId) clearPendingPurchase();
|
|
1969
|
-
|
|
1970
|
-
type: "cohub.
|
|
2941
|
+
replyForRequest(data.requestId, {
|
|
2942
|
+
type: "cohub.app.checkout-state.result",
|
|
1971
2943
|
status: checkoutState.status,
|
|
1972
2944
|
orderId
|
|
1973
|
-
});
|
|
2945
|
+
}, true);
|
|
1974
2946
|
}
|
|
1975
|
-
if (data.type === "cohub.work.purchase") {
|
|
2947
|
+
if (data.type === "cohub.app.purchase" || data.type === "cohub.work.purchase") {
|
|
1976
2948
|
const productKey = typeof data.productKey === "string" ? data.productKey.trim() : "";
|
|
1977
2949
|
if (!productKey) {
|
|
1978
|
-
|
|
1979
|
-
type: "cohub.
|
|
2950
|
+
replyForRequest(data.requestId, {
|
|
2951
|
+
type: "cohub.app.error",
|
|
1980
2952
|
message: "Product key is required."
|
|
1981
|
-
});
|
|
2953
|
+
}, true);
|
|
1982
2954
|
return;
|
|
1983
2955
|
}
|
|
1984
2956
|
const purchaseAttemptId = (typeof data.purchaseAttemptId === "string" ? data.purchaseAttemptId.trim() : "") || data.requestId.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 128);
|
|
1985
2957
|
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(purchaseAttemptId)) {
|
|
1986
|
-
|
|
1987
|
-
type: "cohub.
|
|
2958
|
+
replyForRequest(data.requestId, {
|
|
2959
|
+
type: "cohub.app.error",
|
|
1988
2960
|
message: "Purchase attempt id is invalid."
|
|
1989
|
-
});
|
|
2961
|
+
}, true);
|
|
1990
2962
|
return;
|
|
1991
2963
|
}
|
|
1992
2964
|
state.pendingPurchase = {
|
|
@@ -1999,34 +2971,34 @@ function createWorkBridgeCore(config) {
|
|
|
1999
2971
|
notify();
|
|
2000
2972
|
config.onPurchaseRequested?.({ ...state.pendingPurchase });
|
|
2001
2973
|
}
|
|
2002
|
-
if (data.type === "cohub.work.authorize") {
|
|
2003
|
-
const allowedViewerScopes = clonePermissionScopes(
|
|
2974
|
+
if (data.type === "cohub.app.authorize" || data.type === "cohub.work.authorize") {
|
|
2975
|
+
const allowedViewerScopes = clonePermissionScopes(app.allowedViewerScopes);
|
|
2004
2976
|
const scopes = clonePermissionScopes(data.scopes).filter((scope) => allowedViewerScopes.includes(scope));
|
|
2005
2977
|
if (scopes.length === 0) {
|
|
2006
|
-
|
|
2007
|
-
type: "cohub.
|
|
2978
|
+
replyForRequest(data.requestId, {
|
|
2979
|
+
type: "cohub.app.error",
|
|
2008
2980
|
message: "No allowed scopes requested."
|
|
2009
|
-
});
|
|
2981
|
+
}, true);
|
|
2010
2982
|
return;
|
|
2011
2983
|
}
|
|
2012
|
-
if (allowsOwnerAutoAuthorization() && await
|
|
2984
|
+
if (allowsOwnerAutoAuthorization() && await isCurrentViewerAppOwner()) {
|
|
2013
2985
|
const token = await authorize(scopes);
|
|
2014
|
-
|
|
2015
|
-
type: "cohub.
|
|
2986
|
+
replyForRequest(data.requestId, {
|
|
2987
|
+
type: "cohub.app.authorize.result",
|
|
2016
2988
|
token
|
|
2017
|
-
});
|
|
2989
|
+
}, true);
|
|
2018
2990
|
return;
|
|
2019
2991
|
}
|
|
2020
2992
|
const viewerUuid = await getViewerUuid();
|
|
2021
|
-
if (viewerUuid &&
|
|
2993
|
+
if (viewerUuid && hasGrantedAppScopes(viewerUuid, app.id, scopes)) try {
|
|
2022
2994
|
const token = await authorize(scopes);
|
|
2023
|
-
|
|
2024
|
-
type: "cohub.
|
|
2995
|
+
replyForRequest(data.requestId, {
|
|
2996
|
+
type: "cohub.app.authorize.result",
|
|
2025
2997
|
token
|
|
2026
|
-
});
|
|
2998
|
+
}, true);
|
|
2027
2999
|
return;
|
|
2028
3000
|
} catch {
|
|
2029
|
-
|
|
3001
|
+
clearGrantedAppScopes(viewerUuid, app.id);
|
|
2030
3002
|
}
|
|
2031
3003
|
state.pendingAuth = {
|
|
2032
3004
|
requestId: data.requestId,
|
|
@@ -2038,19 +3010,19 @@ function createWorkBridgeCore(config) {
|
|
|
2038
3010
|
notify();
|
|
2039
3011
|
}
|
|
2040
3012
|
} catch (error) {
|
|
2041
|
-
|
|
2042
|
-
type: "cohub.
|
|
3013
|
+
replyForRequest(data.requestId, {
|
|
3014
|
+
type: "cohub.app.error",
|
|
2043
3015
|
message: error instanceof Error ? error.message : "Request failed."
|
|
2044
|
-
});
|
|
3016
|
+
}, true);
|
|
2045
3017
|
}
|
|
2046
3018
|
}
|
|
2047
3019
|
function cancelAuth() {
|
|
2048
3020
|
if (state.authSaving) return;
|
|
2049
3021
|
if (!state.pendingAuth) return;
|
|
2050
|
-
|
|
2051
|
-
type: "cohub.
|
|
3022
|
+
replyForRequest(state.pendingAuth.requestId, {
|
|
3023
|
+
type: "cohub.app.authorize.result",
|
|
2052
3024
|
token: null
|
|
2053
|
-
});
|
|
3025
|
+
}, true);
|
|
2054
3026
|
state.authOpen = false;
|
|
2055
3027
|
state.pendingAuth = null;
|
|
2056
3028
|
state.authError = null;
|
|
@@ -2060,10 +3032,10 @@ function createWorkBridgeCore(config) {
|
|
|
2060
3032
|
function cancelPurchase() {
|
|
2061
3033
|
if (state.purchaseSaving) return;
|
|
2062
3034
|
if (!state.pendingPurchase) return;
|
|
2063
|
-
|
|
2064
|
-
type: "cohub.
|
|
3035
|
+
replyForRequest(state.pendingPurchase.requestId, {
|
|
3036
|
+
type: "cohub.app.purchase.result",
|
|
2065
3037
|
checkout: null
|
|
2066
|
-
});
|
|
3038
|
+
}, true);
|
|
2067
3039
|
state.purchaseOpen = false;
|
|
2068
3040
|
state.purchaseError = null;
|
|
2069
3041
|
state.pendingPurchase = null;
|
|
@@ -2077,10 +3049,10 @@ function createWorkBridgeCore(config) {
|
|
|
2077
3049
|
notify();
|
|
2078
3050
|
try {
|
|
2079
3051
|
const checkout = await createPurchase(state.pendingPurchase.productKey, state.pendingPurchase.purchaseAttemptId);
|
|
2080
|
-
|
|
2081
|
-
type: "cohub.
|
|
3052
|
+
replyForRequest(state.pendingPurchase.requestId, {
|
|
3053
|
+
type: "cohub.app.purchase.result",
|
|
2082
3054
|
checkout
|
|
2083
|
-
});
|
|
3055
|
+
}, true);
|
|
2084
3056
|
if (checkout && typeof checkout === "object") {
|
|
2085
3057
|
const next = checkout;
|
|
2086
3058
|
if (typeof next.orderId === "string" && typeof next.productKey === "string") writePendingPurchase({
|
|
@@ -2113,11 +3085,11 @@ function createWorkBridgeCore(config) {
|
|
|
2113
3085
|
notify();
|
|
2114
3086
|
try {
|
|
2115
3087
|
const token = await authorize(state.pendingAuth.scopes);
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
type: "cohub.
|
|
3088
|
+
setGrantedAppScopes(await getViewerUuid(), app.id, state.pendingAuth.scopes);
|
|
3089
|
+
replyForRequest(state.pendingAuth.requestId, {
|
|
3090
|
+
type: "cohub.app.authorize.result",
|
|
2119
3091
|
token
|
|
2120
|
-
});
|
|
3092
|
+
}, true);
|
|
2121
3093
|
state.authOpen = false;
|
|
2122
3094
|
state.pendingAuth = null;
|
|
2123
3095
|
} catch (error) {
|
|
@@ -2130,6 +3102,7 @@ function createWorkBridgeCore(config) {
|
|
|
2130
3102
|
return {
|
|
2131
3103
|
getState: () => ({ ...state }),
|
|
2132
3104
|
handleMessage,
|
|
3105
|
+
notifyContextChanged,
|
|
2133
3106
|
confirmAuth,
|
|
2134
3107
|
cancelAuth,
|
|
2135
3108
|
confirmPurchase,
|
|
@@ -2370,4 +3343,4 @@ function createBoardExtensionRegistry() {
|
|
|
2370
3343
|
}
|
|
2371
3344
|
const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
|
|
2372
3345
|
//#endregion
|
|
2373
|
-
export { BILLING_ACCESS_BLOCKED_ERROR_CODE, BOARD_ANIMATION_CHANNEL_CAPABILITIES, BOARD_CHANNELS, BOARD_COLOR_IDS, BOARD_GEO_KINDS,
|
|
3346
|
+
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, 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, 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, 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, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebsocketUrl, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, setGrantedAppScopes, track };
|