@shadowob/sdk 1.1.3 → 1.1.5
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/index.cjs +468 -8
- package/dist/index.d.cts +703 -22
- package/dist/index.d.ts +703 -22
- package/dist/index.js +467 -8
- package/package.json +12 -2
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,7 @@ __export(index_exports, {
|
|
|
34
34
|
SERVER_EVENTS: () => import_shared.SERVER_EVENTS,
|
|
35
35
|
ShadowClient: () => ShadowClient,
|
|
36
36
|
ShadowSocket: () => ShadowSocket,
|
|
37
|
+
ShadowVoiceConsumer: () => ShadowVoiceConsumer,
|
|
37
38
|
channelRoom: () => channelRoom,
|
|
38
39
|
threadRoom: () => threadRoom,
|
|
39
40
|
userRoom: () => userRoom
|
|
@@ -87,12 +88,13 @@ var ShadowClient = class {
|
|
|
87
88
|
const url = `${this.baseUrl}${path}`;
|
|
88
89
|
const controller = new AbortController();
|
|
89
90
|
const timeout = setTimeout(() => controller.abort(), 6e4);
|
|
91
|
+
const isFormData = init?.body instanceof FormData;
|
|
90
92
|
try {
|
|
91
93
|
const res = await fetch(url, {
|
|
92
94
|
...init,
|
|
93
95
|
signal: init?.signal ?? controller.signal,
|
|
94
96
|
headers: {
|
|
95
|
-
"Content-Type": "application/json",
|
|
97
|
+
...!isFormData ? { "Content-Type": "application/json" } : {},
|
|
96
98
|
Authorization: `Bearer ${this.token}`,
|
|
97
99
|
...init?.headers
|
|
98
100
|
}
|
|
@@ -217,8 +219,11 @@ var ShadowClient = class {
|
|
|
217
219
|
});
|
|
218
220
|
}
|
|
219
221
|
// ── Agents ────────────────────────────────────────────────────────────
|
|
220
|
-
async listAgents() {
|
|
221
|
-
|
|
222
|
+
async listAgents(options) {
|
|
223
|
+
const params = new URLSearchParams();
|
|
224
|
+
if (options?.includeRentals) params.set("includeRentals", "true");
|
|
225
|
+
const query = params.toString();
|
|
226
|
+
return this.request(`/api/agents${query ? `?${query}` : ""}`);
|
|
222
227
|
}
|
|
223
228
|
async createAgent(data) {
|
|
224
229
|
return this.request("/api/agents", {
|
|
@@ -334,15 +339,113 @@ var ShadowClient = class {
|
|
|
334
339
|
async getServerAccess(serverIdOrSlug) {
|
|
335
340
|
return this.request(`/api/servers/${serverIdOrSlug}/access`);
|
|
336
341
|
}
|
|
342
|
+
// ── Server App Integrations ───────────────────────────────────────────
|
|
343
|
+
async listServerApps(serverIdOrSlug) {
|
|
344
|
+
return this.request(`/api/servers/${serverIdOrSlug}/apps`);
|
|
345
|
+
}
|
|
346
|
+
async listServerAppCatalog(serverIdOrSlug) {
|
|
347
|
+
return this.request(`/api/servers/${serverIdOrSlug}/apps/catalog`);
|
|
348
|
+
}
|
|
349
|
+
async discoverServerApp(serverIdOrSlug, data) {
|
|
350
|
+
return this.request(`/api/servers/${serverIdOrSlug}/apps/discover`, {
|
|
351
|
+
method: "POST",
|
|
352
|
+
body: JSON.stringify(data)
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
async installServerApp(serverIdOrSlug, data) {
|
|
356
|
+
return this.request(`/api/servers/${serverIdOrSlug}/apps`, {
|
|
357
|
+
method: "POST",
|
|
358
|
+
body: JSON.stringify(data)
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
async installServerAppFromCatalog(serverIdOrSlug, catalogEntryId, data = {}) {
|
|
362
|
+
return this.request(
|
|
363
|
+
`/api/servers/${serverIdOrSlug}/apps/catalog/${encodeURIComponent(catalogEntryId)}/install`,
|
|
364
|
+
{
|
|
365
|
+
method: "POST",
|
|
366
|
+
body: JSON.stringify(data)
|
|
367
|
+
}
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
async getServerApp(serverIdOrSlug, appKey) {
|
|
371
|
+
return this.request(`/api/servers/${serverIdOrSlug}/apps/${encodeURIComponent(appKey)}`);
|
|
372
|
+
}
|
|
373
|
+
async deleteServerApp(serverIdOrSlug, appKey) {
|
|
374
|
+
return this.request(`/api/servers/${serverIdOrSlug}/apps/${encodeURIComponent(appKey)}`, {
|
|
375
|
+
method: "DELETE"
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
async grantServerAppToBuddy(serverIdOrSlug, appKey, data) {
|
|
379
|
+
return this.request(
|
|
380
|
+
`/api/servers/${serverIdOrSlug}/apps/${encodeURIComponent(appKey)}/grants`,
|
|
381
|
+
{
|
|
382
|
+
method: "POST",
|
|
383
|
+
body: JSON.stringify(data)
|
|
384
|
+
}
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
async getServerAppSkills(serverIdOrSlug, appKey) {
|
|
388
|
+
return this.request(`/api/servers/${serverIdOrSlug}/apps/${encodeURIComponent(appKey)}/skills`);
|
|
389
|
+
}
|
|
390
|
+
async createServerAppLaunch(serverIdOrSlug, appKey) {
|
|
391
|
+
return this.request(
|
|
392
|
+
`/api/servers/${serverIdOrSlug}/apps/${encodeURIComponent(appKey)}/launch`,
|
|
393
|
+
{
|
|
394
|
+
method: "POST"
|
|
395
|
+
}
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
async introspectServerAppToken(serverIdOrSlug, appKey, token) {
|
|
399
|
+
const url = `${this.baseUrl}/api/servers/${serverIdOrSlug}/apps/${encodeURIComponent(
|
|
400
|
+
appKey
|
|
401
|
+
)}/oauth/introspect`;
|
|
402
|
+
const res = await fetch(url, {
|
|
403
|
+
method: "POST",
|
|
404
|
+
headers: {
|
|
405
|
+
Authorization: `Bearer ${token}`,
|
|
406
|
+
"Content-Type": "application/json"
|
|
407
|
+
},
|
|
408
|
+
body: JSON.stringify({ token })
|
|
409
|
+
});
|
|
410
|
+
if (!res.ok) {
|
|
411
|
+
const body = await res.text().catch(() => "");
|
|
412
|
+
const message = sanitizeErrorBody(body);
|
|
413
|
+
throw new Error(`Shadow API POST /oauth/introspect failed (${res.status}): ${message}`);
|
|
414
|
+
}
|
|
415
|
+
return await res.json();
|
|
416
|
+
}
|
|
417
|
+
async callServerAppCommand(serverIdOrSlug, appKey, commandName, data) {
|
|
418
|
+
return this.request(
|
|
419
|
+
`/api/servers/${serverIdOrSlug}/apps/${encodeURIComponent(appKey)}/commands/${encodeURIComponent(
|
|
420
|
+
commandName
|
|
421
|
+
)}`,
|
|
422
|
+
{
|
|
423
|
+
method: "POST",
|
|
424
|
+
body: JSON.stringify(data ?? {})
|
|
425
|
+
}
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
async callServerAppCommandMultipart(serverIdOrSlug, appKey, commandName, data) {
|
|
429
|
+
const form = new FormData();
|
|
430
|
+
form.set("input", JSON.stringify(data.input ?? {}));
|
|
431
|
+
if (data.channelId) form.set("channelId", data.channelId);
|
|
432
|
+
form.set(data.field ?? "file", data.file, data.filename);
|
|
433
|
+
return this.request(
|
|
434
|
+
`/api/servers/${serverIdOrSlug}/apps/${encodeURIComponent(appKey)}/commands/${encodeURIComponent(
|
|
435
|
+
commandName
|
|
436
|
+
)}`,
|
|
437
|
+
{
|
|
438
|
+
method: "POST",
|
|
439
|
+
body: form
|
|
440
|
+
}
|
|
441
|
+
);
|
|
442
|
+
}
|
|
337
443
|
async updateServer(serverIdOrSlug, data) {
|
|
338
444
|
return this.request(`/api/servers/${serverIdOrSlug}`, {
|
|
339
445
|
method: "PATCH",
|
|
340
446
|
body: JSON.stringify(data)
|
|
341
447
|
});
|
|
342
448
|
}
|
|
343
|
-
async updateServerHomepage(serverIdOrSlug, homepageHtml) {
|
|
344
|
-
return this.updateServer(serverIdOrSlug, { homepageHtml });
|
|
345
|
-
}
|
|
346
449
|
async deleteServer(serverId) {
|
|
347
450
|
return this.request(`/api/servers/${serverId}`, { method: "DELETE" });
|
|
348
451
|
}
|
|
@@ -407,6 +510,14 @@ var ShadowClient = class {
|
|
|
407
510
|
const ch = await this.request(`/api/channels/${channelId}`);
|
|
408
511
|
return { ...ch, description: ch.topic };
|
|
409
512
|
}
|
|
513
|
+
async getChannelBootstrap(channelId, options) {
|
|
514
|
+
const params = new URLSearchParams();
|
|
515
|
+
if (options?.messagesLimit) params.set("messagesLimit", String(options.messagesLimit));
|
|
516
|
+
const query = params.toString();
|
|
517
|
+
return this.request(
|
|
518
|
+
`/api/channels/${channelId}/bootstrap${query ? `?${query}` : ""}`
|
|
519
|
+
);
|
|
520
|
+
}
|
|
410
521
|
async getChannelAccess(channelId) {
|
|
411
522
|
return this.request(`/api/channels/${channelId}/access`);
|
|
412
523
|
}
|
|
@@ -454,6 +565,43 @@ var ShadowClient = class {
|
|
|
454
565
|
async removeChannelMember(channelId, userId) {
|
|
455
566
|
return this.request(`/api/channels/${channelId}/members/${userId}`, { method: "DELETE" });
|
|
456
567
|
}
|
|
568
|
+
async getVoiceState(channelId) {
|
|
569
|
+
return this.request(`/api/channels/${channelId}/voice/state`);
|
|
570
|
+
}
|
|
571
|
+
async joinVoiceChannel(channelId, options) {
|
|
572
|
+
return this.request(`/api/channels/${channelId}/voice/join`, {
|
|
573
|
+
method: "POST",
|
|
574
|
+
body: JSON.stringify(options ?? {})
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
async renewVoiceCredentials(channelId, options) {
|
|
578
|
+
return this.request(`/api/channels/${channelId}/voice/renew`, {
|
|
579
|
+
method: "POST",
|
|
580
|
+
body: JSON.stringify(options ?? {})
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
async leaveVoiceChannel(channelId, options) {
|
|
584
|
+
return this.request(`/api/channels/${channelId}/voice/leave`, {
|
|
585
|
+
method: "POST",
|
|
586
|
+
body: JSON.stringify(options ?? {})
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
async updateVoiceState(channelId, data) {
|
|
590
|
+
return this.request(`/api/channels/${channelId}/voice/state`, {
|
|
591
|
+
method: "PATCH",
|
|
592
|
+
body: JSON.stringify(data)
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
async getVoicePolicy(channelId, agentId) {
|
|
596
|
+
const params = new URLSearchParams({ agentId });
|
|
597
|
+
return this.request(`/api/channels/${channelId}/voice-policy?${params}`);
|
|
598
|
+
}
|
|
599
|
+
async updateVoicePolicy(channelId, data) {
|
|
600
|
+
return this.request(`/api/channels/${channelId}/voice-policy`, {
|
|
601
|
+
method: "PUT",
|
|
602
|
+
body: JSON.stringify(data)
|
|
603
|
+
});
|
|
604
|
+
}
|
|
457
605
|
// ── Channel Buddy Policy ─────────────────────────────────────────────
|
|
458
606
|
async setBuddyPolicy(channelId, agentId, data) {
|
|
459
607
|
return this.request(`/api/channels/${channelId}/agents/${agentId}/policy`, {
|
|
@@ -670,9 +818,19 @@ var ShadowClient = class {
|
|
|
670
818
|
return res.json();
|
|
671
819
|
}
|
|
672
820
|
async resolveAttachmentMediaUrl(attachmentId, options) {
|
|
673
|
-
const
|
|
821
|
+
const params = new URLSearchParams();
|
|
822
|
+
params.set("disposition", options?.disposition ?? "inline");
|
|
823
|
+
if (options?.variant) params.set("variant", options.variant);
|
|
674
824
|
return this.request(
|
|
675
|
-
`/api/attachments/${attachmentId}/media-url
|
|
825
|
+
`/api/attachments/${attachmentId}/media-url?${params}`
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
async resolveWorkspaceMediaUrl(serverId, fileId, options) {
|
|
829
|
+
const params = new URLSearchParams();
|
|
830
|
+
params.set("disposition", options?.disposition ?? "inline");
|
|
831
|
+
if (options?.contentRef) params.set("contentRef", options.contentRef);
|
|
832
|
+
return this.request(
|
|
833
|
+
`/api/servers/${serverId}/workspace/files/${fileId}/media-url?${params}`
|
|
676
834
|
);
|
|
677
835
|
}
|
|
678
836
|
/**
|
|
@@ -898,9 +1056,21 @@ var ShadowClient = class {
|
|
|
898
1056
|
async listOAuthAccounts() {
|
|
899
1057
|
return this.request("/api/auth/oauth/accounts");
|
|
900
1058
|
}
|
|
1059
|
+
async createOAuthConnectUrl(provider, redirect) {
|
|
1060
|
+
return this.request(`/api/auth/oauth/${provider}/link`, {
|
|
1061
|
+
method: "POST",
|
|
1062
|
+
body: JSON.stringify({ redirect })
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
901
1065
|
async unlinkOAuthAccount(accountId) {
|
|
902
1066
|
return this.request(`/api/auth/oauth/accounts/${accountId}`, { method: "DELETE" });
|
|
903
1067
|
}
|
|
1068
|
+
async listAuthSessions() {
|
|
1069
|
+
return this.request("/api/auth/sessions");
|
|
1070
|
+
}
|
|
1071
|
+
async revokeAuthSession(sessionId) {
|
|
1072
|
+
return this.request(`/api/auth/sessions/${sessionId}`, { method: "DELETE" });
|
|
1073
|
+
}
|
|
904
1074
|
async changePassword(data) {
|
|
905
1075
|
return this.request("/api/auth/password", {
|
|
906
1076
|
method: "PUT",
|
|
@@ -1028,6 +1198,21 @@ var ShadowClient = class {
|
|
|
1028
1198
|
body: JSON.stringify({ appId })
|
|
1029
1199
|
});
|
|
1030
1200
|
}
|
|
1201
|
+
async sendOAuthChannelMessage(channelId, content, opts) {
|
|
1202
|
+
return this.request(`/api/oauth/channels/${channelId}/messages`, {
|
|
1203
|
+
method: "POST",
|
|
1204
|
+
body: JSON.stringify({
|
|
1205
|
+
content,
|
|
1206
|
+
...opts?.metadata ? { metadata: opts.metadata } : {}
|
|
1207
|
+
})
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
async sendOAuthBuddyMessage(buddyId, data) {
|
|
1211
|
+
return this.request(`/api/oauth/buddies/${buddyId}/messages`, {
|
|
1212
|
+
method: "POST",
|
|
1213
|
+
body: JSON.stringify(data)
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1031
1216
|
// ── Marketplace / Rentals ─────────────────────────────────────────────
|
|
1032
1217
|
async browseListings(params) {
|
|
1033
1218
|
const qs = new URLSearchParams();
|
|
@@ -1135,6 +1320,9 @@ var ShadowClient = class {
|
|
|
1135
1320
|
async getScopeNeutralProduct(productId) {
|
|
1136
1321
|
return this.request(`/api/products/${productId}`);
|
|
1137
1322
|
}
|
|
1323
|
+
async getCommerceProductContext(productId) {
|
|
1324
|
+
return this.request(`/api/commerce/products/${productId}/context`);
|
|
1325
|
+
}
|
|
1138
1326
|
async getShopProduct(shopId, productId) {
|
|
1139
1327
|
return this.request(`/api/shops/${shopId}/products/${productId}`);
|
|
1140
1328
|
}
|
|
@@ -1325,6 +1513,11 @@ var ShadowClient = class {
|
|
|
1325
1513
|
method: "POST"
|
|
1326
1514
|
});
|
|
1327
1515
|
}
|
|
1516
|
+
async completeOrder(serverId, orderId) {
|
|
1517
|
+
return this.request(`/api/servers/${serverId}/shop/orders/${orderId}/complete`, {
|
|
1518
|
+
method: "POST"
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1328
1521
|
async getProductReviews(serverId, productId) {
|
|
1329
1522
|
return this.request(`/api/servers/${serverId}/shop/products/${productId}/reviews`);
|
|
1330
1523
|
}
|
|
@@ -1560,6 +1753,23 @@ var ShadowClient = class {
|
|
|
1560
1753
|
async getAllEntitlements() {
|
|
1561
1754
|
return this.request("/api/entitlements");
|
|
1562
1755
|
}
|
|
1756
|
+
async getEntitlement(entitlementId) {
|
|
1757
|
+
return this.request(`/api/entitlements/${entitlementId}`);
|
|
1758
|
+
}
|
|
1759
|
+
async getOAuthCommerceEntitlementAccess(params) {
|
|
1760
|
+
const qs = new URLSearchParams();
|
|
1761
|
+
if (params?.resourceType) qs.set("resourceType", params.resourceType);
|
|
1762
|
+
if (params?.resourceId) qs.set("resourceId", params.resourceId);
|
|
1763
|
+
if (params?.capability) qs.set("capability", params.capability);
|
|
1764
|
+
const query = qs.toString();
|
|
1765
|
+
return this.request(`/api/oauth/commerce/entitlements${query ? `?${query}` : ""}`);
|
|
1766
|
+
}
|
|
1767
|
+
async redeemOAuthCommerceEntitlement(data) {
|
|
1768
|
+
return this.request("/api/oauth/commerce/entitlements/redeem", {
|
|
1769
|
+
method: "POST",
|
|
1770
|
+
body: JSON.stringify(data)
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1563
1773
|
async verifyEntitlement(entitlementId) {
|
|
1564
1774
|
return this.request(`/api/entitlements/${entitlementId}/verify`);
|
|
1565
1775
|
}
|
|
@@ -1569,6 +1779,12 @@ var ShadowClient = class {
|
|
|
1569
1779
|
body: JSON.stringify({ reason })
|
|
1570
1780
|
});
|
|
1571
1781
|
}
|
|
1782
|
+
async cancelEntitlementRenewal(entitlementId, reason) {
|
|
1783
|
+
return this.request(`/api/entitlements/${entitlementId}/cancel-renewal`, {
|
|
1784
|
+
method: "POST",
|
|
1785
|
+
body: JSON.stringify({ reason })
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1572
1788
|
// ── Task Center ───────────────────────────────────────────────────────
|
|
1573
1789
|
async getTaskCenter() {
|
|
1574
1790
|
return this.request("/api/tasks");
|
|
@@ -1609,6 +1825,16 @@ var ShadowClient = class {
|
|
|
1609
1825
|
if (params?.limit) qs.set("limit", String(params.limit));
|
|
1610
1826
|
return this.request(`/api/discover/search?${qs}`);
|
|
1611
1827
|
}
|
|
1828
|
+
async discoverCommerce(params) {
|
|
1829
|
+
const qs = new URLSearchParams();
|
|
1830
|
+
if (params?.q) qs.set("q", params.q);
|
|
1831
|
+
if (params?.limit) qs.set("limit", String(params.limit));
|
|
1832
|
+
const suffix = qs.toString();
|
|
1833
|
+
return this.request(`/api/discover/business${suffix ? `?${suffix}` : ""}`);
|
|
1834
|
+
}
|
|
1835
|
+
async discoverBusinessHub(params) {
|
|
1836
|
+
return this.discoverCommerce(params);
|
|
1837
|
+
}
|
|
1612
1838
|
// ── Voice Enhance ─────────────────────────────────────────────────────
|
|
1613
1839
|
async enhanceVoice(data) {
|
|
1614
1840
|
return this.request("/api/voice/enhance", {
|
|
@@ -1818,6 +2044,53 @@ var ShadowSocket = class {
|
|
|
1818
2044
|
leaveChannel(channelId) {
|
|
1819
2045
|
this.socket.emit("channel:leave", { channelId });
|
|
1820
2046
|
}
|
|
2047
|
+
joinVoiceChannel(channelId, options) {
|
|
2048
|
+
return new Promise((resolve) => {
|
|
2049
|
+
this.socket.emit(
|
|
2050
|
+
"voice:join",
|
|
2051
|
+
{ channelId, ...options },
|
|
2052
|
+
(res) => {
|
|
2053
|
+
resolve(res ?? { ok: false, error: "Voice join failed" });
|
|
2054
|
+
}
|
|
2055
|
+
);
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
leaveVoiceChannel(channelId, options) {
|
|
2059
|
+
return new Promise((resolve) => {
|
|
2060
|
+
this.socket.emit(
|
|
2061
|
+
"voice:leave",
|
|
2062
|
+
{ channelId, ...options },
|
|
2063
|
+
(res) => {
|
|
2064
|
+
resolve(res ?? { ok: true });
|
|
2065
|
+
}
|
|
2066
|
+
);
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
renewVoiceCredentials(channelId, options) {
|
|
2070
|
+
return new Promise((resolve) => {
|
|
2071
|
+
this.socket.emit(
|
|
2072
|
+
"voice:token:renew",
|
|
2073
|
+
{ channelId, ...options },
|
|
2074
|
+
(res) => {
|
|
2075
|
+
resolve(res ?? { ok: false, error: "Voice token renewal failed" });
|
|
2076
|
+
}
|
|
2077
|
+
);
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
updateVoiceState(channelId, data) {
|
|
2081
|
+
return new Promise((resolve) => {
|
|
2082
|
+
this.socket.emit(
|
|
2083
|
+
"voice:state:update",
|
|
2084
|
+
{ channelId, ...data },
|
|
2085
|
+
(res) => {
|
|
2086
|
+
resolve(res ?? { ok: true });
|
|
2087
|
+
}
|
|
2088
|
+
);
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
2091
|
+
sendVoiceHeartbeat(channelId, options) {
|
|
2092
|
+
this.socket.emit("voice:heartbeat", { channelId, ...options });
|
|
2093
|
+
}
|
|
1821
2094
|
// ── Client actions ────────────────────────────────────────────────────
|
|
1822
2095
|
/** Send a message via WebSocket (text-only; for file attachments use REST) */
|
|
1823
2096
|
sendMessage(data) {
|
|
@@ -1836,12 +2109,199 @@ var ShadowSocket = class {
|
|
|
1836
2109
|
this.socket.emit("presence:activity", { channelId, activity });
|
|
1837
2110
|
}
|
|
1838
2111
|
};
|
|
2112
|
+
|
|
2113
|
+
// src/voice.ts
|
|
2114
|
+
var cachedAgoraRTC = null;
|
|
2115
|
+
async function loadAgoraRTC() {
|
|
2116
|
+
if (cachedAgoraRTC) return cachedAgoraRTC;
|
|
2117
|
+
try {
|
|
2118
|
+
const module2 = await import("agora-rtc-sdk-ng");
|
|
2119
|
+
cachedAgoraRTC = module2.default;
|
|
2120
|
+
return cachedAgoraRTC;
|
|
2121
|
+
} catch (error) {
|
|
2122
|
+
throw new Error(
|
|
2123
|
+
`Agora RTC SDK is required for ShadowVoiceConsumer. Install agora-rtc-sdk-ng in this app to use browser voice media. ${error instanceof Error ? error.message : String(error)}`
|
|
2124
|
+
);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
function createVoiceClientId() {
|
|
2128
|
+
return `shadow-sdk-${globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`}`;
|
|
2129
|
+
}
|
|
2130
|
+
var ShadowVoiceConsumer = class {
|
|
2131
|
+
constructor(options) {
|
|
2132
|
+
this.options = options;
|
|
2133
|
+
this.clientId = options.clientId ?? createVoiceClientId();
|
|
2134
|
+
}
|
|
2135
|
+
rtc = null;
|
|
2136
|
+
screenRtc = null;
|
|
2137
|
+
audioTrack = null;
|
|
2138
|
+
screenTrack = null;
|
|
2139
|
+
session = null;
|
|
2140
|
+
clientId;
|
|
2141
|
+
tokenRenewTimer = null;
|
|
2142
|
+
get joinResult() {
|
|
2143
|
+
return this.session;
|
|
2144
|
+
}
|
|
2145
|
+
async join() {
|
|
2146
|
+
const AgoraRTC = await loadAgoraRTC();
|
|
2147
|
+
this.session = await this.options.client.joinVoiceChannel(this.options.channelId, {
|
|
2148
|
+
muted: this.options.muted,
|
|
2149
|
+
clientId: this.clientId
|
|
2150
|
+
});
|
|
2151
|
+
const { credentials } = this.session;
|
|
2152
|
+
const rtc = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
|
|
2153
|
+
this.rtc = rtc;
|
|
2154
|
+
try {
|
|
2155
|
+
this.bindTokenRenewal(rtc);
|
|
2156
|
+
rtc.on("user-published", async (user, mediaType) => {
|
|
2157
|
+
await rtc.subscribe(user, mediaType);
|
|
2158
|
+
if (mediaType === "audio" && user.audioTrack) {
|
|
2159
|
+
this.options.onRemoteAudio?.({ uid: user.uid, track: user.audioTrack });
|
|
2160
|
+
}
|
|
2161
|
+
if (mediaType === "video" && user.videoTrack) {
|
|
2162
|
+
this.options.onRemoteScreen?.({ uid: user.uid, track: user.videoTrack });
|
|
2163
|
+
}
|
|
2164
|
+
});
|
|
2165
|
+
await rtc.join(
|
|
2166
|
+
credentials.appId,
|
|
2167
|
+
credentials.agoraChannelName,
|
|
2168
|
+
credentials.token,
|
|
2169
|
+
credentials.uid
|
|
2170
|
+
);
|
|
2171
|
+
this.audioTrack = await AgoraRTC.createMicrophoneAudioTrack();
|
|
2172
|
+
await this.audioTrack.setEnabled(!this.options.muted);
|
|
2173
|
+
await rtc.publish([this.audioTrack]);
|
|
2174
|
+
this.scheduleTokenRenewal();
|
|
2175
|
+
return this.session;
|
|
2176
|
+
} catch (error) {
|
|
2177
|
+
this.clearTokenRenewal();
|
|
2178
|
+
this.audioTrack?.stop();
|
|
2179
|
+
this.audioTrack?.close();
|
|
2180
|
+
this.audioTrack = null;
|
|
2181
|
+
await rtc.leave().catch(() => void 0);
|
|
2182
|
+
this.rtc = null;
|
|
2183
|
+
await this.options.client.leaveVoiceChannel(this.options.channelId, { clientId: this.clientId }).catch(() => void 0);
|
|
2184
|
+
this.session = null;
|
|
2185
|
+
throw error;
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
async setMuted(muted) {
|
|
2189
|
+
await this.audioTrack?.setEnabled(!muted);
|
|
2190
|
+
await this.options.client.updateVoiceState(this.options.channelId, {
|
|
2191
|
+
clientId: this.clientId,
|
|
2192
|
+
muted
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
2195
|
+
async startScreenShare() {
|
|
2196
|
+
if (!this.session || this.screenRtc || this.screenTrack) return;
|
|
2197
|
+
const { credentials } = this.session;
|
|
2198
|
+
const AgoraRTC = await loadAgoraRTC();
|
|
2199
|
+
const screenRtc = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
|
|
2200
|
+
this.screenRtc = screenRtc;
|
|
2201
|
+
try {
|
|
2202
|
+
this.bindTokenRenewal(screenRtc);
|
|
2203
|
+
await screenRtc.join(
|
|
2204
|
+
credentials.appId,
|
|
2205
|
+
credentials.agoraChannelName,
|
|
2206
|
+
credentials.screenToken,
|
|
2207
|
+
credentials.screenUid
|
|
2208
|
+
);
|
|
2209
|
+
const trackResult = await AgoraRTC.createScreenVideoTrack(
|
|
2210
|
+
{ encoderConfig: "1080p_1" },
|
|
2211
|
+
"disable"
|
|
2212
|
+
);
|
|
2213
|
+
const screenTrack = Array.isArray(trackResult) ? trackResult[0] : trackResult;
|
|
2214
|
+
this.screenTrack = screenTrack;
|
|
2215
|
+
await screenRtc.publish([this.screenTrack]);
|
|
2216
|
+
await this.options.client.updateVoiceState(this.options.channelId, {
|
|
2217
|
+
clientId: this.clientId,
|
|
2218
|
+
screenSharing: true
|
|
2219
|
+
});
|
|
2220
|
+
} catch (error) {
|
|
2221
|
+
this.screenTrack?.stop();
|
|
2222
|
+
this.screenTrack?.close();
|
|
2223
|
+
this.screenTrack = null;
|
|
2224
|
+
await screenRtc.leave().catch(() => void 0);
|
|
2225
|
+
this.screenRtc = null;
|
|
2226
|
+
await this.options.client.updateVoiceState(this.options.channelId, {
|
|
2227
|
+
clientId: this.clientId,
|
|
2228
|
+
screenSharing: false
|
|
2229
|
+
}).catch(() => void 0);
|
|
2230
|
+
throw error;
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
async stopScreenShare() {
|
|
2234
|
+
this.screenTrack?.stop();
|
|
2235
|
+
this.screenTrack?.close();
|
|
2236
|
+
this.screenTrack = null;
|
|
2237
|
+
await this.screenRtc?.leave();
|
|
2238
|
+
this.screenRtc = null;
|
|
2239
|
+
await this.options.client.updateVoiceState(this.options.channelId, {
|
|
2240
|
+
clientId: this.clientId,
|
|
2241
|
+
screenSharing: false
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
async leave() {
|
|
2245
|
+
this.clearTokenRenewal();
|
|
2246
|
+
await this.stopScreenShare();
|
|
2247
|
+
this.audioTrack?.stop();
|
|
2248
|
+
this.audioTrack?.close();
|
|
2249
|
+
this.audioTrack = null;
|
|
2250
|
+
await this.rtc?.leave();
|
|
2251
|
+
this.rtc = null;
|
|
2252
|
+
await this.options.client.leaveVoiceChannel(this.options.channelId, { clientId: this.clientId });
|
|
2253
|
+
this.session = null;
|
|
2254
|
+
}
|
|
2255
|
+
bindTokenRenewal(rtc) {
|
|
2256
|
+
rtc.on("token-privilege-will-expire", () => {
|
|
2257
|
+
void this.renewTokens();
|
|
2258
|
+
});
|
|
2259
|
+
rtc.on("token-privilege-did-expire", () => {
|
|
2260
|
+
void this.renewTokens();
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
scheduleTokenRenewal() {
|
|
2264
|
+
this.clearTokenRenewal();
|
|
2265
|
+
const expiresAt = this.session?.credentials.expiresAt;
|
|
2266
|
+
if (!expiresAt) return;
|
|
2267
|
+
const renewAt = new Date(expiresAt).getTime() - 5 * 6e4;
|
|
2268
|
+
const delay = Math.max(3e4, renewAt - Date.now());
|
|
2269
|
+
this.tokenRenewTimer = setTimeout(() => {
|
|
2270
|
+
void this.renewTokens();
|
|
2271
|
+
}, delay);
|
|
2272
|
+
}
|
|
2273
|
+
clearTokenRenewal() {
|
|
2274
|
+
if (this.tokenRenewTimer) {
|
|
2275
|
+
clearTimeout(this.tokenRenewTimer);
|
|
2276
|
+
this.tokenRenewTimer = null;
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
async renewTokens() {
|
|
2280
|
+
if (!this.session) return;
|
|
2281
|
+
const result = await this.options.client.renewVoiceCredentials(this.options.channelId, {
|
|
2282
|
+
clientId: this.clientId
|
|
2283
|
+
});
|
|
2284
|
+
this.session = {
|
|
2285
|
+
...this.session,
|
|
2286
|
+
credentials: result.credentials,
|
|
2287
|
+
state: result.state
|
|
2288
|
+
};
|
|
2289
|
+
if (result.credentials.token) {
|
|
2290
|
+
await this.rtc?.renewToken?.(result.credentials.token);
|
|
2291
|
+
}
|
|
2292
|
+
if (result.credentials.screenToken) {
|
|
2293
|
+
await this.screenRtc?.renewToken?.(result.credentials.screenToken);
|
|
2294
|
+
}
|
|
2295
|
+
this.scheduleTokenRenewal();
|
|
2296
|
+
}
|
|
2297
|
+
};
|
|
1839
2298
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1840
2299
|
0 && (module.exports = {
|
|
1841
2300
|
CLIENT_EVENTS,
|
|
1842
2301
|
SERVER_EVENTS,
|
|
1843
2302
|
ShadowClient,
|
|
1844
2303
|
ShadowSocket,
|
|
2304
|
+
ShadowVoiceConsumer,
|
|
1845
2305
|
channelRoom,
|
|
1846
2306
|
threadRoom,
|
|
1847
2307
|
userRoom
|