@neta-art/cohub 7.0.0 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/http.d.ts +328 -226
- package/dist/chunks/http.js +227 -194
- package/dist/chunks/transport.js +2 -1
- package/dist/chunks/websocket.d.ts +96 -77
- package/dist/http.d.ts +3 -3
- package/dist/index.d.ts +165 -119
- package/dist/index.js +409 -286
- package/dist/protocol/dist/index.d.ts +1 -1
- package/dist/protocol/dist/realtime/types.d.ts +1 -1
- package/package.json +1 -1
- /package/dist/protocol/dist/{work.d.ts → app.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ 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
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";
|
|
@@ -1149,7 +1149,8 @@ const RESERVED_PLATFORM_PATH_SEGMENTS = Object.freeze([
|
|
|
1149
1149
|
"u",
|
|
1150
1150
|
"user",
|
|
1151
1151
|
"users",
|
|
1152
|
-
"work-auth"
|
|
1152
|
+
"work-auth",
|
|
1153
|
+
"app-auth"
|
|
1153
1154
|
]);
|
|
1154
1155
|
new Set(RESERVED_PLATFORM_PATH_SEGMENTS);
|
|
1155
1156
|
[...RESERVED_PLATFORM_PATH_SEGMENTS];
|
|
@@ -1164,34 +1165,34 @@ function parseSpaceSlug(value) {
|
|
|
1164
1165
|
return SPACE_SLUG_PATTERN.test(normalized) ? normalized : null;
|
|
1165
1166
|
}
|
|
1166
1167
|
//#endregion
|
|
1167
|
-
//#region ../protocol/dist/
|
|
1168
|
-
const
|
|
1169
|
-
const
|
|
1170
|
-
const
|
|
1171
|
-
const
|
|
1172
|
-
const
|
|
1173
|
-
const
|
|
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;
|
|
1174
1175
|
const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1175
|
-
const isSurfaceEnvelope = (value) => isRecord(value) && value.protocol === "cohub.surface" && value.version === 1;
|
|
1176
|
-
const
|
|
1176
|
+
const isSurfaceEnvelope = (value) => isRecord(value) && value.protocol === "cohub.app.surface" && value.version === 1;
|
|
1177
|
+
const parseAppSurfaceReady = (value) => {
|
|
1177
1178
|
if (!isSurfaceEnvelope(value) || value.type !== "ready") return null;
|
|
1178
1179
|
const methods = Array.isArray(value.methods) ? value.methods.filter((method) => typeof method === "string" && Boolean(method)) : [];
|
|
1179
1180
|
return {
|
|
1180
|
-
protocol:
|
|
1181
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1181
1182
|
version: 1,
|
|
1182
1183
|
type: "ready",
|
|
1183
1184
|
methods
|
|
1184
1185
|
};
|
|
1185
1186
|
};
|
|
1186
|
-
const
|
|
1187
|
+
const parseAppSurfaceResponse = (value) => {
|
|
1187
1188
|
if (!isSurfaceEnvelope(value) || value.type !== "response") return null;
|
|
1188
1189
|
if (typeof value.requestId !== "string" || !value.requestId) return null;
|
|
1189
1190
|
const error = isRecord(value.error) ? {
|
|
1190
1191
|
code: typeof value.error.code === "string" && value.error.code ? value.error.code : "surface_error",
|
|
1191
|
-
message: typeof value.error.message === "string" ? value.error.message : "
|
|
1192
|
+
message: typeof value.error.message === "string" ? value.error.message : "App surface call failed"
|
|
1192
1193
|
} : void 0;
|
|
1193
1194
|
return {
|
|
1194
|
-
protocol:
|
|
1195
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1195
1196
|
version: 1,
|
|
1196
1197
|
type: "response",
|
|
1197
1198
|
requestId: value.requestId,
|
|
@@ -1205,7 +1206,7 @@ const parseComposerChipKey = (value) => {
|
|
|
1205
1206
|
if (!key || key.length > 80) return null;
|
|
1206
1207
|
return key;
|
|
1207
1208
|
};
|
|
1208
|
-
const
|
|
1209
|
+
const parseAppComposerChipSet = (value) => {
|
|
1209
1210
|
if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.set" || !isRecord(value.chip)) return null;
|
|
1210
1211
|
const key = parseComposerChipKey(value.chip.key);
|
|
1211
1212
|
if (!key || typeof value.chip.label !== "string" || typeof value.chip.content !== "string") return null;
|
|
@@ -1214,7 +1215,7 @@ const parseWorkComposerChipSet = (value) => {
|
|
|
1214
1215
|
if (!value.chip.content.trim()) return null;
|
|
1215
1216
|
if (new TextEncoder().encode(value.chip.content).length > 32768) return null;
|
|
1216
1217
|
return {
|
|
1217
|
-
protocol:
|
|
1218
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1218
1219
|
version: 1,
|
|
1219
1220
|
type: "composer.chip.set",
|
|
1220
1221
|
chip: {
|
|
@@ -1224,24 +1225,24 @@ const parseWorkComposerChipSet = (value) => {
|
|
|
1224
1225
|
}
|
|
1225
1226
|
};
|
|
1226
1227
|
};
|
|
1227
|
-
const
|
|
1228
|
+
const parseAppComposerChipClear = (value) => {
|
|
1228
1229
|
if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.clear") return null;
|
|
1229
1230
|
const key = parseComposerChipKey(value.key);
|
|
1230
1231
|
return key ? {
|
|
1231
|
-
protocol:
|
|
1232
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1232
1233
|
version: 1,
|
|
1233
1234
|
type: "composer.chip.clear",
|
|
1234
1235
|
key
|
|
1235
1236
|
} : null;
|
|
1236
1237
|
};
|
|
1237
|
-
const
|
|
1238
|
+
const parseAppSurfaceRequest = (value) => {
|
|
1238
1239
|
if (!isSurfaceEnvelope(value) || value.type !== "request") return null;
|
|
1239
1240
|
if (typeof value.requestId !== "string" || !value.requestId) return null;
|
|
1240
1241
|
if (typeof value.method !== "string" || !value.method) return null;
|
|
1241
|
-
const commandId =
|
|
1242
|
+
const commandId = parseDesktopCommandId(value.commandId);
|
|
1242
1243
|
if (!commandId) return null;
|
|
1243
1244
|
return {
|
|
1244
|
-
protocol:
|
|
1245
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1245
1246
|
version: 1,
|
|
1246
1247
|
type: "request",
|
|
1247
1248
|
requestId: value.requestId,
|
|
@@ -1250,37 +1251,45 @@ const parseWorkSurfaceRequest = (value) => {
|
|
|
1250
1251
|
commandId
|
|
1251
1252
|
};
|
|
1252
1253
|
};
|
|
1253
|
-
const
|
|
1254
|
-
protocol:
|
|
1254
|
+
const buildAppSurfaceReady = (methods) => ({
|
|
1255
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1255
1256
|
version: 1,
|
|
1256
1257
|
type: "ready",
|
|
1257
1258
|
methods: [...methods]
|
|
1258
1259
|
});
|
|
1259
|
-
const
|
|
1260
|
-
protocol:
|
|
1260
|
+
const buildAppSurfaceRequest = (input) => ({
|
|
1261
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1261
1262
|
version: 1,
|
|
1262
1263
|
type: "request",
|
|
1263
1264
|
...input
|
|
1264
1265
|
});
|
|
1265
|
-
const
|
|
1266
|
-
protocol:
|
|
1266
|
+
const buildAppSurfaceResponse = (input) => ({
|
|
1267
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1267
1268
|
version: 1,
|
|
1268
1269
|
type: "response",
|
|
1269
1270
|
...input
|
|
1270
1271
|
});
|
|
1271
|
-
const
|
|
1272
|
-
protocol:
|
|
1272
|
+
const buildAppComposerChipSet = (chip) => ({
|
|
1273
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1273
1274
|
version: 1,
|
|
1274
1275
|
type: "composer.chip.set",
|
|
1275
1276
|
chip
|
|
1276
1277
|
});
|
|
1277
|
-
const
|
|
1278
|
-
protocol:
|
|
1278
|
+
const buildAppComposerChipClear = (key) => ({
|
|
1279
|
+
protocol: APP_SURFACE_PROTOCOL,
|
|
1279
1280
|
version: 1,
|
|
1280
1281
|
type: "composer.chip.clear",
|
|
1281
1282
|
key
|
|
1282
1283
|
});
|
|
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
|
|
1284
1293
|
//#region src/apis/billing.ts
|
|
1285
1294
|
var BillingApi = class {
|
|
1286
1295
|
transport;
|
|
@@ -1353,7 +1362,7 @@ var BillingApi = class {
|
|
|
1353
1362
|
}
|
|
1354
1363
|
};
|
|
1355
1364
|
//#endregion
|
|
1356
|
-
//#region src/apis/
|
|
1365
|
+
//#region src/apis/app-realtime.ts
|
|
1357
1366
|
const randomId = () => globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1358
1367
|
const textEncoder = new TextEncoder();
|
|
1359
1368
|
const byteLength = (text) => textEncoder.encode(text).byteLength;
|
|
@@ -1377,7 +1386,7 @@ const isRoomEvent = (event, roomId) => {
|
|
|
1377
1386
|
if (event.domain !== "room") return false;
|
|
1378
1387
|
return event.payload.roomId === roomId || event.rooms?.includes(`room:${roomId}`);
|
|
1379
1388
|
};
|
|
1380
|
-
var
|
|
1389
|
+
var AppRoom = class {
|
|
1381
1390
|
websocket;
|
|
1382
1391
|
admission;
|
|
1383
1392
|
id;
|
|
@@ -1569,7 +1578,7 @@ var WorkRoom = class {
|
|
|
1569
1578
|
if (this.state === "expired") return;
|
|
1570
1579
|
this.setState("closed");
|
|
1571
1580
|
this.dispose();
|
|
1572
|
-
console.warn("[Cohub
|
|
1581
|
+
console.warn("[Cohub AppRoom] failed to rejoin room", error);
|
|
1573
1582
|
}
|
|
1574
1583
|
}
|
|
1575
1584
|
async joinOverWebsocket(isReconnect) {
|
|
@@ -1734,7 +1743,7 @@ var WorkRoom = class {
|
|
|
1734
1743
|
this.expiryTimer = null;
|
|
1735
1744
|
}
|
|
1736
1745
|
};
|
|
1737
|
-
var
|
|
1746
|
+
var AppRealtimeApi = class {
|
|
1738
1747
|
transport;
|
|
1739
1748
|
websocket;
|
|
1740
1749
|
getContext;
|
|
@@ -1744,7 +1753,7 @@ var WorkRealtimeApi = class {
|
|
|
1744
1753
|
this.getContext = getContext;
|
|
1745
1754
|
}
|
|
1746
1755
|
async createRoom(input = {}) {
|
|
1747
|
-
const admission = await this.transport.request(`/api/
|
|
1756
|
+
const admission = await this.transport.request(`/api/apps/${encodeURIComponent(await this.requireAppId())}/realtime/rooms`, {
|
|
1748
1757
|
method: "POST",
|
|
1749
1758
|
headers: { "Content-Type": "application/json" },
|
|
1750
1759
|
body: JSON.stringify(input)
|
|
@@ -1752,20 +1761,20 @@ var WorkRealtimeApi = class {
|
|
|
1752
1761
|
return this.openRoom(admission);
|
|
1753
1762
|
}
|
|
1754
1763
|
async joinRoom(input) {
|
|
1755
|
-
const admission = await this.transport.request(`/api/
|
|
1764
|
+
const admission = await this.transport.request(`/api/apps/${encodeURIComponent(await this.requireAppId())}/realtime/rooms/join`, {
|
|
1756
1765
|
method: "POST",
|
|
1757
1766
|
headers: { "Content-Type": "application/json" },
|
|
1758
1767
|
body: JSON.stringify(input)
|
|
1759
1768
|
});
|
|
1760
1769
|
return this.openRoom(admission);
|
|
1761
1770
|
}
|
|
1762
|
-
async
|
|
1771
|
+
async requireAppId() {
|
|
1763
1772
|
const context = await this.getContext();
|
|
1764
|
-
if (!context?.
|
|
1765
|
-
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;
|
|
1766
1775
|
}
|
|
1767
1776
|
async openRoom(admission) {
|
|
1768
|
-
const room = new
|
|
1777
|
+
const room = new AppRoom(this.websocket, admission);
|
|
1769
1778
|
try {
|
|
1770
1779
|
await room.connect();
|
|
1771
1780
|
return room;
|
|
@@ -1775,11 +1784,15 @@ var WorkRealtimeApi = class {
|
|
|
1775
1784
|
}
|
|
1776
1785
|
}
|
|
1777
1786
|
};
|
|
1787
|
+
/** @deprecated Use `AppRealtimeApi`. */
|
|
1788
|
+
var WorkRealtimeApi = class extends AppRealtimeApi {};
|
|
1789
|
+
/** @deprecated Use `AppRoom`. */
|
|
1790
|
+
var WorkRoom = class extends AppRoom {};
|
|
1778
1791
|
//#endregion
|
|
1779
|
-
//#region src/
|
|
1792
|
+
//#region src/app-surface.ts
|
|
1780
1793
|
/**
|
|
1781
|
-
* Explicit app origins, not a Cohub suffix match:
|
|
1782
|
-
* 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
|
|
1783
1796
|
* another and turn a subdomain takeover into surface access.
|
|
1784
1797
|
*/
|
|
1785
1798
|
const COHUB_APP_ORIGINS = [
|
|
@@ -1802,7 +1815,7 @@ const resolveEmbedderOrigin = () => {
|
|
|
1802
1815
|
return null;
|
|
1803
1816
|
}
|
|
1804
1817
|
};
|
|
1805
|
-
var
|
|
1818
|
+
var AppSurfaceApi = class {
|
|
1806
1819
|
handlers = /* @__PURE__ */ new Map();
|
|
1807
1820
|
listening = false;
|
|
1808
1821
|
allowedOrigins = null;
|
|
@@ -1820,7 +1833,7 @@ var WorkSurfaceApi = class {
|
|
|
1820
1833
|
}
|
|
1821
1834
|
handle(method, handler) {
|
|
1822
1835
|
const name = method.trim();
|
|
1823
|
-
if (!name) throw new Error("
|
|
1836
|
+
if (!name) throw new Error("App surface method name is required");
|
|
1824
1837
|
this.handlers.set(name, handler);
|
|
1825
1838
|
this.start();
|
|
1826
1839
|
this.announce();
|
|
@@ -1835,17 +1848,17 @@ var WorkSurfaceApi = class {
|
|
|
1835
1848
|
return [...this.handlers.keys()];
|
|
1836
1849
|
}
|
|
1837
1850
|
setComposerChip(chip) {
|
|
1838
|
-
const message =
|
|
1839
|
-
if (!message) throw new Error("Invalid
|
|
1851
|
+
const message = parseAppComposerChipSet(buildAppComposerChipSet(chip));
|
|
1852
|
+
if (!message) throw new Error("Invalid App composer chip");
|
|
1840
1853
|
this.post(message);
|
|
1841
1854
|
}
|
|
1842
1855
|
clearComposerChip(key) {
|
|
1843
|
-
const message =
|
|
1844
|
-
if (!message) throw new Error("Invalid
|
|
1856
|
+
const message = parseAppComposerChipClear(buildAppComposerChipClear(key));
|
|
1857
|
+
if (!message) throw new Error("Invalid App composer chip key");
|
|
1845
1858
|
this.post(message);
|
|
1846
1859
|
}
|
|
1847
1860
|
announce() {
|
|
1848
|
-
this.post(
|
|
1861
|
+
this.post(buildAppSurfaceReady(this.methods));
|
|
1849
1862
|
}
|
|
1850
1863
|
isTrusted(origin) {
|
|
1851
1864
|
if (!origin || origin === "null") return false;
|
|
@@ -1866,7 +1879,7 @@ var WorkSurfaceApi = class {
|
|
|
1866
1879
|
onMessage = (event) => {
|
|
1867
1880
|
if (typeof window === "undefined" || event.source !== window.parent) return;
|
|
1868
1881
|
if (!this.isTrusted(event.origin)) return;
|
|
1869
|
-
const request =
|
|
1882
|
+
const request = parseAppSurfaceRequest(event.data);
|
|
1870
1883
|
if (!request) return;
|
|
1871
1884
|
const commandId = request.commandId;
|
|
1872
1885
|
if (!commandId) return;
|
|
@@ -1876,24 +1889,24 @@ var WorkSurfaceApi = class {
|
|
|
1876
1889
|
async dispatch(requestId, method, input, commandId) {
|
|
1877
1890
|
const handler = this.handlers.get(method);
|
|
1878
1891
|
if (!handler) {
|
|
1879
|
-
this.post(
|
|
1892
|
+
this.post(buildAppSurfaceResponse({
|
|
1880
1893
|
requestId,
|
|
1881
1894
|
ok: false,
|
|
1882
1895
|
error: {
|
|
1883
1896
|
code: "method_not_found",
|
|
1884
|
-
message: `This
|
|
1897
|
+
message: `This app does not expose "${method}".`
|
|
1885
1898
|
}
|
|
1886
1899
|
}));
|
|
1887
1900
|
return;
|
|
1888
1901
|
}
|
|
1889
1902
|
try {
|
|
1890
1903
|
await handler(input, { commandId });
|
|
1891
|
-
this.post(
|
|
1904
|
+
this.post(buildAppSurfaceResponse({
|
|
1892
1905
|
requestId,
|
|
1893
1906
|
ok: true
|
|
1894
1907
|
}));
|
|
1895
1908
|
} catch (error) {
|
|
1896
|
-
this.post(
|
|
1909
|
+
this.post(buildAppSurfaceResponse({
|
|
1897
1910
|
requestId,
|
|
1898
1911
|
ok: false,
|
|
1899
1912
|
error: {
|
|
@@ -1912,8 +1925,10 @@ var WorkSurfaceApi = class {
|
|
|
1912
1925
|
} catch {}
|
|
1913
1926
|
}
|
|
1914
1927
|
};
|
|
1928
|
+
/** @deprecated Use `AppSurfaceApi`. */
|
|
1929
|
+
var WorkSurfaceApi = class extends AppSurfaceApi {};
|
|
1915
1930
|
//#endregion
|
|
1916
|
-
//#region src/
|
|
1931
|
+
//#region src/app-runtime.ts
|
|
1917
1932
|
const isBrowser$1 = () => typeof window !== "undefined" && typeof window.parent !== "undefined";
|
|
1918
1933
|
const hasParent = () => isBrowser$1() && window.parent !== window;
|
|
1919
1934
|
const getParentOrigin = () => {
|
|
@@ -1929,11 +1944,39 @@ const getParentOrigin = () => {
|
|
|
1929
1944
|
const generateRequestId = () => globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1930
1945
|
/**
|
|
1931
1946
|
* Bridge-mode transport: posts messages to `window.parent` (the Cohub host
|
|
1932
|
-
* embedding the
|
|
1947
|
+
* embedding the app in an iframe) and listens for the matching reply.
|
|
1933
1948
|
* Behaviorally identical to the previous module-level `request()` helper.
|
|
1934
1949
|
*/
|
|
1935
1950
|
var ParentBridgeTransport = class {
|
|
1936
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
|
+
}
|
|
1937
1980
|
request(message, options) {
|
|
1938
1981
|
const timeoutMs = options?.timeoutMs ?? 1200;
|
|
1939
1982
|
const retryIntervalMs = options?.retryIntervalMs;
|
|
@@ -1968,7 +2011,7 @@ var ParentBridgeTransport = class {
|
|
|
1968
2011
|
if (!data || data.requestId !== requestId) return;
|
|
1969
2012
|
cleanup();
|
|
1970
2013
|
this.trustedParentOrigin = event.origin;
|
|
1971
|
-
if (data.type === "cohub.
|
|
2014
|
+
if (data.type === "cohub.app.error") {
|
|
1972
2015
|
reject(new Error(data.message));
|
|
1973
2016
|
return;
|
|
1974
2017
|
}
|
|
@@ -1981,64 +2024,64 @@ var ParentBridgeTransport = class {
|
|
|
1981
2024
|
}
|
|
1982
2025
|
};
|
|
1983
2026
|
/**
|
|
1984
|
-
* Broker-mode transport for standalone-deployed
|
|
2027
|
+
* Broker-mode transport for standalone-deployed apps. Opens a popup window to
|
|
1985
2028
|
* the Cohub auth broker page, performs a ready-handshake, sends the request via
|
|
1986
2029
|
* postMessage, and resolves with the broker's response. The popup is closed
|
|
1987
2030
|
* after a single request is fulfilled (one-shot, per §7.2 of the plan).
|
|
1988
2031
|
*
|
|
1989
2032
|
* Non-interactive messages (`context`, `checkout-state`) are answered locally
|
|
1990
|
-
* without opening a popup — the
|
|
1991
|
-
* 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.
|
|
1992
2035
|
*/
|
|
1993
2036
|
var PopupBrokerTransport = class {
|
|
1994
2037
|
brokerOrigin;
|
|
1995
|
-
|
|
1996
|
-
|
|
2038
|
+
appId;
|
|
2039
|
+
getAppId;
|
|
1997
2040
|
constructor(config) {
|
|
1998
2041
|
this.brokerOrigin = config.brokerOrigin;
|
|
1999
|
-
this.
|
|
2000
|
-
this.
|
|
2001
|
-
if (!this.
|
|
2042
|
+
this.appId = config.appId;
|
|
2043
|
+
this.getAppId = config.getAppId;
|
|
2044
|
+
if (!this.appId && this.getAppId) this.getAppId();
|
|
2002
2045
|
}
|
|
2003
|
-
|
|
2004
|
-
if (this.
|
|
2005
|
-
if (this.
|
|
2046
|
+
resolveAppId() {
|
|
2047
|
+
if (this.appId) return Promise.resolve(this.appId);
|
|
2048
|
+
if (this.getAppId) return this.getAppId();
|
|
2006
2049
|
return Promise.resolve(null);
|
|
2007
2050
|
}
|
|
2008
2051
|
async request(message, options) {
|
|
2009
|
-
if (message.type === "cohub.
|
|
2010
|
-
type: "cohub.
|
|
2052
|
+
if (message.type === "cohub.app.context") return {
|
|
2053
|
+
type: "cohub.app.context.result",
|
|
2011
2054
|
context: {
|
|
2012
|
-
|
|
2013
|
-
id: await this.
|
|
2055
|
+
app: {
|
|
2056
|
+
id: await this.resolveAppId() ?? "",
|
|
2014
2057
|
slug: "",
|
|
2015
2058
|
url: null
|
|
2016
2059
|
},
|
|
2017
2060
|
space: { id: "" },
|
|
2018
2061
|
permissions: {
|
|
2019
2062
|
scopes: [],
|
|
2020
|
-
|
|
2063
|
+
appScopes: [],
|
|
2021
2064
|
viewerScopes: []
|
|
2022
2065
|
}
|
|
2023
2066
|
}
|
|
2024
2067
|
};
|
|
2025
|
-
if (message.type === "cohub.
|
|
2026
|
-
type: "cohub.
|
|
2068
|
+
if (message.type === "cohub.app.checkout-state") return {
|
|
2069
|
+
type: "cohub.app.checkout-state.result",
|
|
2027
2070
|
status: null,
|
|
2028
2071
|
orderId: null
|
|
2029
2072
|
};
|
|
2030
2073
|
const timeoutMs = options?.timeoutMs ?? 12e4;
|
|
2031
2074
|
const requestId = generateRequestId();
|
|
2032
|
-
const
|
|
2033
|
-
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).");
|
|
2034
2077
|
return new Promise((resolve, reject) => {
|
|
2035
2078
|
if (typeof window === "undefined" || typeof window.open !== "function") {
|
|
2036
2079
|
resolve(null);
|
|
2037
2080
|
return;
|
|
2038
2081
|
}
|
|
2039
|
-
const
|
|
2040
|
-
const brokerUrl = `${this.brokerOrigin}/
|
|
2041
|
-
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");
|
|
2042
2085
|
if (!popup) {
|
|
2043
2086
|
reject(/* @__PURE__ */ new Error("Failed to open authorization window. Please allow popups for this site."));
|
|
2044
2087
|
return;
|
|
@@ -2069,7 +2112,7 @@ var PopupBrokerTransport = class {
|
|
|
2069
2112
|
if (event.origin !== this.brokerOrigin) return;
|
|
2070
2113
|
const data = event.data;
|
|
2071
2114
|
if (!data) return;
|
|
2072
|
-
if (data.type === "cohub.
|
|
2115
|
+
if (data.type === "cohub.app.broker.ready" && !ready) {
|
|
2073
2116
|
ready = true;
|
|
2074
2117
|
try {
|
|
2075
2118
|
popup.postMessage({
|
|
@@ -2083,7 +2126,7 @@ var PopupBrokerTransport = class {
|
|
|
2083
2126
|
}
|
|
2084
2127
|
if (data.requestId !== requestId) return;
|
|
2085
2128
|
finish(() => {
|
|
2086
|
-
if (data.type === "cohub.
|
|
2129
|
+
if (data.type === "cohub.app.error") {
|
|
2087
2130
|
reject(new Error(data.message));
|
|
2088
2131
|
return;
|
|
2089
2132
|
}
|
|
@@ -2100,30 +2143,30 @@ var PopupBrokerTransport = class {
|
|
|
2100
2143
|
});
|
|
2101
2144
|
}
|
|
2102
2145
|
};
|
|
2103
|
-
const TOKEN_STORAGE_PREFIX = "cohub:
|
|
2104
|
-
const AUTHORIZED_SCOPES_STORAGE_PREFIX = "cohub:
|
|
2105
|
-
var
|
|
2146
|
+
const TOKEN_STORAGE_PREFIX = "cohub:app-token";
|
|
2147
|
+
const AUTHORIZED_SCOPES_STORAGE_PREFIX = "cohub:app-auth-scopes";
|
|
2148
|
+
var AppRuntimeApi = class {
|
|
2106
2149
|
token = null;
|
|
2107
2150
|
transport;
|
|
2108
2151
|
tokenStorageKey;
|
|
2109
2152
|
scopesStorageKey;
|
|
2110
|
-
|
|
2153
|
+
appIdResolver;
|
|
2111
2154
|
/** Ensures storage keys are resolved (via slug lookup) at most once. */
|
|
2112
2155
|
storageKeysReady;
|
|
2113
2156
|
/** Scopes previously granted via requestAuthorization, retained so token
|
|
2114
2157
|
* refreshes can re-authorize (preserving viewerScopes) instead of falling
|
|
2115
|
-
* back to a base session token that only carries
|
|
2158
|
+
* back to a base session token that only carries appScopes. */
|
|
2116
2159
|
authorizedScopes = null;
|
|
2117
|
-
constructor(transport = new ParentBridgeTransport(),
|
|
2160
|
+
constructor(transport = new ParentBridgeTransport(), appId, appIdResolver) {
|
|
2118
2161
|
this.transport = transport;
|
|
2119
|
-
this.
|
|
2120
|
-
if (
|
|
2121
|
-
this.tokenStorageKey = `${TOKEN_STORAGE_PREFIX}:${
|
|
2122
|
-
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}`;
|
|
2123
2166
|
this.storageKeysReady = Promise.resolve();
|
|
2124
2167
|
this.token = this.readStoredToken();
|
|
2125
2168
|
this.authorizedScopes = this.readStoredScopes();
|
|
2126
|
-
} else if (
|
|
2169
|
+
} else if (appIdResolver) {
|
|
2127
2170
|
this.tokenStorageKey = null;
|
|
2128
2171
|
this.scopesStorageKey = null;
|
|
2129
2172
|
this.storageKeysReady = null;
|
|
@@ -2134,17 +2177,17 @@ var WorkRuntimeApi = class {
|
|
|
2134
2177
|
}
|
|
2135
2178
|
}
|
|
2136
2179
|
/**
|
|
2137
|
-
* Resolves the localStorage keys once the
|
|
2180
|
+
* Resolves the localStorage keys once the appId is known. When the appId is
|
|
2138
2181
|
* only available via slug reverse lookup, this performs the lookup on first
|
|
2139
|
-
* use and then restores any cached token/scopes for that
|
|
2182
|
+
* use and then restores any cached token/scopes for that appId.
|
|
2140
2183
|
*/
|
|
2141
2184
|
ensureStorageKeys() {
|
|
2142
2185
|
if (this.storageKeysReady) return this.storageKeysReady;
|
|
2143
2186
|
this.storageKeysReady = (async () => {
|
|
2144
|
-
const
|
|
2145
|
-
if (
|
|
2146
|
-
this.tokenStorageKey = `${TOKEN_STORAGE_PREFIX}:${
|
|
2147
|
-
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}`;
|
|
2148
2191
|
const stored = this.readStoredToken();
|
|
2149
2192
|
if (stored && !this.token) this.token = stored;
|
|
2150
2193
|
const storedScopes = this.readStoredScopes();
|
|
@@ -2187,11 +2230,14 @@ var WorkRuntimeApi = class {
|
|
|
2187
2230
|
} catch {}
|
|
2188
2231
|
}
|
|
2189
2232
|
async context() {
|
|
2190
|
-
return (await this.transport.request({ type: "cohub.
|
|
2233
|
+
return (await this.transport.request({ type: "cohub.app.context" }, {
|
|
2191
2234
|
timeoutMs: 8e3,
|
|
2192
2235
|
retryIntervalMs: 250
|
|
2193
2236
|
}))?.context ?? null;
|
|
2194
2237
|
}
|
|
2238
|
+
onContextChanged(listener) {
|
|
2239
|
+
return this.transport.subscribeContextChanged?.(listener) ?? (() => {});
|
|
2240
|
+
}
|
|
2195
2241
|
async getAccessToken(options) {
|
|
2196
2242
|
await this.ensureStorageKeys();
|
|
2197
2243
|
if (this.token && !options?.forceRefresh) return this.token;
|
|
@@ -2201,7 +2247,7 @@ var WorkRuntimeApi = class {
|
|
|
2201
2247
|
}
|
|
2202
2248
|
if (options?.forceRefresh && this.authorizedScopes && this.authorizedScopes.length > 0) {
|
|
2203
2249
|
const response = await this.transport.request({
|
|
2204
|
-
type: "cohub.
|
|
2250
|
+
type: "cohub.app.authorize",
|
|
2205
2251
|
scopes: this.authorizedScopes
|
|
2206
2252
|
}, { timeoutMs: 12e4 });
|
|
2207
2253
|
this.token = response?.token ?? null;
|
|
@@ -2209,7 +2255,7 @@ var WorkRuntimeApi = class {
|
|
|
2209
2255
|
return this.token;
|
|
2210
2256
|
}
|
|
2211
2257
|
const response = await this.transport.request({
|
|
2212
|
-
type: "cohub.
|
|
2258
|
+
type: "cohub.app.token",
|
|
2213
2259
|
forceRefresh: Boolean(options?.forceRefresh)
|
|
2214
2260
|
}, { timeoutMs: 2e4 });
|
|
2215
2261
|
this.token = response?.token ?? null;
|
|
@@ -2219,7 +2265,7 @@ var WorkRuntimeApi = class {
|
|
|
2219
2265
|
async requestAuthorization(input) {
|
|
2220
2266
|
await this.ensureStorageKeys();
|
|
2221
2267
|
const response = await this.transport.request({
|
|
2222
|
-
type: "cohub.
|
|
2268
|
+
type: "cohub.app.authorize",
|
|
2223
2269
|
scopes: input.scopes,
|
|
2224
2270
|
reason: input.reason
|
|
2225
2271
|
}, { timeoutMs: 12e4 });
|
|
@@ -2234,32 +2280,34 @@ var WorkRuntimeApi = class {
|
|
|
2234
2280
|
async purchase(input) {
|
|
2235
2281
|
const purchaseAttemptId = input.purchaseAttemptId?.trim() || generateRequestId();
|
|
2236
2282
|
return (await this.transport.request({
|
|
2237
|
-
type: "cohub.
|
|
2283
|
+
type: "cohub.app.purchase",
|
|
2238
2284
|
productKey: input.productKey,
|
|
2239
2285
|
purchaseAttemptId
|
|
2240
2286
|
}, { timeoutMs: 12e4 }))?.checkout ?? null;
|
|
2241
2287
|
}
|
|
2242
2288
|
async checkoutState() {
|
|
2243
|
-
return await this.transport.request({ type: "cohub.
|
|
2289
|
+
return await this.transport.request({ type: "cohub.app.checkout-state" }, {
|
|
2244
2290
|
timeoutMs: 8e3,
|
|
2245
2291
|
retryIntervalMs: 250
|
|
2246
2292
|
}) ?? null;
|
|
2247
2293
|
}
|
|
2248
2294
|
};
|
|
2249
2295
|
/**
|
|
2250
|
-
* Builds a memoized
|
|
2251
|
-
* public slug triple via `GET /api/
|
|
2252
|
-
*
|
|
2253
|
-
*
|
|
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.
|
|
2254
2302
|
*/
|
|
2255
|
-
function
|
|
2303
|
+
function createSlugAppIdResolver(deps) {
|
|
2256
2304
|
let cached = null;
|
|
2257
2305
|
return () => {
|
|
2258
2306
|
if (cached) return cached;
|
|
2259
2307
|
const run = (async () => {
|
|
2260
2308
|
const doFetch = deps.fetch ?? globalThis.fetch;
|
|
2261
2309
|
if (typeof doFetch !== "function") return null;
|
|
2262
|
-
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)}`);
|
|
2263
2311
|
if (!response.ok) throw new Error(`getBySlug failed: ${response.status}`);
|
|
2264
2312
|
return (await response.json())?.work?.id ?? null;
|
|
2265
2313
|
})().catch(() => {
|
|
@@ -2271,27 +2319,27 @@ function createSlugWorkIdResolver(deps) {
|
|
|
2271
2319
|
};
|
|
2272
2320
|
}
|
|
2273
2321
|
/**
|
|
2274
|
-
* Resolves the appropriate transport based on the
|
|
2322
|
+
* Resolves the appropriate transport based on the app mode configuration.
|
|
2275
2323
|
* Auto-detection: inside an iframe → bridge; standalone with broker config →
|
|
2276
|
-
* broker; otherwise → bridge (returns null for non-
|
|
2324
|
+
* broker; otherwise → bridge (returns null for non-app contexts).
|
|
2277
2325
|
*/
|
|
2278
|
-
function
|
|
2326
|
+
function resolveAppTransport(config, appIdResolver) {
|
|
2279
2327
|
const explicitMode = config?.mode;
|
|
2280
2328
|
const brokerOrigin = config?.brokerOrigin;
|
|
2281
|
-
const
|
|
2282
|
-
const
|
|
2283
|
-
const hasBrokerConfig = Boolean(brokerOrigin &&
|
|
2284
|
-
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({
|
|
2285
2333
|
brokerOrigin,
|
|
2286
|
-
|
|
2287
|
-
|
|
2334
|
+
appId,
|
|
2335
|
+
getAppId: appIdResolver
|
|
2288
2336
|
}) : new ParentBridgeTransport();
|
|
2289
2337
|
if (explicitMode === "bridge") return new ParentBridgeTransport();
|
|
2290
2338
|
if (explicitMode === "broker") return createBroker();
|
|
2291
2339
|
if (typeof window !== "undefined" && window.parent !== window) return new ParentBridgeTransport();
|
|
2292
2340
|
return hasBrokerConfig ? createBroker() : new ParentBridgeTransport();
|
|
2293
2341
|
}
|
|
2294
|
-
const
|
|
2342
|
+
const createAppRuntime = (transport, appId, appIdResolver) => new AppRuntimeApi(transport, appId, appIdResolver);
|
|
2295
2343
|
//#endregion
|
|
2296
2344
|
//#region src/client.ts
|
|
2297
2345
|
var CohubClient = class {
|
|
@@ -2310,27 +2358,40 @@ var CohubClient = class {
|
|
|
2310
2358
|
references;
|
|
2311
2359
|
tasks;
|
|
2312
2360
|
cronJobs;
|
|
2313
|
-
|
|
2361
|
+
desktop;
|
|
2314
2362
|
invite;
|
|
2315
2363
|
referrals;
|
|
2316
2364
|
voice;
|
|
2317
|
-
|
|
2318
|
-
|
|
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
|
+
}
|
|
2319
2379
|
transport;
|
|
2320
2380
|
websocketClient;
|
|
2321
|
-
|
|
2381
|
+
appRuntime;
|
|
2322
2382
|
constructor(options = {}) {
|
|
2323
2383
|
const apiBaseUrl = resolveApiBaseUrl(options);
|
|
2324
|
-
const
|
|
2384
|
+
const appRuntime = options.app ?? options.work;
|
|
2385
|
+
const appIdResolver = !appRuntime?.appId && appRuntime?.ownerUsername && appRuntime?.spaceSlug && appRuntime?.appSlug ? createSlugAppIdResolver({
|
|
2325
2386
|
apiBaseUrl,
|
|
2326
2387
|
fetch: options.fetch,
|
|
2327
|
-
ownerUsername:
|
|
2328
|
-
spaceSlug:
|
|
2329
|
-
|
|
2388
|
+
ownerUsername: appRuntime.ownerUsername,
|
|
2389
|
+
spaceSlug: appRuntime.spaceSlug,
|
|
2390
|
+
appSlug: appRuntime.appSlug
|
|
2330
2391
|
}) : void 0;
|
|
2331
|
-
const
|
|
2332
|
-
this.
|
|
2333
|
-
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));
|
|
2334
2395
|
const resolvedOptions = {
|
|
2335
2396
|
...options,
|
|
2336
2397
|
getAccessToken
|
|
@@ -2367,55 +2428,56 @@ var CohubClient = class {
|
|
|
2367
2428
|
this.references = new ReferencesApi(this.transport);
|
|
2368
2429
|
this.tasks = new TasksApi(this.transport);
|
|
2369
2430
|
this.cronJobs = new CronJobsApi(this.transport);
|
|
2370
|
-
this.
|
|
2431
|
+
this.desktop = new DesktopCommandsApi(this.transport);
|
|
2371
2432
|
this.invite = new PublicInviteApi(this.transport);
|
|
2372
2433
|
this.referrals = new ReferralsApi(this.transport);
|
|
2373
|
-
this.
|
|
2374
|
-
this.
|
|
2375
|
-
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());
|
|
2376
2437
|
}
|
|
2377
2438
|
context() {
|
|
2378
|
-
return this.
|
|
2439
|
+
return this.appRuntime.context();
|
|
2379
2440
|
}
|
|
2380
|
-
auth = { request: (input) => this.
|
|
2381
|
-
|
|
2441
|
+
auth = { request: (input) => this.appRuntime.requestAuthorization(input) };
|
|
2442
|
+
app = {
|
|
2382
2443
|
realtime: null,
|
|
2383
|
-
/** Expose callable methods from inside a published
|
|
2384
|
-
surface: new
|
|
2444
|
+
/** Expose callable methods from inside a published app. */
|
|
2445
|
+
surface: new AppSurfaceApi(),
|
|
2446
|
+
onContextChanged: (listener) => this.appRuntime.onContextChanged(listener),
|
|
2385
2447
|
composer: {
|
|
2386
|
-
/** Attach or update context from this
|
|
2387
|
-
setChip: (chip) => this.
|
|
2388
|
-
/** Remove context previously attached by this
|
|
2389
|
-
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)
|
|
2390
2452
|
},
|
|
2391
2453
|
commerce: {
|
|
2392
2454
|
resolveProducts: async (input) => {
|
|
2393
|
-
const context = await this.
|
|
2394
|
-
if (!context?.
|
|
2395
|
-
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);
|
|
2396
2458
|
},
|
|
2397
2459
|
getEntitlements: async () => {
|
|
2398
|
-
const context = await this.
|
|
2399
|
-
if (!context?.
|
|
2400
|
-
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);
|
|
2401
2463
|
},
|
|
2402
2464
|
consumeCredits: async (input) => {
|
|
2403
|
-
const context = await this.
|
|
2404
|
-
if (!context?.
|
|
2405
|
-
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);
|
|
2406
2468
|
},
|
|
2407
|
-
purchase: async (input) => this.
|
|
2469
|
+
purchase: async (input) => this.appRuntime.purchase(input),
|
|
2408
2470
|
getCheckoutState: async () => {
|
|
2409
|
-
const result = await this.
|
|
2471
|
+
const result = await this.appRuntime.checkoutState();
|
|
2410
2472
|
return {
|
|
2411
2473
|
status: result?.status ?? null,
|
|
2412
2474
|
orderId: result?.orderId ?? null
|
|
2413
2475
|
};
|
|
2414
2476
|
},
|
|
2415
2477
|
getOrder: async (orderId) => {
|
|
2416
|
-
const context = await this.
|
|
2417
|
-
if (!context?.
|
|
2418
|
-
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);
|
|
2419
2481
|
}
|
|
2420
2482
|
}
|
|
2421
2483
|
};
|
|
@@ -2473,8 +2535,8 @@ var CohubClient = class {
|
|
|
2473
2535
|
};
|
|
2474
2536
|
const createCohubClient = (options) => new CohubClient(options);
|
|
2475
2537
|
//#endregion
|
|
2476
|
-
//#region src/
|
|
2477
|
-
const
|
|
2538
|
+
//#region src/app-ref.ts
|
|
2539
|
+
const isAppId = (value) => isUuid(value.trim());
|
|
2478
2540
|
function decodePart(value) {
|
|
2479
2541
|
try {
|
|
2480
2542
|
return decodeURIComponent(value).trim();
|
|
@@ -2484,14 +2546,14 @@ function decodePart(value) {
|
|
|
2484
2546
|
}
|
|
2485
2547
|
function publicRef(parts) {
|
|
2486
2548
|
if (parts.length !== 3) return null;
|
|
2487
|
-
const [usernameRaw = "", spaceSlugRaw = "",
|
|
2549
|
+
const [usernameRaw = "", spaceSlugRaw = "", appSlugRaw = ""] = parts.map(decodePart);
|
|
2488
2550
|
const username = parseUsername(usernameRaw);
|
|
2489
2551
|
const spaceSlug = parseSpaceSlug(spaceSlugRaw);
|
|
2490
|
-
const
|
|
2491
|
-
return username && spaceSlug &&
|
|
2552
|
+
const appSlug = parseSpaceSlug(appSlugRaw);
|
|
2553
|
+
return username && spaceSlug && appSlug ? {
|
|
2492
2554
|
username,
|
|
2493
2555
|
spaceSlug,
|
|
2494
|
-
|
|
2556
|
+
appSlug
|
|
2495
2557
|
} : null;
|
|
2496
2558
|
}
|
|
2497
2559
|
function launchState(url) {
|
|
@@ -2508,15 +2570,22 @@ function parseUrlRef(value) {
|
|
|
2508
2570
|
return null;
|
|
2509
2571
|
}
|
|
2510
2572
|
const parts = url.pathname.split("/").filter(Boolean);
|
|
2511
|
-
if (url.protocol === "cohub:" && url.hostname === "works") {
|
|
2573
|
+
if (url.protocol === "cohub:" && (url.hostname === "apps" || url.hostname === "works")) {
|
|
2512
2574
|
const ref = publicRef(parts);
|
|
2513
2575
|
return ref ? {
|
|
2514
2576
|
...ref,
|
|
2515
2577
|
...launchState(url)
|
|
2516
2578
|
} : null;
|
|
2517
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
|
+
}
|
|
2518
2587
|
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
2519
|
-
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] };
|
|
2520
2589
|
if (parts.length === 4 && parts[2] === "w") {
|
|
2521
2590
|
const ref = publicRef([
|
|
2522
2591
|
parts[0],
|
|
@@ -2530,13 +2599,13 @@ function parseUrlRef(value) {
|
|
|
2530
2599
|
}
|
|
2531
2600
|
return null;
|
|
2532
2601
|
}
|
|
2533
|
-
var
|
|
2602
|
+
var AppRefParseError = class extends Error {
|
|
2534
2603
|
constructor() {
|
|
2535
|
-
super("
|
|
2536
|
-
this.name = "
|
|
2604
|
+
super("App must be an id, public URL, cohub://apps URI, or username/space/app reference");
|
|
2605
|
+
this.name = "AppRefParseError";
|
|
2537
2606
|
}
|
|
2538
2607
|
};
|
|
2539
|
-
function
|
|
2608
|
+
function parseAppRef(input) {
|
|
2540
2609
|
const value = input.trim();
|
|
2541
2610
|
if (isUuid(value)) return { id: value };
|
|
2542
2611
|
const parsedUrl = parseUrlRef(value.includes("://") ? value : value.startsWith("/") ? `https://cohub.invalid${value}` : value);
|
|
@@ -2544,21 +2613,29 @@ function parseWorkRef(input) {
|
|
|
2544
2613
|
const parts = value.split("/").filter(Boolean);
|
|
2545
2614
|
const parsedPublic = parts.length === 3 ? publicRef(parts) : null;
|
|
2546
2615
|
if (parsedPublic) return parsedPublic;
|
|
2547
|
-
throw new
|
|
2548
|
-
}
|
|
2549
|
-
function
|
|
2550
|
-
return "id" in ref ? ref.id : `${ref.username}/${ref.spaceSlug}/${ref.
|
|
2551
|
-
}
|
|
2616
|
+
throw new AppRefParseError();
|
|
2617
|
+
}
|
|
2618
|
+
function formatAppRef(ref) {
|
|
2619
|
+
return "id" in ref ? ref.id : `${ref.username}/${ref.spaceSlug}/${ref.appSlug}`;
|
|
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;
|
|
2552
2629
|
//#endregion
|
|
2553
|
-
//#region src/
|
|
2630
|
+
//#region src/app-grant-cache.ts
|
|
2554
2631
|
const STORAGE_PREFIX = "cohub:work-grants";
|
|
2555
2632
|
const CACHE_VERSION = 1;
|
|
2556
2633
|
const MAX_AGE_MS = 336 * 60 * 60 * 1e3;
|
|
2557
2634
|
function isBrowser() {
|
|
2558
2635
|
return typeof localStorage !== "undefined";
|
|
2559
2636
|
}
|
|
2560
|
-
function storageKey(userUuid,
|
|
2561
|
-
return `${STORAGE_PREFIX}:${encodeURIComponent(userUuid)}:${encodeURIComponent(
|
|
2637
|
+
function storageKey(userUuid, appId) {
|
|
2638
|
+
return `${STORAGE_PREFIX}:${encodeURIComponent(userUuid)}:${encodeURIComponent(appId)}:v${CACHE_VERSION}`;
|
|
2562
2639
|
}
|
|
2563
2640
|
function userPrefix(userUuid) {
|
|
2564
2641
|
return `${STORAGE_PREFIX}:${encodeURIComponent(userUuid)}:`;
|
|
@@ -2569,16 +2646,16 @@ function isPermissionArray(value) {
|
|
|
2569
2646
|
function isCachedWorkGrant(value) {
|
|
2570
2647
|
if (!value || typeof value !== "object") return false;
|
|
2571
2648
|
const record = value;
|
|
2572
|
-
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";
|
|
2573
2650
|
}
|
|
2574
|
-
function readEntry(userUuid,
|
|
2651
|
+
function readEntry(userUuid, appId) {
|
|
2575
2652
|
if (!isBrowser()) return null;
|
|
2576
|
-
const key = storageKey(userUuid,
|
|
2653
|
+
const key = storageKey(userUuid, appId);
|
|
2577
2654
|
try {
|
|
2578
2655
|
const raw = localStorage.getItem(key);
|
|
2579
2656
|
if (!raw) return null;
|
|
2580
2657
|
const parsed = JSON.parse(raw);
|
|
2581
|
-
if (!isCachedWorkGrant(parsed) || parsed.userUuid !== userUuid || parsed.
|
|
2658
|
+
if (!isCachedWorkGrant(parsed) || parsed.userUuid !== userUuid || parsed.appId !== appId) {
|
|
2582
2659
|
localStorage.removeItem(key);
|
|
2583
2660
|
return null;
|
|
2584
2661
|
}
|
|
@@ -2598,9 +2675,9 @@ function readEntry(userUuid, workId) {
|
|
|
2598
2675
|
* Returns true when the viewer has previously granted every requested scope
|
|
2599
2676
|
* for this work, allowing a silent re-authorization.
|
|
2600
2677
|
*/
|
|
2601
|
-
function
|
|
2602
|
-
if (!userUuid || !
|
|
2603
|
-
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);
|
|
2604
2681
|
if (!entry) return false;
|
|
2605
2682
|
const granted = new Set(entry.scopes);
|
|
2606
2683
|
return scopes.every((scope) => granted.has(scope));
|
|
@@ -2609,31 +2686,31 @@ function hasGrantedWorkScopes(userUuid, workId, scopes) {
|
|
|
2609
2686
|
* Records the granted scopes for a work, merged with any previously granted
|
|
2610
2687
|
* scopes so a growing permission set stays covered.
|
|
2611
2688
|
*/
|
|
2612
|
-
function
|
|
2613
|
-
if (!userUuid || !
|
|
2614
|
-
const existing = readEntry(userUuid,
|
|
2689
|
+
function setGrantedAppScopes(userUuid, appId, scopes) {
|
|
2690
|
+
if (!userUuid || !appId || scopes.length === 0) return;
|
|
2691
|
+
const existing = readEntry(userUuid, appId);
|
|
2615
2692
|
const merged = Array.from(/* @__PURE__ */ new Set([...existing?.scopes ?? [], ...scopes]));
|
|
2616
2693
|
const entry = {
|
|
2617
2694
|
version: CACHE_VERSION,
|
|
2618
2695
|
userUuid,
|
|
2619
|
-
|
|
2696
|
+
appId,
|
|
2620
2697
|
scopes: merged,
|
|
2621
2698
|
updatedAt: Date.now()
|
|
2622
2699
|
};
|
|
2623
2700
|
if (!isBrowser()) return;
|
|
2624
2701
|
try {
|
|
2625
|
-
localStorage.setItem(storageKey(userUuid,
|
|
2702
|
+
localStorage.setItem(storageKey(userUuid, appId), JSON.stringify(entry));
|
|
2626
2703
|
} catch {}
|
|
2627
2704
|
}
|
|
2628
2705
|
/**
|
|
2629
|
-
* Clears cached grants. Pass a
|
|
2706
|
+
* Clears cached grants. Pass a appId to clear a single work, or omit it to
|
|
2630
2707
|
* clear every cached grant for the user (used on sign-out).
|
|
2631
2708
|
*/
|
|
2632
|
-
function
|
|
2709
|
+
function clearGrantedAppScopes(userUuid, appId) {
|
|
2633
2710
|
if (!isBrowser() || !userUuid) return;
|
|
2634
2711
|
try {
|
|
2635
|
-
if (
|
|
2636
|
-
localStorage.removeItem(storageKey(userUuid,
|
|
2712
|
+
if (appId) {
|
|
2713
|
+
localStorage.removeItem(storageKey(userUuid, appId));
|
|
2637
2714
|
return;
|
|
2638
2715
|
}
|
|
2639
2716
|
const prefix = userPrefix(userUuid);
|
|
@@ -2644,7 +2721,7 @@ function clearGrantedWorkScopes(userUuid, workId) {
|
|
|
2644
2721
|
} catch {}
|
|
2645
2722
|
}
|
|
2646
2723
|
//#endregion
|
|
2647
|
-
//#region src/
|
|
2724
|
+
//#region src/app-bridge-core.ts
|
|
2648
2725
|
function readTokenResponse(value) {
|
|
2649
2726
|
if (!value || typeof value !== "object") return null;
|
|
2650
2727
|
const token = value.token;
|
|
@@ -2654,20 +2731,78 @@ function clonePermissionScopes(scopes) {
|
|
|
2654
2731
|
return Array.from(scopes ?? []).filter((scope) => typeof scope === "string");
|
|
2655
2732
|
}
|
|
2656
2733
|
/**
|
|
2657
|
-
* Framework-agnostic
|
|
2734
|
+
* Framework-agnostic app bridge host core — message handling, app session
|
|
2658
2735
|
* token minting, authorization (with silent re-grant cache), and
|
|
2659
2736
|
* purchase/checkout flow — without any rendering or reactive primitives.
|
|
2660
2737
|
*
|
|
2661
|
-
* Both the Cohub iframe host (
|
|
2738
|
+
* Both the Cohub iframe host (AppSurface, Svelte) and the standalone broker
|
|
2662
2739
|
* page compose this with their own transport-specific reply and auth
|
|
2663
2740
|
* dependencies. External hosts (e.g. Neta-Studio in React) can do the same.
|
|
2664
2741
|
*/
|
|
2665
|
-
function
|
|
2666
|
-
const {
|
|
2742
|
+
function createAppBridgeCore(config) {
|
|
2743
|
+
const { app, reply, getCheckoutState, getAccessToken, getViewerUuid } = config;
|
|
2667
2744
|
const apiOrigin = config.apiOrigin;
|
|
2668
2745
|
const authorizationContext = config.authorizationContext ?? (config.isBackground ? { surface: "background" } : { surface: "page" });
|
|
2669
2746
|
const onStateChange = config.onStateChange;
|
|
2670
|
-
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
|
+
}
|
|
2671
2806
|
const state = {
|
|
2672
2807
|
authOpen: false,
|
|
2673
2808
|
pendingAuth: null,
|
|
@@ -2681,30 +2816,30 @@ function createWorkBridgeCore(config) {
|
|
|
2681
2816
|
function notify() {
|
|
2682
2817
|
onStateChange?.({ ...state });
|
|
2683
2818
|
}
|
|
2684
|
-
const pendingPurchaseStorageKey = `cohub-
|
|
2685
|
-
async function
|
|
2819
|
+
const pendingPurchaseStorageKey = `cohub-app-purchase:${app.id}`;
|
|
2820
|
+
async function isCurrentViewerAppOwner() {
|
|
2686
2821
|
const viewerUuid = await getViewerUuid();
|
|
2687
|
-
return Boolean(viewerUuid && viewerUuid ===
|
|
2822
|
+
return Boolean(viewerUuid && viewerUuid === app.userUuid);
|
|
2688
2823
|
}
|
|
2689
2824
|
function allowsOwnerAutoAuthorization() {
|
|
2690
|
-
return authorizationContext.surface === "background" || authorizationContext.surface === "
|
|
2825
|
+
return authorizationContext.surface === "background" || authorizationContext.surface === "app";
|
|
2691
2826
|
}
|
|
2692
2827
|
async function ensureBaseToken(forceRefresh = false) {
|
|
2693
|
-
if (
|
|
2828
|
+
if (appToken && !forceRefresh) return appToken;
|
|
2694
2829
|
const userToken = await getAccessToken({ forceRefresh });
|
|
2695
2830
|
if (!userToken) {
|
|
2696
2831
|
await config.requestSignIn(typeof location !== "undefined" ? location.pathname : "/");
|
|
2697
2832
|
return null;
|
|
2698
2833
|
}
|
|
2699
|
-
const response = await fetch(`${apiOrigin}/api/
|
|
2834
|
+
const response = await fetch(`${apiOrigin}/api/apps/${app.id}/session`, {
|
|
2700
2835
|
method: "POST",
|
|
2701
2836
|
headers: { Authorization: `Bearer ${userToken}` }
|
|
2702
2837
|
});
|
|
2703
|
-
if (!response.ok) throw new Error("Failed to create
|
|
2838
|
+
if (!response.ok) throw new Error("Failed to create app session.");
|
|
2704
2839
|
const token = readTokenResponse(await response.json());
|
|
2705
|
-
if (!token) throw new Error("Invalid
|
|
2706
|
-
|
|
2707
|
-
return
|
|
2840
|
+
if (!token) throw new Error("Invalid app session response.");
|
|
2841
|
+
appToken = token;
|
|
2842
|
+
return appToken;
|
|
2708
2843
|
}
|
|
2709
2844
|
async function authorize(scopes) {
|
|
2710
2845
|
const userToken = await getAccessToken();
|
|
@@ -2712,7 +2847,7 @@ function createWorkBridgeCore(config) {
|
|
|
2712
2847
|
await config.requestSignIn(typeof location !== "undefined" ? location.pathname : "/");
|
|
2713
2848
|
return null;
|
|
2714
2849
|
}
|
|
2715
|
-
const response = await fetch(`${apiOrigin}/api/
|
|
2850
|
+
const response = await fetch(`${apiOrigin}/api/apps/${app.id}/authorize`, {
|
|
2716
2851
|
method: "POST",
|
|
2717
2852
|
headers: {
|
|
2718
2853
|
Authorization: `Bearer ${userToken}`,
|
|
@@ -2722,9 +2857,9 @@ function createWorkBridgeCore(config) {
|
|
|
2722
2857
|
});
|
|
2723
2858
|
if (!response.ok) throw new Error((await response.json().catch(() => null))?.message ?? "Authorization failed.");
|
|
2724
2859
|
const token = readTokenResponse(await response.json());
|
|
2725
|
-
if (!token) throw new Error("Invalid
|
|
2726
|
-
|
|
2727
|
-
return
|
|
2860
|
+
if (!token) throw new Error("Invalid app authorization response.");
|
|
2861
|
+
appToken = token;
|
|
2862
|
+
return appToken;
|
|
2728
2863
|
}
|
|
2729
2864
|
function writePendingPurchase(input) {
|
|
2730
2865
|
if (typeof sessionStorage === "undefined") return;
|
|
@@ -2763,7 +2898,7 @@ function createWorkBridgeCore(config) {
|
|
|
2763
2898
|
return null;
|
|
2764
2899
|
}
|
|
2765
2900
|
const promotionAttribution = config.getPromotionAttribution?.() ?? null;
|
|
2766
|
-
const response = await fetch(`${apiOrigin}/api/
|
|
2901
|
+
const response = await fetch(`${apiOrigin}/api/apps/${app.id}/commerce/purchase`, {
|
|
2767
2902
|
method: "POST",
|
|
2768
2903
|
headers: {
|
|
2769
2904
|
Authorization: `Bearer ${userToken}`,
|
|
@@ -2781,62 +2916,49 @@ function createWorkBridgeCore(config) {
|
|
|
2781
2916
|
async function handleMessage(event) {
|
|
2782
2917
|
const data = event.data;
|
|
2783
2918
|
if (!data?.requestId) return;
|
|
2919
|
+
const isLegacyWork = data.type?.startsWith("cohub.work.") === true;
|
|
2920
|
+
if (isLegacyWork) legacyRequestIds.add(data.requestId);
|
|
2784
2921
|
try {
|
|
2785
|
-
if (data.type === "cohub.work.context") {
|
|
2786
|
-
const
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
work: {
|
|
2792
|
-
id: work.id,
|
|
2793
|
-
slug: work.slug,
|
|
2794
|
-
url: typeof location !== "undefined" ? location.href : ""
|
|
2795
|
-
},
|
|
2796
|
-
space: { id: work.spaceId },
|
|
2797
|
-
viewer: viewerUuid ? { userUuid: viewerUuid } : null,
|
|
2798
|
-
...config.invocation ? { invocation: { ...config.invocation } } : {},
|
|
2799
|
-
permissions: {
|
|
2800
|
-
scopes: workScopes,
|
|
2801
|
-
workScopes,
|
|
2802
|
-
viewerScopes: []
|
|
2803
|
-
}
|
|
2804
|
-
}
|
|
2805
|
-
});
|
|
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);
|
|
2806
2928
|
}
|
|
2807
|
-
if (data.type === "cohub.work.token") {
|
|
2929
|
+
if (data.type === "cohub.app.token" || data.type === "cohub.work.token") {
|
|
2808
2930
|
const token = await ensureBaseToken(Boolean(data.forceRefresh));
|
|
2809
|
-
|
|
2810
|
-
type: "cohub.
|
|
2931
|
+
replyForRequest(data.requestId, {
|
|
2932
|
+
type: "cohub.app.token.result",
|
|
2811
2933
|
token
|
|
2812
|
-
});
|
|
2934
|
+
}, true);
|
|
2813
2935
|
}
|
|
2814
|
-
if (data.type === "cohub.work.checkout-state") {
|
|
2936
|
+
if (data.type === "cohub.app.checkout-state" || data.type === "cohub.work.checkout-state") {
|
|
2815
2937
|
const pending = readPendingPurchase();
|
|
2816
2938
|
const checkoutState = getCheckoutState();
|
|
2817
2939
|
const orderId = checkoutState.orderId ?? pending?.orderId ?? null;
|
|
2818
2940
|
if (checkoutState.status && checkoutState.orderId) clearPendingPurchase();
|
|
2819
|
-
|
|
2820
|
-
type: "cohub.
|
|
2941
|
+
replyForRequest(data.requestId, {
|
|
2942
|
+
type: "cohub.app.checkout-state.result",
|
|
2821
2943
|
status: checkoutState.status,
|
|
2822
2944
|
orderId
|
|
2823
|
-
});
|
|
2945
|
+
}, true);
|
|
2824
2946
|
}
|
|
2825
|
-
if (data.type === "cohub.work.purchase") {
|
|
2947
|
+
if (data.type === "cohub.app.purchase" || data.type === "cohub.work.purchase") {
|
|
2826
2948
|
const productKey = typeof data.productKey === "string" ? data.productKey.trim() : "";
|
|
2827
2949
|
if (!productKey) {
|
|
2828
|
-
|
|
2829
|
-
type: "cohub.
|
|
2950
|
+
replyForRequest(data.requestId, {
|
|
2951
|
+
type: "cohub.app.error",
|
|
2830
2952
|
message: "Product key is required."
|
|
2831
|
-
});
|
|
2953
|
+
}, true);
|
|
2832
2954
|
return;
|
|
2833
2955
|
}
|
|
2834
2956
|
const purchaseAttemptId = (typeof data.purchaseAttemptId === "string" ? data.purchaseAttemptId.trim() : "") || data.requestId.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 128);
|
|
2835
2957
|
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(purchaseAttemptId)) {
|
|
2836
|
-
|
|
2837
|
-
type: "cohub.
|
|
2958
|
+
replyForRequest(data.requestId, {
|
|
2959
|
+
type: "cohub.app.error",
|
|
2838
2960
|
message: "Purchase attempt id is invalid."
|
|
2839
|
-
});
|
|
2961
|
+
}, true);
|
|
2840
2962
|
return;
|
|
2841
2963
|
}
|
|
2842
2964
|
state.pendingPurchase = {
|
|
@@ -2849,34 +2971,34 @@ function createWorkBridgeCore(config) {
|
|
|
2849
2971
|
notify();
|
|
2850
2972
|
config.onPurchaseRequested?.({ ...state.pendingPurchase });
|
|
2851
2973
|
}
|
|
2852
|
-
if (data.type === "cohub.work.authorize") {
|
|
2853
|
-
const allowedViewerScopes = clonePermissionScopes(
|
|
2974
|
+
if (data.type === "cohub.app.authorize" || data.type === "cohub.work.authorize") {
|
|
2975
|
+
const allowedViewerScopes = clonePermissionScopes(app.allowedViewerScopes);
|
|
2854
2976
|
const scopes = clonePermissionScopes(data.scopes).filter((scope) => allowedViewerScopes.includes(scope));
|
|
2855
2977
|
if (scopes.length === 0) {
|
|
2856
|
-
|
|
2857
|
-
type: "cohub.
|
|
2978
|
+
replyForRequest(data.requestId, {
|
|
2979
|
+
type: "cohub.app.error",
|
|
2858
2980
|
message: "No allowed scopes requested."
|
|
2859
|
-
});
|
|
2981
|
+
}, true);
|
|
2860
2982
|
return;
|
|
2861
2983
|
}
|
|
2862
|
-
if (allowsOwnerAutoAuthorization() && await
|
|
2984
|
+
if (allowsOwnerAutoAuthorization() && await isCurrentViewerAppOwner()) {
|
|
2863
2985
|
const token = await authorize(scopes);
|
|
2864
|
-
|
|
2865
|
-
type: "cohub.
|
|
2986
|
+
replyForRequest(data.requestId, {
|
|
2987
|
+
type: "cohub.app.authorize.result",
|
|
2866
2988
|
token
|
|
2867
|
-
});
|
|
2989
|
+
}, true);
|
|
2868
2990
|
return;
|
|
2869
2991
|
}
|
|
2870
2992
|
const viewerUuid = await getViewerUuid();
|
|
2871
|
-
if (viewerUuid &&
|
|
2993
|
+
if (viewerUuid && hasGrantedAppScopes(viewerUuid, app.id, scopes)) try {
|
|
2872
2994
|
const token = await authorize(scopes);
|
|
2873
|
-
|
|
2874
|
-
type: "cohub.
|
|
2995
|
+
replyForRequest(data.requestId, {
|
|
2996
|
+
type: "cohub.app.authorize.result",
|
|
2875
2997
|
token
|
|
2876
|
-
});
|
|
2998
|
+
}, true);
|
|
2877
2999
|
return;
|
|
2878
3000
|
} catch {
|
|
2879
|
-
|
|
3001
|
+
clearGrantedAppScopes(viewerUuid, app.id);
|
|
2880
3002
|
}
|
|
2881
3003
|
state.pendingAuth = {
|
|
2882
3004
|
requestId: data.requestId,
|
|
@@ -2888,19 +3010,19 @@ function createWorkBridgeCore(config) {
|
|
|
2888
3010
|
notify();
|
|
2889
3011
|
}
|
|
2890
3012
|
} catch (error) {
|
|
2891
|
-
|
|
2892
|
-
type: "cohub.
|
|
3013
|
+
replyForRequest(data.requestId, {
|
|
3014
|
+
type: "cohub.app.error",
|
|
2893
3015
|
message: error instanceof Error ? error.message : "Request failed."
|
|
2894
|
-
});
|
|
3016
|
+
}, true);
|
|
2895
3017
|
}
|
|
2896
3018
|
}
|
|
2897
3019
|
function cancelAuth() {
|
|
2898
3020
|
if (state.authSaving) return;
|
|
2899
3021
|
if (!state.pendingAuth) return;
|
|
2900
|
-
|
|
2901
|
-
type: "cohub.
|
|
3022
|
+
replyForRequest(state.pendingAuth.requestId, {
|
|
3023
|
+
type: "cohub.app.authorize.result",
|
|
2902
3024
|
token: null
|
|
2903
|
-
});
|
|
3025
|
+
}, true);
|
|
2904
3026
|
state.authOpen = false;
|
|
2905
3027
|
state.pendingAuth = null;
|
|
2906
3028
|
state.authError = null;
|
|
@@ -2910,10 +3032,10 @@ function createWorkBridgeCore(config) {
|
|
|
2910
3032
|
function cancelPurchase() {
|
|
2911
3033
|
if (state.purchaseSaving) return;
|
|
2912
3034
|
if (!state.pendingPurchase) return;
|
|
2913
|
-
|
|
2914
|
-
type: "cohub.
|
|
3035
|
+
replyForRequest(state.pendingPurchase.requestId, {
|
|
3036
|
+
type: "cohub.app.purchase.result",
|
|
2915
3037
|
checkout: null
|
|
2916
|
-
});
|
|
3038
|
+
}, true);
|
|
2917
3039
|
state.purchaseOpen = false;
|
|
2918
3040
|
state.purchaseError = null;
|
|
2919
3041
|
state.pendingPurchase = null;
|
|
@@ -2927,10 +3049,10 @@ function createWorkBridgeCore(config) {
|
|
|
2927
3049
|
notify();
|
|
2928
3050
|
try {
|
|
2929
3051
|
const checkout = await createPurchase(state.pendingPurchase.productKey, state.pendingPurchase.purchaseAttemptId);
|
|
2930
|
-
|
|
2931
|
-
type: "cohub.
|
|
3052
|
+
replyForRequest(state.pendingPurchase.requestId, {
|
|
3053
|
+
type: "cohub.app.purchase.result",
|
|
2932
3054
|
checkout
|
|
2933
|
-
});
|
|
3055
|
+
}, true);
|
|
2934
3056
|
if (checkout && typeof checkout === "object") {
|
|
2935
3057
|
const next = checkout;
|
|
2936
3058
|
if (typeof next.orderId === "string" && typeof next.productKey === "string") writePendingPurchase({
|
|
@@ -2963,11 +3085,11 @@ function createWorkBridgeCore(config) {
|
|
|
2963
3085
|
notify();
|
|
2964
3086
|
try {
|
|
2965
3087
|
const token = await authorize(state.pendingAuth.scopes);
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
type: "cohub.
|
|
3088
|
+
setGrantedAppScopes(await getViewerUuid(), app.id, state.pendingAuth.scopes);
|
|
3089
|
+
replyForRequest(state.pendingAuth.requestId, {
|
|
3090
|
+
type: "cohub.app.authorize.result",
|
|
2969
3091
|
token
|
|
2970
|
-
});
|
|
3092
|
+
}, true);
|
|
2971
3093
|
state.authOpen = false;
|
|
2972
3094
|
state.pendingAuth = null;
|
|
2973
3095
|
} catch (error) {
|
|
@@ -2980,6 +3102,7 @@ function createWorkBridgeCore(config) {
|
|
|
2980
3102
|
return {
|
|
2981
3103
|
getState: () => ({ ...state }),
|
|
2982
3104
|
handleMessage,
|
|
3105
|
+
notifyContextChanged,
|
|
2983
3106
|
confirmAuth,
|
|
2984
3107
|
cancelAuth,
|
|
2985
3108
|
confirmPurchase,
|
|
@@ -3220,4 +3343,4 @@ function createBoardExtensionRegistry() {
|
|
|
3220
3343
|
}
|
|
3221
3344
|
const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
|
|
3222
3345
|
//#endregion
|
|
3223
|
-
export { 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,
|
|
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 };
|