@neta-art/cohub 8.0.1 → 8.2.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 +50 -38
- package/dist/chunks/http.d.ts +68 -9
- package/dist/chunks/http.js +67 -1
- package/dist/chunks/websocket.d.ts +11 -2
- package/dist/http.d.ts +3 -3
- package/dist/http.js +2 -2
- package/dist/index.d.ts +43 -14
- package/dist/index.js +387 -95
- package/docs/work-runtime-guide.md +366 -290
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as BOARD_BUILTIN_CAPABILITIES, A as DESKTOP_COMMAND_PENDING_TTL_SECONDS, B as BoardSemanticMutationSchema, C as parseAssistantMessageCommit, D as DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, E as ensureRealtimeConnected, F as isTerminalDesktopCommandStatus, G as BOARD_ANIMATION_CHANNEL_CAPABILITIES, H as BoardEffectSchema, I as parseDesktopCommand, J as BoardProceduralClipSchema, K as BoardCompositionInputSchema, L as parseDesktopCommandId, M as DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, N as DESKTOP_COMMAND_VERSION, O as DESKTOP_COMMAND_MAX_TIMEOUT_MS, P as isDesktopCallMethod, Q as BOARD_ARROW_STROKE_SIZE, R as BoardAuthoringItemSchema, S as createSessionGenerationStreamClient, T as createSessionPatchReducer, U as parseBoardEffectInput, V as BoardEffectInputSchema, W as BOARD_ANIMATION_CHANNELS, X as parseBoardCompositionInput, Y as BoardTrackSchema, Z as BoardConnectionSchema, _ as SpacesApi, a as AppCommerceApi, at as SearchApi, b as buildSpacePath, c as UiCommandsApi, ct as PromptsApi, d as UsersApi, dt as CronJobsApi, et as BOARD_BUILTIN_CLIP_KINDS, f as UserApi, ft as ChannelsApi, g as SpacePublicFilesApi, h as SpaceClient, i as scopeListHasPermission, it as ReferencesApi, j as DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, k as DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, l as AppsApi, lt as ModelsApi, m as BoardClient, n as createHttpClient, nt as DEFAULT_BOARD_RENDER_LIMITS, o as WorkCommerceApi, ot as PublicAssetsApi, p as TasksApi, q as BoardCompositionSchema, r as PERMISSIONS, rt as SessionAccessApi, s as DesktopCommandsApi, st as SkillsApi, t as CohubHttpClient, tt as BOARD_BUILTIN_EFFECT_KINDS, u as ReferralsApi, ut as GenerationsApi, v as PublicInviteApi, w as SessionPatchReducer, x as SessionGenerationStreamClient, y as buildSpaceInvitePath, z as BoardItemPatchSchema } from "./chunks/http.js";
|
|
2
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";
|
|
@@ -1150,7 +1150,8 @@ const RESERVED_PLATFORM_PATH_SEGMENTS = Object.freeze([
|
|
|
1150
1150
|
"user",
|
|
1151
1151
|
"users",
|
|
1152
1152
|
"work-auth",
|
|
1153
|
-
"app-auth"
|
|
1153
|
+
"app-auth",
|
|
1154
|
+
"zh"
|
|
1154
1155
|
]);
|
|
1155
1156
|
new Set(RESERVED_PLATFORM_PATH_SEGMENTS);
|
|
1156
1157
|
[...RESERVED_PLATFORM_PATH_SEGMENTS];
|
|
@@ -2144,35 +2145,54 @@ var PopupBrokerTransport = class {
|
|
|
2144
2145
|
}
|
|
2145
2146
|
};
|
|
2146
2147
|
const TOKEN_STORAGE_PREFIX = "cohub:app-token";
|
|
2147
|
-
const
|
|
2148
|
+
const AUTHORIZED_GRANTS_STORAGE_PREFIX = "cohub:app-auth-grants";
|
|
2149
|
+
/** The space a server response actually granted, when it says so. */
|
|
2150
|
+
const responseSpaceId = (value) => typeof value === "string" && value ? value : void 0;
|
|
2151
|
+
/**
|
|
2152
|
+
* Records one consent, keyed by its space. Newer entries replace older ones
|
|
2153
|
+
* for the same space, so implicit home-space requests (no `spaceId`) and
|
|
2154
|
+
* explicit ones converge onto a single entry once the server echoes its
|
|
2155
|
+
* canonical space id back.
|
|
2156
|
+
*/
|
|
2157
|
+
function recordConsent(grants, spaceId, scopes) {
|
|
2158
|
+
return [...(grants ?? []).filter((grant) => grant.spaceId !== spaceId), spaceId ? {
|
|
2159
|
+
spaceId,
|
|
2160
|
+
scopes
|
|
2161
|
+
} : { scopes }];
|
|
2162
|
+
}
|
|
2163
|
+
const isAuthorizedGrant = (value) => {
|
|
2164
|
+
if (!value || typeof value !== "object") return false;
|
|
2165
|
+
const record = value;
|
|
2166
|
+
return (record.spaceId === void 0 || typeof record.spaceId === "string") && Array.isArray(record.scopes) && record.scopes.every((scope) => typeof scope === "string");
|
|
2167
|
+
};
|
|
2148
2168
|
var AppRuntimeApi = class {
|
|
2149
2169
|
token = null;
|
|
2150
2170
|
transport;
|
|
2151
2171
|
tokenStorageKey;
|
|
2152
|
-
|
|
2172
|
+
grantsStorageKey;
|
|
2153
2173
|
appIdResolver;
|
|
2154
2174
|
/** Ensures storage keys are resolved (via slug lookup) at most once. */
|
|
2155
2175
|
storageKeysReady;
|
|
2156
|
-
/**
|
|
2157
|
-
* refreshes can re-authorize (preserving
|
|
2158
|
-
* back to a base session token that only carries
|
|
2159
|
-
|
|
2176
|
+
/** Consents previously granted via requestAuthorization, retained so token
|
|
2177
|
+
* refreshes can re-authorize (preserving viewer grants) instead of falling
|
|
2178
|
+
* back to a base session token that only carries app-side scopes. */
|
|
2179
|
+
authorizedGrants = null;
|
|
2160
2180
|
constructor(transport = new ParentBridgeTransport(), appId, appIdResolver) {
|
|
2161
2181
|
this.transport = transport;
|
|
2162
2182
|
this.appIdResolver = appIdResolver;
|
|
2163
2183
|
if (appId) {
|
|
2164
2184
|
this.tokenStorageKey = `${TOKEN_STORAGE_PREFIX}:${appId}`;
|
|
2165
|
-
this.
|
|
2185
|
+
this.grantsStorageKey = `${AUTHORIZED_GRANTS_STORAGE_PREFIX}:${appId}`;
|
|
2166
2186
|
this.storageKeysReady = Promise.resolve();
|
|
2167
2187
|
this.token = this.readStoredToken();
|
|
2168
|
-
this.
|
|
2188
|
+
this.authorizedGrants = this.readStoredGrants();
|
|
2169
2189
|
} else if (appIdResolver) {
|
|
2170
2190
|
this.tokenStorageKey = null;
|
|
2171
|
-
this.
|
|
2191
|
+
this.grantsStorageKey = null;
|
|
2172
2192
|
this.storageKeysReady = null;
|
|
2173
2193
|
} else {
|
|
2174
2194
|
this.tokenStorageKey = null;
|
|
2175
|
-
this.
|
|
2195
|
+
this.grantsStorageKey = null;
|
|
2176
2196
|
this.storageKeysReady = Promise.resolve();
|
|
2177
2197
|
}
|
|
2178
2198
|
}
|
|
@@ -2187,11 +2207,11 @@ var AppRuntimeApi = class {
|
|
|
2187
2207
|
const appId = this.appIdResolver ? await this.appIdResolver() : null;
|
|
2188
2208
|
if (appId) {
|
|
2189
2209
|
this.tokenStorageKey = `${TOKEN_STORAGE_PREFIX}:${appId}`;
|
|
2190
|
-
this.
|
|
2210
|
+
this.grantsStorageKey = `${AUTHORIZED_GRANTS_STORAGE_PREFIX}:${appId}`;
|
|
2191
2211
|
const stored = this.readStoredToken();
|
|
2192
2212
|
if (stored && !this.token) this.token = stored;
|
|
2193
|
-
const
|
|
2194
|
-
if (
|
|
2213
|
+
const storedGrants = this.readStoredGrants();
|
|
2214
|
+
if (storedGrants && !this.authorizedGrants) this.authorizedGrants = storedGrants;
|
|
2195
2215
|
}
|
|
2196
2216
|
})();
|
|
2197
2217
|
return this.storageKeysReady;
|
|
@@ -2211,22 +2231,23 @@ var AppRuntimeApi = class {
|
|
|
2211
2231
|
else localStorage.removeItem(this.tokenStorageKey);
|
|
2212
2232
|
} catch {}
|
|
2213
2233
|
}
|
|
2214
|
-
|
|
2215
|
-
if (!this.
|
|
2234
|
+
readStoredGrants() {
|
|
2235
|
+
if (!this.grantsStorageKey || typeof localStorage === "undefined") return null;
|
|
2216
2236
|
try {
|
|
2217
|
-
const raw = localStorage.getItem(this.
|
|
2237
|
+
const raw = localStorage.getItem(this.grantsStorageKey);
|
|
2218
2238
|
if (!raw) return null;
|
|
2219
2239
|
const parsed = JSON.parse(raw);
|
|
2220
|
-
|
|
2240
|
+
if (!Array.isArray(parsed) || !parsed.every(isAuthorizedGrant)) return null;
|
|
2241
|
+
return parsed;
|
|
2221
2242
|
} catch {
|
|
2222
2243
|
return null;
|
|
2223
2244
|
}
|
|
2224
2245
|
}
|
|
2225
|
-
|
|
2226
|
-
if (!this.
|
|
2246
|
+
writeStoredGrants(grants) {
|
|
2247
|
+
if (!this.grantsStorageKey || typeof localStorage === "undefined") return;
|
|
2227
2248
|
try {
|
|
2228
|
-
if (
|
|
2229
|
-
else localStorage.removeItem(this.
|
|
2249
|
+
if (grants && grants.length > 0) localStorage.setItem(this.grantsStorageKey, JSON.stringify(grants));
|
|
2250
|
+
else localStorage.removeItem(this.grantsStorageKey);
|
|
2230
2251
|
} catch {}
|
|
2231
2252
|
}
|
|
2232
2253
|
async context() {
|
|
@@ -2245,14 +2266,29 @@ var AppRuntimeApi = class {
|
|
|
2245
2266
|
this.token = null;
|
|
2246
2267
|
this.writeStoredToken(null);
|
|
2247
2268
|
}
|
|
2248
|
-
if (options?.forceRefresh && this.
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2269
|
+
if (options?.forceRefresh && this.authorizedGrants && this.authorizedGrants.length > 0) {
|
|
2270
|
+
let refreshed = false;
|
|
2271
|
+
let next = [];
|
|
2272
|
+
for (const grant of this.authorizedGrants) try {
|
|
2273
|
+
const response = await this.transport.request({
|
|
2274
|
+
type: "cohub.app.authorize",
|
|
2275
|
+
scopes: grant.scopes,
|
|
2276
|
+
spaceId: grant.spaceId
|
|
2277
|
+
}, { timeoutMs: 12e4 });
|
|
2278
|
+
const token = response?.token ?? null;
|
|
2279
|
+
if (!token) continue;
|
|
2280
|
+
this.token = token;
|
|
2281
|
+
next = recordConsent(next, responseSpaceId(response?.space?.id) ?? grant.spaceId, grant.scopes);
|
|
2282
|
+
refreshed = true;
|
|
2283
|
+
} catch {
|
|
2284
|
+
next = recordConsent(next, grant.spaceId, grant.scopes);
|
|
2285
|
+
}
|
|
2286
|
+
this.authorizedGrants = next;
|
|
2287
|
+
this.writeStoredGrants(next);
|
|
2288
|
+
if (refreshed) {
|
|
2289
|
+
this.writeStoredToken(this.token);
|
|
2290
|
+
return this.token;
|
|
2291
|
+
}
|
|
2256
2292
|
}
|
|
2257
2293
|
const response = await this.transport.request({
|
|
2258
2294
|
type: "cohub.app.token",
|
|
@@ -2262,20 +2298,63 @@ var AppRuntimeApi = class {
|
|
|
2262
2298
|
this.writeStoredToken(this.token);
|
|
2263
2299
|
return this.token;
|
|
2264
2300
|
}
|
|
2301
|
+
/**
|
|
2302
|
+
* Requests viewer consent. Without a `spaceId` the grant targets the app's
|
|
2303
|
+
* home space; with one it targets that space — the app may only grant what
|
|
2304
|
+
* the viewer can already do there themselves. Reuses a previous grant
|
|
2305
|
+
* silently unless `alwaysAsk` forces the consent dialog.
|
|
2306
|
+
*/
|
|
2265
2307
|
async requestAuthorization(input) {
|
|
2266
2308
|
await this.ensureStorageKeys();
|
|
2267
2309
|
const response = await this.transport.request({
|
|
2268
2310
|
type: "cohub.app.authorize",
|
|
2269
2311
|
scopes: input.scopes,
|
|
2270
|
-
reason: input.reason
|
|
2312
|
+
reason: input.reason,
|
|
2313
|
+
spaceId: input.spaceId,
|
|
2314
|
+
alwaysAsk: input.alwaysAsk
|
|
2271
2315
|
}, { timeoutMs: 12e4 });
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
this.
|
|
2276
|
-
|
|
2316
|
+
const token = response?.token ?? null;
|
|
2317
|
+
if (token) {
|
|
2318
|
+
this.token = token;
|
|
2319
|
+
this.writeStoredToken(token);
|
|
2320
|
+
const spaceId = responseSpaceId(response?.space?.id) ?? input.spaceId;
|
|
2321
|
+
this.authorizedGrants = recordConsent(this.authorizedGrants, spaceId, input.scopes);
|
|
2322
|
+
this.writeStoredGrants(this.authorizedGrants);
|
|
2277
2323
|
}
|
|
2278
|
-
return Boolean(
|
|
2324
|
+
return Boolean(token);
|
|
2325
|
+
}
|
|
2326
|
+
/**
|
|
2327
|
+
* Asks the viewer to pick a Space and grant the scopes on it — one consent
|
|
2328
|
+
* dialog covers both. Resolves with the picked space so the app knows where
|
|
2329
|
+
* it may act; `space` is null when the viewer denied.
|
|
2330
|
+
*/
|
|
2331
|
+
async requestSpaceAuthorization(input) {
|
|
2332
|
+
await this.ensureStorageKeys();
|
|
2333
|
+
const response = await this.transport.request({
|
|
2334
|
+
type: "cohub.app.authorize",
|
|
2335
|
+
scopes: input.scopes,
|
|
2336
|
+
reason: input.reason,
|
|
2337
|
+
selectSpace: true,
|
|
2338
|
+
alwaysAsk: input.alwaysAsk
|
|
2339
|
+
}, { timeoutMs: 12e4 });
|
|
2340
|
+
const token = response?.token ?? null;
|
|
2341
|
+
const spaceId = typeof response?.space?.id === "string" ? response.space.id : null;
|
|
2342
|
+
const spaceName = typeof response?.space?.name === "string" ? response.space.name : null;
|
|
2343
|
+
if (token) {
|
|
2344
|
+
this.token = token;
|
|
2345
|
+
this.writeStoredToken(token);
|
|
2346
|
+
}
|
|
2347
|
+
if (token && spaceId) {
|
|
2348
|
+
this.authorizedGrants = recordConsent(this.authorizedGrants, spaceId, input.scopes);
|
|
2349
|
+
this.writeStoredGrants(this.authorizedGrants);
|
|
2350
|
+
}
|
|
2351
|
+
return {
|
|
2352
|
+
granted: Boolean(token),
|
|
2353
|
+
space: spaceId ? {
|
|
2354
|
+
id: spaceId,
|
|
2355
|
+
name: spaceName
|
|
2356
|
+
} : null
|
|
2357
|
+
};
|
|
2279
2358
|
}
|
|
2280
2359
|
async purchase(input) {
|
|
2281
2360
|
const purchaseAttemptId = input.purchaseAttemptId?.trim() || generateRequestId();
|
|
@@ -2438,7 +2517,12 @@ var CohubClient = class {
|
|
|
2438
2517
|
context() {
|
|
2439
2518
|
return this.appRuntime.context();
|
|
2440
2519
|
}
|
|
2441
|
-
auth = {
|
|
2520
|
+
auth = {
|
|
2521
|
+
/** Ensure the app holds these scopes. Silent when a grant already covers them; `alwaysAsk` forces the dialog. */
|
|
2522
|
+
request: (input) => this.appRuntime.requestAuthorization(input),
|
|
2523
|
+
/** One consent: the viewer picks a Space and grants the scopes on it. `alwaysAsk` re-opens the picker. */
|
|
2524
|
+
requestSpace: (input) => this.appRuntime.requestSpaceAuthorization(input)
|
|
2525
|
+
};
|
|
2442
2526
|
app = {
|
|
2443
2527
|
realtime: null,
|
|
2444
2528
|
/** Expose callable methods from inside a published app. */
|
|
@@ -2634,8 +2718,9 @@ const MAX_AGE_MS = 336 * 60 * 60 * 1e3;
|
|
|
2634
2718
|
function isBrowser() {
|
|
2635
2719
|
return typeof localStorage !== "undefined";
|
|
2636
2720
|
}
|
|
2637
|
-
function storageKey(userUuid, appId) {
|
|
2638
|
-
|
|
2721
|
+
function storageKey(userUuid, appId, spaceId) {
|
|
2722
|
+
const base = `${STORAGE_PREFIX}:${encodeURIComponent(userUuid)}:${encodeURIComponent(appId)}`;
|
|
2723
|
+
return spaceId ? `${base}:${encodeURIComponent(spaceId)}:v${CACHE_VERSION}` : `${base}:v${CACHE_VERSION}`;
|
|
2639
2724
|
}
|
|
2640
2725
|
function userPrefix(userUuid) {
|
|
2641
2726
|
return `${STORAGE_PREFIX}:${encodeURIComponent(userUuid)}:`;
|
|
@@ -2643,19 +2728,19 @@ function userPrefix(userUuid) {
|
|
|
2643
2728
|
function isPermissionArray(value) {
|
|
2644
2729
|
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
2645
2730
|
}
|
|
2646
|
-
function
|
|
2731
|
+
function isCachedAppGrant(value) {
|
|
2647
2732
|
if (!value || typeof value !== "object") return false;
|
|
2648
2733
|
const record = value;
|
|
2649
2734
|
return record.version === CACHE_VERSION && typeof record.userUuid === "string" && typeof record.appId === "string" && isPermissionArray(record.scopes) && typeof record.updatedAt === "number";
|
|
2650
2735
|
}
|
|
2651
|
-
function readEntry(userUuid, appId) {
|
|
2736
|
+
function readEntry(userUuid, appId, spaceId) {
|
|
2652
2737
|
if (!isBrowser()) return null;
|
|
2653
|
-
const key = storageKey(userUuid, appId);
|
|
2738
|
+
const key = storageKey(userUuid, appId, spaceId);
|
|
2654
2739
|
try {
|
|
2655
2740
|
const raw = localStorage.getItem(key);
|
|
2656
2741
|
if (!raw) return null;
|
|
2657
2742
|
const parsed = JSON.parse(raw);
|
|
2658
|
-
if (!
|
|
2743
|
+
if (!isCachedAppGrant(parsed) || parsed.userUuid !== userUuid || parsed.appId !== appId) {
|
|
2659
2744
|
localStorage.removeItem(key);
|
|
2660
2745
|
return null;
|
|
2661
2746
|
}
|
|
@@ -2673,44 +2758,86 @@ function readEntry(userUuid, appId) {
|
|
|
2673
2758
|
}
|
|
2674
2759
|
/**
|
|
2675
2760
|
* Returns true when the viewer has previously granted every requested scope
|
|
2676
|
-
* for this
|
|
2761
|
+
* for this app on the target space, allowing a silent re-authorization.
|
|
2677
2762
|
*/
|
|
2678
|
-
function hasGrantedAppScopes(userUuid, appId, scopes) {
|
|
2763
|
+
function hasGrantedAppScopes(userUuid, appId, scopes, spaceId) {
|
|
2679
2764
|
if (!userUuid || !appId || scopes.length === 0) return false;
|
|
2680
|
-
const entry = readEntry(userUuid, appId);
|
|
2765
|
+
const entry = readEntry(userUuid, appId, spaceId);
|
|
2681
2766
|
if (!entry) return false;
|
|
2682
|
-
|
|
2683
|
-
return scopes.every((scope) => granted.has(scope));
|
|
2767
|
+
return scopes.every((scope) => scopeListHasPermission(entry.scopes, scope));
|
|
2684
2768
|
}
|
|
2685
2769
|
/**
|
|
2686
|
-
*
|
|
2687
|
-
*
|
|
2770
|
+
* Lists every cached grant for an app — one entry per space — so hosts can
|
|
2771
|
+
* report what the viewer previously consented to without a server round trip.
|
|
2772
|
+
* Entries past their re-consent window are dropped on read.
|
|
2688
2773
|
*/
|
|
2689
|
-
function
|
|
2774
|
+
function listGrantedAppScopes(userUuid, appId, homeSpaceId) {
|
|
2775
|
+
if (!userUuid || !appId || !isBrowser()) return [];
|
|
2776
|
+
const prefix = `${STORAGE_PREFIX}:${encodeURIComponent(userUuid)}:${encodeURIComponent(appId)}:`;
|
|
2777
|
+
const grants = [];
|
|
2778
|
+
try {
|
|
2779
|
+
for (let i = 0; i < localStorage.length; i += 1) {
|
|
2780
|
+
const key = localStorage.key(i);
|
|
2781
|
+
if (!key?.startsWith(prefix)) continue;
|
|
2782
|
+
const spaceId = key.slice(prefix.length).match(/^(?:(.*):)?v\d+$/)?.[1] || homeSpaceId;
|
|
2783
|
+
if (!spaceId) continue;
|
|
2784
|
+
const raw = localStorage.getItem(key);
|
|
2785
|
+
if (!raw) continue;
|
|
2786
|
+
const parsed = JSON.parse(raw);
|
|
2787
|
+
if (!isCachedAppGrant(parsed) || parsed.userUuid !== userUuid || parsed.appId !== appId || Date.now() - parsed.updatedAt > MAX_AGE_MS) continue;
|
|
2788
|
+
grants.push({
|
|
2789
|
+
spaceId: decodeURIComponent(spaceId),
|
|
2790
|
+
scopes: parsed.scopes
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2793
|
+
} catch {}
|
|
2794
|
+
return grants;
|
|
2795
|
+
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Records the granted scopes for an app on a space. Replaces any previously
|
|
2798
|
+
* cached scopes so the cache mirrors the server row exactly — an explicit
|
|
2799
|
+
* consent with fewer scopes must narrow the cache too, or silent reuse could
|
|
2800
|
+
* hand back permissions the viewer just removed.
|
|
2801
|
+
*/
|
|
2802
|
+
function setGrantedAppScopes(userUuid, appId, scopes, spaceId) {
|
|
2690
2803
|
if (!userUuid || !appId || scopes.length === 0) return;
|
|
2691
|
-
const existing = readEntry(userUuid, appId);
|
|
2692
|
-
const merged = Array.from(/* @__PURE__ */ new Set([...existing?.scopes ?? [], ...scopes]));
|
|
2693
2804
|
const entry = {
|
|
2694
2805
|
version: CACHE_VERSION,
|
|
2695
2806
|
userUuid,
|
|
2696
2807
|
appId,
|
|
2697
|
-
scopes:
|
|
2808
|
+
scopes: Array.from(new Set(scopes)),
|
|
2698
2809
|
updatedAt: Date.now()
|
|
2699
2810
|
};
|
|
2700
2811
|
if (!isBrowser()) return;
|
|
2701
2812
|
try {
|
|
2702
|
-
localStorage.setItem(storageKey(userUuid, appId), JSON.stringify(entry));
|
|
2813
|
+
localStorage.setItem(storageKey(userUuid, appId, spaceId), JSON.stringify(entry));
|
|
2814
|
+
} catch {}
|
|
2815
|
+
}
|
|
2816
|
+
/**
|
|
2817
|
+
* Synchronizes a cached grant with the server while preserving the last
|
|
2818
|
+
* explicit-consent timestamp. Also migrates legacy keys to the canonical Space.
|
|
2819
|
+
*/
|
|
2820
|
+
function syncGrantedAppScopes(userUuid, appId, fromSpaceId, toSpaceId, scopes) {
|
|
2821
|
+
if (!userUuid || !appId || !toSpaceId || scopes.length === 0 || !isBrowser()) return;
|
|
2822
|
+
const entry = readEntry(userUuid, appId, toSpaceId) ?? readEntry(userUuid, appId, fromSpaceId);
|
|
2823
|
+
if (!entry) return;
|
|
2824
|
+
try {
|
|
2825
|
+
localStorage.setItem(storageKey(userUuid, appId, toSpaceId), JSON.stringify({
|
|
2826
|
+
...entry,
|
|
2827
|
+
scopes: Array.from(new Set(scopes))
|
|
2828
|
+
}));
|
|
2829
|
+
if (fromSpaceId !== toSpaceId) localStorage.removeItem(storageKey(userUuid, appId, fromSpaceId));
|
|
2703
2830
|
} catch {}
|
|
2704
2831
|
}
|
|
2705
2832
|
/**
|
|
2706
|
-
* Clears cached grants. Pass
|
|
2707
|
-
* clear every cached grant for the user (
|
|
2833
|
+
* Clears cached grants. Pass an appId to clear one app (optionally one
|
|
2834
|
+
* space), or omit it to clear every cached grant for the user (sign-out).
|
|
2708
2835
|
*/
|
|
2709
|
-
function clearGrantedAppScopes(userUuid, appId) {
|
|
2836
|
+
function clearGrantedAppScopes(userUuid, appId, spaceId) {
|
|
2710
2837
|
if (!isBrowser() || !userUuid) return;
|
|
2711
2838
|
try {
|
|
2712
2839
|
if (appId) {
|
|
2713
|
-
localStorage.removeItem(storageKey(userUuid, appId));
|
|
2840
|
+
localStorage.removeItem(storageKey(userUuid, appId, spaceId));
|
|
2714
2841
|
return;
|
|
2715
2842
|
}
|
|
2716
2843
|
const prefix = userPrefix(userUuid);
|
|
@@ -2731,6 +2858,42 @@ function clonePermissionScopes(scopes) {
|
|
|
2731
2858
|
return Array.from(scopes ?? []).filter((scope) => typeof scope === "string");
|
|
2732
2859
|
}
|
|
2733
2860
|
/**
|
|
2861
|
+
* Scopes arriving over postMessage are untrusted: keep only known permission
|
|
2862
|
+
* names (in first-seen order, deduplicated) so a malicious app cannot push
|
|
2863
|
+
* arbitrary strings, duplicates, or oversized arrays into the consent dialog.
|
|
2864
|
+
*/
|
|
2865
|
+
function sanitizeRequestedScopes(value) {
|
|
2866
|
+
if (!Array.isArray(value)) return [];
|
|
2867
|
+
const known = new Set(PERMISSIONS);
|
|
2868
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2869
|
+
for (const scope of value) if (typeof scope === "string" && known.has(scope)) seen.add(scope);
|
|
2870
|
+
return Array.from(seen);
|
|
2871
|
+
}
|
|
2872
|
+
/** Consent dialogs render the reason; keep hostile input bounded. */
|
|
2873
|
+
const MAX_REASON_LENGTH = 280;
|
|
2874
|
+
var AppAuthorizationError = class extends Error {
|
|
2875
|
+
status;
|
|
2876
|
+
constructor(message, status) {
|
|
2877
|
+
super(message);
|
|
2878
|
+
this.status = status;
|
|
2879
|
+
this.name = "AppAuthorizationError";
|
|
2880
|
+
}
|
|
2881
|
+
};
|
|
2882
|
+
const isDefinitiveAuthorizationFailure = (error) => error instanceof AppAuthorizationError && [
|
|
2883
|
+
401,
|
|
2884
|
+
403,
|
|
2885
|
+
404
|
|
2886
|
+
].includes(error.status);
|
|
2887
|
+
function sanitizeReason(value) {
|
|
2888
|
+
if (typeof value !== "string") return void 0;
|
|
2889
|
+
const trimmed = value.trim();
|
|
2890
|
+
if (!trimmed) return void 0;
|
|
2891
|
+
return trimmed.length > MAX_REASON_LENGTH ? trimmed.slice(0, MAX_REASON_LENGTH) : trimmed;
|
|
2892
|
+
}
|
|
2893
|
+
function normalizePermissionScopes(scopes) {
|
|
2894
|
+
return Array.from(new Set(clonePermissionScopes(scopes)));
|
|
2895
|
+
}
|
|
2896
|
+
/**
|
|
2734
2897
|
* Framework-agnostic app bridge host core — message handling, app session
|
|
2735
2898
|
* token minting, authorization (with silent re-grant cache), and
|
|
2736
2899
|
* purchase/checkout flow — without any rendering or reactive primitives.
|
|
@@ -2745,6 +2908,8 @@ function createAppBridgeCore(config) {
|
|
|
2745
2908
|
const authorizationContext = config.authorizationContext ?? (config.isBackground ? { surface: "background" } : { surface: "page" });
|
|
2746
2909
|
const onStateChange = config.onStateChange;
|
|
2747
2910
|
let appToken = null;
|
|
2911
|
+
/** Consents made through this host, keyed by target space. */
|
|
2912
|
+
const sessionViewerGrants = /* @__PURE__ */ new Map();
|
|
2748
2913
|
let activeInvocation;
|
|
2749
2914
|
let contextChangeVersion = 0;
|
|
2750
2915
|
const legacyRequestIds = /* @__PURE__ */ new Set();
|
|
@@ -2752,6 +2917,7 @@ function createAppBridgeCore(config) {
|
|
|
2752
2917
|
const invocation = activeInvocation !== void 0 ? activeInvocation : config.getInvocation?.() ?? config.invocation;
|
|
2753
2918
|
const appScopes = clonePermissionScopes(app.appScopes);
|
|
2754
2919
|
const viewerUuid = await getViewerUuid();
|
|
2920
|
+
const viewerGrants = mergeViewerGrants(viewerUuid, Array.from(sessionViewerGrants.values()));
|
|
2755
2921
|
return {
|
|
2756
2922
|
app: {
|
|
2757
2923
|
id: app.id,
|
|
@@ -2762,9 +2928,24 @@ function createAppBridgeCore(config) {
|
|
|
2762
2928
|
viewer: viewerUuid ? { userUuid: viewerUuid } : null,
|
|
2763
2929
|
...invocation ? { invocation: { ...invocation } } : {},
|
|
2764
2930
|
permissions: {
|
|
2765
|
-
scopes: appScopes,
|
|
2931
|
+
scopes: normalizePermissionScopes([...appScopes, ...viewerGrants.flatMap((grant) => grant.scopes)]),
|
|
2766
2932
|
appScopes,
|
|
2767
|
-
viewerScopes:
|
|
2933
|
+
viewerScopes: normalizePermissionScopes(viewerGrants.flatMap((grant) => grant.scopes)),
|
|
2934
|
+
viewerGrants
|
|
2935
|
+
}
|
|
2936
|
+
};
|
|
2937
|
+
}
|
|
2938
|
+
/** Builds an authorize reply; the target space rides along for the app. */
|
|
2939
|
+
function authorizeResult(token, spaceId, spaceName) {
|
|
2940
|
+
return {
|
|
2941
|
+
type: "cohub.app.authorize.result",
|
|
2942
|
+
token,
|
|
2943
|
+
space: spaceId ? {
|
|
2944
|
+
id: spaceId,
|
|
2945
|
+
name: spaceName
|
|
2946
|
+
} : {
|
|
2947
|
+
id: app.spaceId,
|
|
2948
|
+
name: app.spaceName ?? null
|
|
2768
2949
|
}
|
|
2769
2950
|
};
|
|
2770
2951
|
}
|
|
@@ -2821,6 +3002,12 @@ function createAppBridgeCore(config) {
|
|
|
2821
3002
|
const viewerUuid = await getViewerUuid();
|
|
2822
3003
|
return Boolean(viewerUuid && viewerUuid === app.userUuid);
|
|
2823
3004
|
}
|
|
3005
|
+
function mergeViewerGrants(viewerUuid, sessionGrants) {
|
|
3006
|
+
const bySpace = /* @__PURE__ */ new Map();
|
|
3007
|
+
for (const grant of listGrantedAppScopes(viewerUuid, app.id, app.spaceId)) bySpace.set(grant.spaceId, grant);
|
|
3008
|
+
for (const grant of sessionGrants) bySpace.set(grant.spaceId, grant);
|
|
3009
|
+
return Array.from(bySpace.values());
|
|
3010
|
+
}
|
|
2824
3011
|
function allowsOwnerAutoAuthorization() {
|
|
2825
3012
|
return authorizationContext.surface === "background" || authorizationContext.surface === "app";
|
|
2826
3013
|
}
|
|
@@ -2841,11 +3028,17 @@ function createAppBridgeCore(config) {
|
|
|
2841
3028
|
appToken = token;
|
|
2842
3029
|
return appToken;
|
|
2843
3030
|
}
|
|
2844
|
-
|
|
3031
|
+
/**
|
|
3032
|
+
* Calls the authorize endpoint. `silent` marks a background refresh of a
|
|
3033
|
+
* previous consent: the server then only renews a live grant and never
|
|
3034
|
+
* creates or revives one, so a revoked grant cannot come back without a
|
|
3035
|
+
* fresh dialog.
|
|
3036
|
+
*/
|
|
3037
|
+
async function authorize(scopes, spaceId, options) {
|
|
2845
3038
|
const userToken = await getAccessToken();
|
|
2846
3039
|
if (!userToken) {
|
|
2847
3040
|
await config.requestSignIn(typeof location !== "undefined" ? location.pathname : "/");
|
|
2848
|
-
|
|
3041
|
+
throw new Error("Sign in is required to authorize this app.");
|
|
2849
3042
|
}
|
|
2850
3043
|
const response = await fetch(`${apiOrigin}/api/apps/${app.id}/authorize`, {
|
|
2851
3044
|
method: "POST",
|
|
@@ -2853,13 +3046,84 @@ function createAppBridgeCore(config) {
|
|
|
2853
3046
|
Authorization: `Bearer ${userToken}`,
|
|
2854
3047
|
"Content-Type": "application/json"
|
|
2855
3048
|
},
|
|
2856
|
-
body: JSON.stringify({
|
|
3049
|
+
body: JSON.stringify({
|
|
3050
|
+
scopes,
|
|
3051
|
+
...spaceId ? { spaceId } : {},
|
|
3052
|
+
...options?.silent ? { silent: true } : {}
|
|
3053
|
+
})
|
|
2857
3054
|
});
|
|
2858
|
-
|
|
2859
|
-
|
|
3055
|
+
const payload = await response.json().catch(() => null);
|
|
3056
|
+
if (!response.ok) throw new AppAuthorizationError(typeof payload?.message === "string" ? payload.message : "Authorization failed.", response.status);
|
|
3057
|
+
const token = readTokenResponse(payload);
|
|
2860
3058
|
if (!token) throw new Error("Invalid app authorization response.");
|
|
3059
|
+
const canonicalSpaceId = typeof payload?.grant?.spaceId === "string" && payload.grant.spaceId ? payload.grant.spaceId : spaceId ?? app.spaceId;
|
|
3060
|
+
const grantedScopes = sanitizeRequestedScopes(payload?.grant?.scopes);
|
|
3061
|
+
const canonicalScopes = grantedScopes.length > 0 ? grantedScopes : clonePermissionScopes(scopes);
|
|
2861
3062
|
appToken = token;
|
|
2862
|
-
|
|
3063
|
+
sessionViewerGrants.set(canonicalSpaceId, {
|
|
3064
|
+
spaceId: canonicalSpaceId,
|
|
3065
|
+
scopes: canonicalScopes
|
|
3066
|
+
});
|
|
3067
|
+
syncGrantedAppScopes(await getViewerUuid(), app.id, spaceId, canonicalSpaceId, canonicalScopes);
|
|
3068
|
+
return {
|
|
3069
|
+
token,
|
|
3070
|
+
spaceId: canonicalSpaceId,
|
|
3071
|
+
scopes: canonicalScopes
|
|
3072
|
+
};
|
|
3073
|
+
}
|
|
3074
|
+
/**
|
|
3075
|
+
* Lists the viewer's spaces for the consent dialog picker. Fetched by the
|
|
3076
|
+
* host with the viewer's own token — the app only ever learns the space
|
|
3077
|
+
* the viewer picks, never the list.
|
|
3078
|
+
*/
|
|
3079
|
+
async function listViewerSpaces() {
|
|
3080
|
+
const userToken = await getAccessToken();
|
|
3081
|
+
if (!userToken) return null;
|
|
3082
|
+
try {
|
|
3083
|
+
const response = await fetch(`${apiOrigin}/api/spaces`, { headers: { Authorization: `Bearer ${userToken}` } });
|
|
3084
|
+
if (!response.ok) return null;
|
|
3085
|
+
const spaces = await response.json();
|
|
3086
|
+
if (!Array.isArray(spaces)) return null;
|
|
3087
|
+
return spaces.flatMap((space) => {
|
|
3088
|
+
if (typeof space.id !== "string" || !space.id) return [];
|
|
3089
|
+
return [{
|
|
3090
|
+
id: space.id,
|
|
3091
|
+
name: typeof space.name === "string" && space.name ? space.name : null
|
|
3092
|
+
}];
|
|
3093
|
+
});
|
|
3094
|
+
} catch {
|
|
3095
|
+
return null;
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
const pickedSpaceStorageKey = `cohub:app-picked-space:${app.id}`;
|
|
3099
|
+
function readLastPickedSpace() {
|
|
3100
|
+
try {
|
|
3101
|
+
return localStorage.getItem(pickedSpaceStorageKey);
|
|
3102
|
+
} catch {
|
|
3103
|
+
return null;
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
function writeLastPickedSpace(spaceId) {
|
|
3107
|
+
try {
|
|
3108
|
+
localStorage.setItem(pickedSpaceStorageKey, spaceId);
|
|
3109
|
+
} catch {}
|
|
3110
|
+
}
|
|
3111
|
+
/**
|
|
3112
|
+
* Resolves the target space's name for the consent dialog. The host — not
|
|
3113
|
+
* the app — resolves it, so the dialog cannot be tricked into labeling a
|
|
3114
|
+
* grant with the wrong space.
|
|
3115
|
+
*/
|
|
3116
|
+
async function resolveSpaceName(spaceId) {
|
|
3117
|
+
const userToken = await getAccessToken();
|
|
3118
|
+
if (!userToken) return null;
|
|
3119
|
+
try {
|
|
3120
|
+
const response = await fetch(`${apiOrigin}/api/spaces/${encodeURIComponent(spaceId)}`, { headers: { Authorization: `Bearer ${userToken}` } });
|
|
3121
|
+
if (!response.ok) return null;
|
|
3122
|
+
const space = await response.json();
|
|
3123
|
+
return typeof space.name === "string" && space.name ? space.name : null;
|
|
3124
|
+
} catch {
|
|
3125
|
+
return null;
|
|
3126
|
+
}
|
|
2863
3127
|
}
|
|
2864
3128
|
function writePendingPurchase(input) {
|
|
2865
3129
|
if (typeof sessionStorage === "undefined") return;
|
|
@@ -2972,38 +3236,60 @@ function createAppBridgeCore(config) {
|
|
|
2972
3236
|
config.onPurchaseRequested?.({ ...state.pendingPurchase });
|
|
2973
3237
|
}
|
|
2974
3238
|
if (data.type === "cohub.app.authorize" || data.type === "cohub.work.authorize") {
|
|
2975
|
-
const
|
|
2976
|
-
const
|
|
3239
|
+
const scopes = sanitizeRequestedScopes(data.scopes);
|
|
3240
|
+
const spaceId = typeof data.spaceId === "string" && data.spaceId ? data.spaceId : void 0;
|
|
3241
|
+
const selectSpace = data.selectSpace === true && !spaceId;
|
|
3242
|
+
const alwaysAsk = data.alwaysAsk === true;
|
|
2977
3243
|
if (scopes.length === 0) {
|
|
2978
3244
|
replyForRequest(data.requestId, {
|
|
2979
3245
|
type: "cohub.app.error",
|
|
2980
|
-
message: "No
|
|
3246
|
+
message: "No scopes requested."
|
|
2981
3247
|
}, true);
|
|
2982
3248
|
return;
|
|
2983
3249
|
}
|
|
2984
|
-
if (allowsOwnerAutoAuthorization() && await isCurrentViewerAppOwner()) {
|
|
2985
|
-
const
|
|
2986
|
-
replyForRequest(data.requestId,
|
|
2987
|
-
type: "cohub.app.authorize.result",
|
|
2988
|
-
token
|
|
2989
|
-
}, true);
|
|
3250
|
+
if (!selectSpace && !alwaysAsk && allowsOwnerAutoAuthorization() && await isCurrentViewerAppOwner()) try {
|
|
3251
|
+
const result = await authorize(scopes, spaceId, { silent: true });
|
|
3252
|
+
replyForRequest(data.requestId, authorizeResult(result.token, result.spaceId, result.spaceId === app.spaceId ? app.spaceName ?? null : null), true);
|
|
2990
3253
|
return;
|
|
3254
|
+
} catch (error) {
|
|
3255
|
+
if (!isDefinitiveAuthorizationFailure(error)) {
|
|
3256
|
+
replyForRequest(data.requestId, {
|
|
3257
|
+
type: "cohub.app.error",
|
|
3258
|
+
message: error instanceof Error ? error.message : "Authorization failed."
|
|
3259
|
+
}, true);
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
2991
3262
|
}
|
|
2992
3263
|
const viewerUuid = await getViewerUuid();
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
token
|
|
2998
|
-
}, true);
|
|
3264
|
+
const silentSpaceId = selectSpace ? readLastPickedSpace() : spaceId;
|
|
3265
|
+
if (!alwaysAsk && viewerUuid && (selectSpace ? Boolean(silentSpaceId) : true) && silentSpaceId !== null && hasGrantedAppScopes(viewerUuid, app.id, scopes, silentSpaceId ?? void 0)) try {
|
|
3266
|
+
const result = await authorize(scopes, silentSpaceId, { silent: true });
|
|
3267
|
+
replyForRequest(data.requestId, authorizeResult(result.token, result.spaceId, null), true);
|
|
2999
3268
|
return;
|
|
3000
|
-
} catch {
|
|
3001
|
-
|
|
3269
|
+
} catch (error) {
|
|
3270
|
+
if (!isDefinitiveAuthorizationFailure(error)) {
|
|
3271
|
+
replyForRequest(data.requestId, {
|
|
3272
|
+
type: "cohub.app.error",
|
|
3273
|
+
message: error instanceof Error ? error.message : "Authorization failed."
|
|
3274
|
+
}, true);
|
|
3275
|
+
return;
|
|
3276
|
+
}
|
|
3277
|
+
clearGrantedAppScopes(viewerUuid, app.id, silentSpaceId);
|
|
3002
3278
|
}
|
|
3279
|
+
const [spaceName, spaces] = await Promise.all([spaceId ? resolveSpaceName(spaceId) : Promise.resolve(null), selectSpace ? listViewerSpaces() : Promise.resolve(void 0)]);
|
|
3003
3280
|
state.pendingAuth = {
|
|
3004
3281
|
requestId: data.requestId,
|
|
3005
3282
|
scopes,
|
|
3006
|
-
reason: data.reason
|
|
3283
|
+
reason: sanitizeReason(data.reason),
|
|
3284
|
+
homeSpaceName: app.spaceName ?? null,
|
|
3285
|
+
...spaceId ? {
|
|
3286
|
+
spaceId,
|
|
3287
|
+
spaceName
|
|
3288
|
+
} : {},
|
|
3289
|
+
...selectSpace ? {
|
|
3290
|
+
selectSpace: true,
|
|
3291
|
+
spaces: spaces ?? null
|
|
3292
|
+
} : {}
|
|
3007
3293
|
};
|
|
3008
3294
|
state.authError = null;
|
|
3009
3295
|
state.authOpen = true;
|
|
@@ -3078,18 +3364,24 @@ function createAppBridgeCore(config) {
|
|
|
3078
3364
|
notify();
|
|
3079
3365
|
}
|
|
3080
3366
|
}
|
|
3081
|
-
async function confirmAuth() {
|
|
3367
|
+
async function confirmAuth(pickedSpaceId) {
|
|
3082
3368
|
if (!state.pendingAuth || state.authSaving) return;
|
|
3369
|
+
const pending = state.pendingAuth;
|
|
3370
|
+
if (pending.selectSpace && !pickedSpaceId) {
|
|
3371
|
+
state.authError = "Pick a Space to continue.";
|
|
3372
|
+
notify();
|
|
3373
|
+
return;
|
|
3374
|
+
}
|
|
3083
3375
|
state.authError = null;
|
|
3084
3376
|
state.authSaving = true;
|
|
3085
3377
|
notify();
|
|
3086
3378
|
try {
|
|
3087
|
-
const
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3379
|
+
const requestedSpaceId = pending.selectSpace ? pickedSpaceId : pending.spaceId;
|
|
3380
|
+
const result = await authorize(pending.scopes, requestedSpaceId);
|
|
3381
|
+
setGrantedAppScopes(await getViewerUuid(), app.id, result.scopes, result.spaceId);
|
|
3382
|
+
if (pending.selectSpace) writeLastPickedSpace(result.spaceId);
|
|
3383
|
+
const spaceName = pending.selectSpace ? pending.spaces?.find((space) => space.id === result.spaceId)?.name ?? null : pending.spaceName ?? app.spaceName ?? null;
|
|
3384
|
+
replyForRequest(pending.requestId, authorizeResult(result.token, result.spaceId, spaceName), true);
|
|
3093
3385
|
state.authOpen = false;
|
|
3094
3386
|
state.pendingAuth = null;
|
|
3095
3387
|
} catch (error) {
|
|
@@ -3343,4 +3635,4 @@ function createBoardExtensionRegistry() {
|
|
|
3343
3635
|
}
|
|
3344
3636
|
const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
|
|
3345
3637
|
//#endregion
|
|
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 };
|
|
3638
|
+
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, PERMISSIONS, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, ReferencesApi, ReferralsApi, SessionGenerationStreamClient, SessionPatchReducer, SpacePublicFilesApi, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkSurfaceApi, assertGenerationRequestAllowedByPolicy, buildAppSurfaceRequest, buildSpaceInvitePath, buildSpacePath, clearGrantedAppScopes, compileComposition, composition, createAppBridgeCore, createAppBridgeCore as createWorkBridgeCore, createAppRuntime, createAppRuntime as createWorkRuntime, createBoardExtensionRegistry, createCohubClient, createHttpClient, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugAppIdResolver, createSlugAppIdResolver as createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, decodeGenerationPolicy, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, findGenerationModelPolicy, formatAppRef, formatWorkRef, getAllowedGenerationModelIds, 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, scopeListHasPermission, setGrantedAppScopes, track };
|