@openclaw/discord 2026.10.1-beta.2 → 2026.10.2

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.
Files changed (72) hide show
  1. package/dist/.setup/{account-inspect-CjKqkDnH.mjs → account-inspect-DsER5ILV.mjs} +2 -2
  2. package/dist/.setup/{channel-type-D1_b5UhU.mjs → allow-list-vxkG9_JD.mjs} +6 -6
  3. package/dist/.setup/{api-_oznjffB.mjs → api-DlNUkDqj.mjs} +3 -3
  4. package/dist/.setup/{approval-handler.runtime-BilYSrRF.mjs → approval-handler.runtime-DmnEa54W.mjs} +8 -8
  5. package/dist/.setup/{approval-message-updates-CgRwc2_Q.mjs → approval-message-updates-B0jdSqOj.mjs} +2 -2
  6. package/dist/.setup/{audit-DtvzxONM.mjs → audit-BpikUwN_.mjs} +4 -4
  7. package/dist/.setup/{channel-z5lQMS_9.mjs → channel-Dr-AUnGt.mjs} +23 -23
  8. package/dist/.setup/{channel.setup-DBJtugJO.mjs → channel.setup-BmNvkidb.mjs} +5 -5
  9. package/dist/.setup/{components-CzXLR3QH.mjs → components-DLJR0xU-.mjs} +2 -2
  10. package/dist/.setup/{conversation-binding-route-Bi62ApSt.mjs → conversation-binding-route-DicJC1Lj.mjs} +1 -1
  11. package/dist/.setup/{directory-config-BH1LA-gi.mjs → directory-config-gqsdcplU.mjs} +1 -1
  12. package/dist/.setup/{directory-live-C9xz7IPm.mjs → directory-live-Bipr0eUJ.mjs} +3 -3
  13. package/dist/.setup/{discord-J7aRzq2Q.mjs → discord-9gysrhzp.mjs} +10 -10
  14. package/dist/.setup/{doctor-DxSAngJq.mjs → doctor-B2bskDcF.mjs} +4 -4
  15. package/dist/.setup/{handle-action-DlmFX-_G.mjs → handle-action-UmkjXBfo.mjs} +8 -8
  16. package/dist/.setup/{handle-action.guild-admin-BnyqTczR.mjs → handle-action.guild-admin-CB4sc9VV.mjs} +2 -2
  17. package/dist/.setup/{live-policy-config-emmPNyh4.mjs → live-policy-config-VxexfRKo.mjs} +1 -1
  18. package/dist/.setup/{message-handler-BNToPZrx.mjs → message-handler-BK69A-NB.mjs} +13 -13
  19. package/dist/.setup/{message-handler.preflight-D0BcCyI7.mjs → message-handler.preflight-DFpsh90H.mjs} +17 -17
  20. package/dist/.setup/{message-handler.preflight-pluralkit-D1g3eq0B.mjs → message-handler.preflight-pluralkit-BXGGVmpe.mjs} +7 -7
  21. package/dist/.setup/{message-handler.process-CThRmCWK.mjs → message-handler.process-TKoqcqIP.mjs} +21 -21
  22. package/dist/.setup/{outbound-adapter-BPq0c2wY.mjs → outbound-adapter-D4wRAb5S.mjs} +9 -9
  23. package/dist/.setup/{outbound-session-route-DGPPKA0z.mjs → outbound-session-route-BvBH4LC6.mjs} +13 -13
  24. package/dist/.setup/{probe-DIRY8vh7.mjs → probe-BwSKpnZf.mjs} +4 -4
  25. package/dist/.setup/{provider-D2OKMo0f.mjs → provider-YP8gN9_o.mjs} +74 -74
  26. package/dist/.setup/{provider-session.runtime-BupCTZQv.mjs → provider-session.runtime-D1gkPxHT.mjs} +3 -3
  27. package/dist/.setup/provider.runtime-CZ51OIgD.mjs +2 -0
  28. package/dist/.setup/{realtime-session.runtime-D5RSC8Mm.mjs → realtime-session.runtime-BKUQBy1u.mjs} +19 -19
  29. package/dist/.setup/{resolve-allowlist-common-DUYRrrVB.mjs → resolve-allowlist-common-BUyU3C-T.mjs} +3 -3
  30. package/dist/.setup/{resolve-channels--MUatisx.mjs → resolve-channels-DA6QlhZN.mjs} +3 -3
  31. package/dist/.setup/{resolve-users-CxLcDmM0.mjs → resolve-users-DNrwvunm.mjs} +2 -2
  32. package/dist/.setup/{runtime-CwUA0RLr.mjs → runtime-CESTexGn.mjs} +12 -12
  33. package/dist/.setup/{runtime-CerRBfaa.mjs → runtime-DPcmPST9.mjs} +2 -2
  34. package/dist/.setup/{send-2X7m2Zmw.mjs → send-CJk3VP5q.mjs} +16 -16
  35. package/dist/.setup/{send.components-RqvgmCs3.mjs → send.components-dPljuV24.mjs} +7 -7
  36. package/dist/.setup/{send.outbound-B4QHSEzm.mjs → send.outbound-rOboMO_6.mjs} +8 -8
  37. package/dist/.setup/{retry-BKzO25k4.mjs → send.receipt-DVOg2Oaw.mjs} +203 -203
  38. package/dist/.setup/{send.shared-BQIUh6Vn.mjs → send.shared-C4CN24D9.mjs} +40 -40
  39. package/dist/.setup/{sender-identity-CooeYfAn.mjs → sender-identity-AHpUNr72.mjs} +1 -1
  40. package/dist/.setup/setup-plugin-api-BVlCz3fm.mjs +3 -0
  41. package/dist/.setup/{shared-CIJn6AEC.mjs → shared-BoBnM5kM.mjs} +7 -7
  42. package/dist/.setup/{subagent-hooks-BYlVnxzy.mjs → subagent-hooks-HH8Uy9cx.mjs} +2 -2
  43. package/dist/.setup/{system-events-CQ3if_pS.mjs → system-events-Cwxaww8i.mjs} +2 -2
  44. package/dist/.setup/{target-resolver-Dzxf80Y3.mjs → target-resolver-CSFoPQ12.mjs} +3 -3
  45. package/dist/.setup/targets-bqXQENmX.mjs +3 -0
  46. package/dist/.setup/{thread-bindings-yNNlcUWE.mjs → thread-bindings-DjQCkmsd.mjs} +4 -4
  47. package/dist/.setup/{thread-bindings.manager-plk0zJAW.mjs → thread-bindings.manager-D2-GYZ5s.mjs} +10 -10
  48. package/dist/.setup/{transcripts-source-CNWswry-.mjs → transcripts-source-CaZQnGRH.mjs} +18 -18
  49. package/dist/.setup/{voice-runtime-Bc75quiH.mjs → voice-runtime-DDymtIw8.mjs} +46 -46
  50. package/dist/account-inspect-api.js +1 -1
  51. package/dist/action-runtime-api.js +1 -1
  52. package/dist/activities-api.js +788 -1
  53. package/dist/api.js +15 -15
  54. package/dist/channel-config-api.js +1 -1
  55. package/dist/channel-plugin-api.js +1 -1
  56. package/dist/directory-contract-api.js +1 -1
  57. package/dist/index.js +9 -5
  58. package/dist/runtime-api.js +20 -20
  59. package/dist/setup-entry.js +1 -1
  60. package/dist/setup-plugin-api.js +1 -1
  61. package/dist/src/voice/audio-worker.runtime.js +3 -3
  62. package/dist/subagent-hooks-api.js +1 -1
  63. package/dist/transcripts-source-api.js +1 -1
  64. package/package.json +4 -4
  65. package/dist/.setup/activities-api-BL_hJ0JQ.mjs +0 -789
  66. package/dist/.setup/provider.runtime-BaW_JnfU.mjs +0 -2
  67. package/dist/.setup/setup-plugin-api-nn_giXvZ.mjs +0 -3
  68. package/dist/.setup/targets-Lf8D55w-.mjs +0 -3
  69. package/dist/.setup/{accounts-DXHCL8Zm.mjs → accounts-HE9-mj1L.mjs} +2 -2
  70. package/dist/.setup/{config-schema-CsemBDpS.mjs → config-schema-BQWfnSCx.mjs} +1 -1
  71. package/dist/.setup/{receive-recovery-GqF2PUN_.mjs → receive-recovery-DgkX7Qvi.mjs} +2 -2
  72. package/dist/.setup/{thread-bindings.session-updates-C9aKtVs6.mjs → thread-bindings.session-updates-Bu_CGr_O.mjs} +2 -2
@@ -1,789 +0,0 @@
1
- import { S as parseDiscordActivityCustomId, i as buildDiscordPresentationComponents } from "./components-CzXLR3QH.mjs";
2
- import { O as getDiscordEndpointRuntime } from "./channel-type-D1_b5UhU.mjs";
3
- import { c as resolveDiscordAccount } from "./accounts-DXHCL8Zm.mjs";
4
- import { r as sendDiscordComponentMessage } from "./send.components-RqvgmCs3.mjs";
5
- import { x as resolveDiscordChannelId$1 } from "./retry-BKzO25k4.mjs";
6
- import { r as setDiscordActivitiesRuntime, t as DiscordActivitiesRuntime } from "./runtime-CerRBfaa.mjs";
7
- import fs from "node:fs/promises";
8
- import { logError } from "openclaw/plugin-sdk/logging-core";
9
- import { asOptionalRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
10
- import { resolveRequestClientIp } from "openclaw/plugin-sdk/webhook-ingress";
11
- import { WEBHOOK_BODY_READ_DEFAULTS, readJsonBodyWithLimit, sendHttpRequestRejection } from "openclaw/plugin-sdk/webhook-request-guards";
12
- import { WIDGET_CDN_ORIGINS } from "openclaw/plugin-sdk/widget-html";
13
- import { randomBytes } from "node:crypto";
14
- import { fetchWithSsrFGuard as fetchWithSsrFGuard$1 } from "openclaw/plugin-sdk/ssrf-runtime";
15
- import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
16
- //#region extensions/discord/src/activities/discord-api.ts
17
- const DISCORD_TOKEN_URL = "https://discord.com/api/oauth2/token";
18
- const DISCORD_USER_URL = "https://discord.com/api/v10/users/@me";
19
- const DISCORD_HOST = "discord.com";
20
- const DISCORD_ACTIVITY_REQUEST_TIMEOUT_MS = 15e3;
21
- const JSON_MAX_BYTES = 65536;
22
- const INSTANCE_ID_MAX_LENGTH = 256;
23
- function normalizeInstanceId(value) {
24
- const instanceId = value?.trim();
25
- if (!instanceId || instanceId.length > INSTANCE_ID_MAX_LENGTH) return;
26
- for (let index = 0; index < instanceId.length; index += 1) {
27
- const codePoint = instanceId.charCodeAt(index);
28
- if (codePoint < 32 || codePoint === 127) return;
29
- }
30
- return instanceId;
31
- }
32
- async function fetchDiscordJson(params) {
33
- const endpoint = Object.hasOwn(params, "endpointRuntime") ? params.endpointRuntime : getDiscordEndpointRuntime();
34
- const liveUrl = new URL(params.url);
35
- const endpointPath = liveUrl.pathname === "/api/oauth2/token" ? "/oauth2/token" : liveUrl.pathname.startsWith("/api/v10/") ? liveUrl.pathname.slice(8) : void 0;
36
- if (endpoint && !endpointPath) throw new Error("Discord Activity request is outside the configured endpoint routes");
37
- const { response, release } = endpoint ? {
38
- response: await endpoint.fetch(`${endpoint.descriptor.restApiBaseUrl}${endpointPath}${liveUrl.search}`, {
39
- ...params.init,
40
- signal: params.init.signal ? AbortSignal.any([params.init.signal, AbortSignal.timeout(DISCORD_ACTIVITY_REQUEST_TIMEOUT_MS)]) : AbortSignal.timeout(DISCORD_ACTIVITY_REQUEST_TIMEOUT_MS)
41
- }),
42
- release: async () => {}
43
- } : await params.fetchGuard({
44
- url: params.url,
45
- fetchImpl: params.fetchImpl,
46
- init: params.init,
47
- policy: { allowedHostnames: [DISCORD_HOST] },
48
- auditContext: params.auditContext,
49
- timeoutMs: DISCORD_ACTIVITY_REQUEST_TIMEOUT_MS
50
- });
51
- try {
52
- if (!response.ok) {
53
- await response.body?.cancel().catch(() => void 0);
54
- return {
55
- ok: false,
56
- status: response.status
57
- };
58
- }
59
- return {
60
- ok: true,
61
- status: response.status,
62
- body: await readProviderJsonResponse(response, "Discord Activity OAuth", { maxBytes: JSON_MAX_BYTES })
63
- };
64
- } finally {
65
- await release();
66
- }
67
- }
68
- async function resolveActivityInstanceChannel(params) {
69
- const endpointRuntime = getDiscordEndpointRuntime() ?? null;
70
- let result;
71
- try {
72
- result = await fetchDiscordJson({
73
- fetchGuard: params.fetchGuard,
74
- fetchImpl: params.proxyFetch,
75
- url: `https://discord.com/api/v10/applications/${encodeURIComponent(params.applicationId)}/activity-instances/${encodeURIComponent(params.instanceId)}`,
76
- init: { headers: { Authorization: `Bot ${params.botAuth}` } },
77
- auditContext: "discord.activities.instance",
78
- endpointRuntime
79
- });
80
- } catch {
81
- return;
82
- }
83
- if (!result.ok || !Array.isArray(result.body?.users) || !result.body.users.includes(params.discordUserId) || !result.body.location || typeof result.body.location !== "object") return;
84
- const channelId = result.body.location.channel_id;
85
- return typeof channelId === "string" && /^\d+$/.test(channelId) ? channelId : void 0;
86
- }
87
- //#endregion
88
- //#region extensions/discord/src/activities/rate-limit.ts
89
- const TOKEN_RATE_LIMIT = 10;
90
- const TOKEN_GLOBAL_RATE_LIMIT = 60;
91
- const TOKEN_RATE_WINDOW_MS = 6e4;
92
- const TOKEN_RATE_MAX_KEYS = 1024;
93
- var TokenRateLimiter = class {
94
- constructor(now) {
95
- this.now = now;
96
- this.attempts = /* @__PURE__ */ new Map();
97
- this.globalAttempts = [];
98
- }
99
- allowKey(key) {
100
- const now = this.now();
101
- const active = (this.attempts.get(key) ?? []).filter((timestamp) => timestamp > now - TOKEN_RATE_WINDOW_MS);
102
- if (active.length >= TOKEN_RATE_LIMIT) {
103
- this.attempts.delete(key);
104
- this.attempts.set(key, active);
105
- return false;
106
- }
107
- if (!this.attempts.has(key) && this.attempts.size >= TOKEN_RATE_MAX_KEYS) {
108
- const oldestKey = this.attempts.keys().next().value;
109
- if (typeof oldestKey === "string") this.attempts.delete(oldestKey);
110
- }
111
- active.push(now);
112
- this.attempts.delete(key);
113
- this.attempts.set(key, active);
114
- return true;
115
- }
116
- reserveGlobal() {
117
- const now = this.now();
118
- this.globalAttempts = this.globalAttempts.filter((reservation) => reservation.at > now - TOKEN_RATE_WINDOW_MS);
119
- if (this.globalAttempts.length >= TOKEN_GLOBAL_RATE_LIMIT) return null;
120
- const reservation = { at: now };
121
- this.globalAttempts.push(reservation);
122
- return reservation;
123
- }
124
- releaseGlobal(reservation) {
125
- const index = this.globalAttempts.indexOf(reservation);
126
- if (index >= 0) this.globalAttempts.splice(index, 1);
127
- }
128
- };
129
- //#endregion
130
- //#region extensions/discord/src/activities/shell.ts
131
- const DISCORD_ACTIVITY_ROUTE_PREFIX = "/discord/activity";
132
- const DISCORD_ACTIVITY_TOKEN_REQUEST_TIMEOUT_MS = 35e3;
133
- const DISCORD_ACTIVITY_WIDGET_REQUEST_TIMEOUT_MS = 2e4;
134
- const DISCORD_ACTIVITY_SHELL_CSP = "default-src 'none'; script-src 'self'; style-src 'unsafe-inline'; connect-src 'self'; frame-src 'self'; img-src data:; base-uri 'none'; frame-ancestors *";
135
- const DISCORD_ACTIVITY_SHELL_HTML = `<!doctype html>
136
- <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
137
- <title>OpenClaw widget</title><style>
138
- :root{color-scheme:dark;font:14px system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#1e1f22;color:#dbdee1}
139
- *{box-sizing:border-box}html,body,#app{height:100%;margin:0}#app{display:grid;place-items:center}main{max-width:560px;padding:32px;text-align:center}
140
- h1{font-size:18px;margin:0 0 8px;color:#f2f3f5}p{margin:0;color:#b5bac1;line-height:1.5}.widget{display:grid;grid-template-rows:42px 1fr;width:100%;height:100%;background:#111214}
141
- .bar{display:flex;align-items:center;padding:0 14px;border-bottom:1px solid #2b2d31;font-weight:600;color:#f2f3f5}.widget iframe{border:0;width:100%;height:100%;background:#111214}
142
- </style></head><body><div id="app"><main><h1>Opening widget</h1><p>Connecting to Discord…</p></main></div>
143
- <script type="module" src="./shell.js"><\/script></body></html>`;
144
- const DISCORD_ACTIVITY_SHELL_JS = `import { DiscordSDK } from "./vendor/embedded-app-sdk.mjs";
145
-
146
- const tokenRequestTimeoutMs = ${DISCORD_ACTIVITY_TOKEN_REQUEST_TIMEOUT_MS};
147
- const widgetRequestTimeoutMs = ${DISCORD_ACTIVITY_WIDGET_REQUEST_TIMEOUT_MS};
148
- const app = document.querySelector("#app");
149
- function show(message, detail) {
150
- app.className = "";
151
- app.innerHTML = "";
152
- const main = document.createElement("main");
153
- const heading = document.createElement("h1");
154
- const paragraph = document.createElement("p");
155
- heading.textContent = message;
156
- paragraph.textContent = detail;
157
- main.append(heading, paragraph);
158
- app.append(main);
159
- }
160
- function proxiedDocUrl(value) {
161
- const url = new URL(value, window.location.origin);
162
- if (window.location.hostname.endsWith(".discordsays.com")) {
163
- // The ROOT mapping target already includes this prefix; strip it before proxying.
164
- const gatewayPrefix = "/discord/activity";
165
- const mappedPath = url.pathname.startsWith(gatewayPrefix + "/")
166
- ? url.pathname.slice(gatewayPrefix.length)
167
- : url.pathname;
168
- return "/.proxy" + mappedPath + url.search;
169
- }
170
- return url.pathname + url.search;
171
- }
172
- async function readJson(response) {
173
- let body;
174
- try {
175
- body = await response.json();
176
- } catch (error) {
177
- // Error responses may legitimately omit JSON details; successful responses must not
178
- // turn an aborted or malformed body into an apparently valid empty payload.
179
- if (response.ok) throw error;
180
- body = {};
181
- }
182
- if (!response.ok) {
183
- const error = new Error(typeof body.error === "string" ? body.error : "request failed");
184
- error.status = response.status;
185
- throw error;
186
- }
187
- return body;
188
- }
189
- async function fetchJsonWithDeadline(input, init, timeoutMs) {
190
- // Activities also run in mobile webviews; avoid requiring the newer
191
- // AbortSignal.timeout() static API just to enforce this request boundary.
192
- const controller = new AbortController();
193
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
194
- try {
195
- return await readJson(await fetch(input, { ...init, signal: controller.signal }));
196
- } finally {
197
- clearTimeout(timeout);
198
- }
199
- }
200
- async function run() {
201
- const match = window.location.hostname.match(/^(\\d+)\\.discordsays\\.com$/i);
202
- if (!match) {
203
- show("Open inside Discord", "This widget must be launched from its Discord button.");
204
- return;
205
- }
206
- const clientId = match[1];
207
- const sdk = new DiscordSDK(clientId);
208
- await sdk.ready();
209
- const { code } = await sdk.commands.authorize({
210
- client_id: clientId,
211
- response_type: "code",
212
- state: "",
213
- prompt: "none",
214
- scope: ["identify"],
215
- });
216
- const auth = await fetchJsonWithDeadline(
217
- "./api/token",
218
- {
219
- method: "POST",
220
- headers: { "Content-Type": "application/json" },
221
- body: JSON.stringify({ code }),
222
- },
223
- tokenRequestTimeoutMs,
224
- );
225
- await sdk.commands.authenticate({ access_token: auth.access_token });
226
- const query = new URLSearchParams({
227
- custom_id: sdk.customId ?? "",
228
- instance_id: sdk.instanceId,
229
- });
230
- const widget = await fetchJsonWithDeadline(
231
- "./api/widget?" + query,
232
- { headers: { Authorization: "Bearer " + auth.session_token } },
233
- widgetRequestTimeoutMs,
234
- );
235
- app.className = "widget";
236
- app.innerHTML = "";
237
- const bar = document.createElement("div");
238
- const frame = document.createElement("iframe");
239
- bar.className = "bar";
240
- bar.textContent = widget.title;
241
- frame.title = widget.title;
242
- // Intentionally minimal: no top-navigation, popups, or same-origin access.
243
- frame.setAttribute("sandbox", "allow-scripts");
244
- frame.referrerPolicy = "no-referrer";
245
- frame.src = proxiedDocUrl(widget.docUrl);
246
- app.append(bar, frame);
247
- }
248
- run().catch((error) => {
249
- if (error?.status === 404) {
250
- show("Widget unavailable", "No widget could be resolved for this channel.");
251
- } else {
252
- show("Gateway offline", "The OpenClaw gateway could not load this widget. Try again shortly.");
253
- }
254
- });
255
- `;
256
- //#endregion
257
- //#region extensions/discord/src/activities/http.ts
258
- const BODY_MAX_BYTES = 8192;
259
- const JSON_CONTENT_TYPE = "application/json; charset=utf-8";
260
- const WIDGET_ID_PATTERN = /^[A-Za-z0-9_-]{22}$/;
261
- const DOC_TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/;
262
- const DISCORD_ACTIVITY_WIDGET_CSP = `sandbox allow-scripts; default-src 'none'; script-src 'unsafe-inline' ${WIDGET_CDN_ORIGINS.join(" ")}; style-src 'unsafe-inline' ${WIDGET_CDN_ORIGINS.join(" ")}; img-src data: blob:; font-src data: ${WIDGET_CDN_ORIGINS.join(" ")}; connect-src 'none'; frame-ancestors *`;
263
- function setCommonHeaders(res) {
264
- res.setHeader("Cache-Control", "no-store");
265
- res.setHeader("Referrer-Policy", "no-referrer");
266
- res.setHeader("X-Content-Type-Options", "nosniff");
267
- }
268
- function respond(res, statusCode, body, contentType, headers) {
269
- res.statusCode = statusCode;
270
- setCommonHeaders(res);
271
- res.setHeader("Content-Type", contentType);
272
- for (const [key, value] of Object.entries(headers ?? {})) res.setHeader(key, value);
273
- res.end(body);
274
- return true;
275
- }
276
- function jsonBody(body) {
277
- return `${JSON.stringify(body)}\n`;
278
- }
279
- function respondJson(res, statusCode, body) {
280
- return respond(res, statusCode, jsonBody(body), JSON_CONTENT_TYPE);
281
- }
282
- function notFound(res, widgetDocument = false) {
283
- return respond(res, 404, "not found", "text/plain; charset=utf-8", widgetDocument ? { "Content-Security-Policy": DISCORD_ACTIVITY_WIDGET_CSP } : void 0);
284
- }
285
- function readHeader(req, name) {
286
- const value = req.headers[name];
287
- return Array.isArray(value) ? value[0] : value;
288
- }
289
- function extractApplicationId(req) {
290
- for (const header of [readHeader(req, "origin"), readHeader(req, "referer")]) {
291
- if (!header) continue;
292
- try {
293
- const match = new URL(header).hostname.match(/^(\d+)\.discordsays\.com$/i);
294
- if (match?.[1]) return match[1];
295
- } catch {}
296
- }
297
- }
298
- function bearerToken(req) {
299
- return ((readHeader(req, "authorization")?.trim())?.match(/^Bearer\s+([A-Za-z0-9_-]{43})$/i))?.[1];
300
- }
301
- function widgetIdFromCustomId(customId) {
302
- if (WIDGET_ID_PATTERN.test(customId)) return customId;
303
- return parseDiscordActivityCustomId(customId)?.widgetId;
304
- }
305
- function createDiscordActivityHttpHandler(deps) {
306
- const fetchGuard = deps.fetchGuard ?? fetchWithSsrFGuard$1;
307
- const limiter = new TokenRateLimiter(deps.now ?? Date.now);
308
- const readVendorAsset = deps.readVendorAsset ?? ((assetPath) => fs.readFile(assetPath));
309
- const reportError = deps.logError ?? logError;
310
- const bodyTimeoutMs = deps.bodyTimeoutMs ?? WEBHOOK_BODY_READ_DEFAULTS.preAuth.timeoutMs;
311
- let vendorAsset;
312
- let pendingLaunchFailureLogged = false;
313
- function logPendingLaunchFailure(error) {
314
- if (pendingLaunchFailureLogged) return;
315
- pendingLaunchFailureLogged = true;
316
- reportError(`discord activity: failed to consume pending launch: ${String(error)}`);
317
- }
318
- async function handleToken(req, res) {
319
- const cfg = deps.runtime.currentConfig();
320
- const sourceIp = resolveRequestClientIp(req, cfg.gateway?.trustedProxies, cfg.gateway?.allowRealIpFallback === true) ?? "unknown";
321
- if (!limiter.allowKey(sourceIp)) return respondJson(res, 429, { error: "too many token requests" });
322
- const applicationId = extractApplicationId(req);
323
- const account = deps.runtime.resolveHttpAccount(applicationId);
324
- if (!account) return respondJson(res, 503, { error: "Discord Activities is not fully configured" });
325
- const endpointRuntime = getDiscordEndpointRuntime() ?? null;
326
- const bodyResult = await readJsonBodyWithLimit(req, {
327
- maxBytes: BODY_MAX_BYTES,
328
- timeoutMs: bodyTimeoutMs,
329
- emptyObjectOnEmpty: true,
330
- destroyOnLimit: false
331
- });
332
- if (!bodyResult.ok && (bodyResult.code === "REQUEST_BODY_TIMEOUT" || bodyResult.code === "PAYLOAD_TOO_LARGE")) {
333
- const timedOut = bodyResult.code === "REQUEST_BODY_TIMEOUT";
334
- await sendHttpRequestRejection(req, res, timedOut ? 408 : 413, jsonBody({ error: timedOut ? "request body timeout" : "request body too large" }), JSON_CONTENT_TYPE);
335
- return true;
336
- }
337
- const body = bodyResult.ok ? asOptionalRecord(bodyResult.value) : void 0;
338
- const code = typeof body?.code === "string" ? body.code.trim() : "";
339
- if (!code) return respondJson(res, 401, { error: "invalid authorization code" });
340
- const reservation = limiter.reserveGlobal();
341
- if (reservation === null) return respondJson(res, 429, { error: "too many token requests" });
342
- let completed = false;
343
- try {
344
- let tokenResponse;
345
- try {
346
- tokenResponse = await fetchDiscordJson({
347
- fetchGuard,
348
- fetchImpl: account.proxyFetch,
349
- url: DISCORD_TOKEN_URL,
350
- init: {
351
- method: "POST",
352
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
353
- body: new URLSearchParams({
354
- grant_type: "authorization_code",
355
- client_id: account.applicationId,
356
- client_secret: account.clientSecret,
357
- code
358
- })
359
- },
360
- auditContext: "discord.activities.oauth.token",
361
- endpointRuntime
362
- });
363
- } catch {
364
- return respondJson(res, 503, { error: "Discord token exchange unavailable" });
365
- }
366
- const granted = typeof tokenResponse.body?.access_token === "string" ? tokenResponse.body.access_token.trim() : "";
367
- if (!tokenResponse.ok || !granted) return respondJson(res, 401, { error: "invalid authorization code" });
368
- let userResponse;
369
- try {
370
- userResponse = await fetchDiscordJson({
371
- fetchGuard,
372
- fetchImpl: account.proxyFetch,
373
- url: DISCORD_USER_URL,
374
- init: { headers: { Authorization: `Bearer ${granted}` } },
375
- auditContext: "discord.activities.oauth.user",
376
- endpointRuntime
377
- });
378
- } catch {
379
- return respondJson(res, 503, { error: "Discord user lookup unavailable" });
380
- }
381
- const discordUserId = typeof userResponse.body?.id === "string" ? userResponse.body.id : void 0;
382
- if (!userResponse.ok || !discordUserId) return respondJson(res, 401, { error: "Discord user lookup failed" });
383
- const minted = await deps.runtime.store.createSession({
384
- discordUserId,
385
- accountId: account.accountId
386
- });
387
- completed = true;
388
- return respondJson(res, 200, {
389
- access_token: granted,
390
- session_token: minted
391
- });
392
- } finally {
393
- if (!completed) limiter.releaseGlobal(reservation);
394
- }
395
- }
396
- async function handleWidget(req, res, url) {
397
- const token = bearerToken(req);
398
- const session = token ? await deps.runtime.store.lookupSession(token) : void 0;
399
- if (!session) return respondJson(res, 401, { error: "invalid session" });
400
- const customId = url.searchParams.get("custom_id")?.trim() ?? "";
401
- const instanceId = normalizeInstanceId(url.searchParams.get("instance_id"));
402
- const account = deps.runtime.resolveAccount(session.accountId);
403
- const channelId = instanceId && account ? await resolveActivityInstanceChannel({
404
- fetchGuard,
405
- applicationId: account.applicationId,
406
- instanceId,
407
- discordUserId: session.discordUserId,
408
- botAuth: account.botAuth,
409
- proxyFetch: account.proxyFetch
410
- }) : void 0;
411
- if (!channelId) return respondJson(res, 404, { error: "widget not found" });
412
- let resolved = null;
413
- const requestedWidgetId = widgetIdFromCustomId(customId);
414
- if (requestedWidgetId) {
415
- const widget = await deps.runtime.store.lookupWidget(requestedWidgetId);
416
- if (widget?.accountId !== session.accountId || widget.channelId !== channelId) return respondJson(res, 404, { error: "widget not found" });
417
- resolved = {
418
- id: requestedWidgetId,
419
- widget
420
- };
421
- try {
422
- await deps.runtime.store.retirePendingLaunch(session.accountId, channelId, session.discordUserId, requestedWidgetId);
423
- } catch (error) {
424
- logPendingLaunchFailure(error);
425
- }
426
- } else {
427
- try {
428
- const pendingLaunch = await deps.runtime.store.consumePendingLaunch(session.accountId, channelId, session.discordUserId);
429
- if (pendingLaunch) {
430
- const widget = await deps.runtime.store.lookupWidget(pendingLaunch.widgetId);
431
- if (widget?.accountId === session.accountId && widget.channelId === channelId) resolved = {
432
- id: pendingLaunch.widgetId,
433
- widget
434
- };
435
- }
436
- } catch (error) {
437
- logPendingLaunchFailure(error);
438
- }
439
- resolved ??= await deps.runtime.store.latestPostedWidgetForChannel(session.accountId, channelId);
440
- }
441
- if (!resolved) return respondJson(res, 404, { error: "widget not found" });
442
- const docToken = await deps.runtime.store.createDocToken({
443
- widgetId: resolved.id,
444
- accountId: session.accountId
445
- });
446
- return respondJson(res, 200, {
447
- id: resolved.id,
448
- title: resolved.widget.title,
449
- docUrl: `${DISCORD_ACTIVITY_ROUTE_PREFIX}/api/widget/${encodeURIComponent(resolved.id)}/doc?wt=${encodeURIComponent(docToken)}`
450
- });
451
- }
452
- async function handleDocument(res, widgetId, token) {
453
- if (!WIDGET_ID_PATTERN.test(widgetId) || !DOC_TOKEN_PATTERN.test(token)) return notFound(res, true);
454
- const capability = await deps.runtime.store.consumeDocToken(token);
455
- if (!capability || capability.widgetId !== widgetId) return notFound(res, true);
456
- const widget = await deps.runtime.store.lookupWidget(widgetId);
457
- if (!widget || widget.accountId !== capability.accountId) return notFound(res, true);
458
- return respond(res, 200, widget.html, "text/html; charset=utf-8", { "Content-Security-Policy": DISCORD_ACTIVITY_WIDGET_CSP });
459
- }
460
- return { async handleHttpRequest(req, res) {
461
- const url = new URL(req.url ?? "/", "http://localhost");
462
- if (url.pathname !== "/discord/activity" && !url.pathname.startsWith(`/discord/activity/`)) return false;
463
- if (!deps.runtime.hasEnabledAccounts()) return false;
464
- const relative = url.pathname.slice(17) || "/";
465
- if (req.method === "GET" && (relative === "/" || relative === "/index.html")) return respond(res, 200, DISCORD_ACTIVITY_SHELL_HTML, "text/html; charset=utf-8", { "Content-Security-Policy": DISCORD_ACTIVITY_SHELL_CSP });
466
- if (req.method === "GET" && relative === "/shell.js") return respond(res, 200, DISCORD_ACTIVITY_SHELL_JS, "text/javascript; charset=utf-8");
467
- if (req.method === "GET" && relative === "/vendor/embedded-app-sdk.mjs") {
468
- const pendingAsset = vendorAsset ??= readVendorAsset(deps.vendorAssetPath);
469
- try {
470
- return respond(res, 200, await pendingAsset, "text/javascript; charset=utf-8");
471
- } catch {
472
- if (vendorAsset === pendingAsset) vendorAsset = void 0;
473
- return notFound(res);
474
- }
475
- }
476
- if (req.method === "POST" && relative === "/api/token") return await handleToken(req, res);
477
- if (req.method === "GET" && relative === "/api/widget") return await handleWidget(req, res, url);
478
- const documentMatch = relative.match(/^\/api\/widget\/([^/]+)\/doc$/);
479
- if (req.method === "GET" && documentMatch?.[1]) {
480
- let widgetId;
481
- try {
482
- widgetId = decodeURIComponent(documentMatch[1]);
483
- } catch {
484
- return notFound(res, true);
485
- }
486
- return await handleDocument(res, widgetId, url.searchParams.get("wt") ?? "");
487
- }
488
- return notFound(res);
489
- } };
490
- }
491
- //#endregion
492
- //#region extensions/discord/src/activities/presenter.ts
493
- const DISCORD_WIDGET_HTML_MAX_BYTES = 49152;
494
- function resolveDiscordChannelId(context) {
495
- const raw = context.nativeChannelId?.trim() || context.currentMessagingTarget?.trim() || context.currentChannelId?.trim() || context.deliveryContext?.to?.trim();
496
- if (!raw) return;
497
- try {
498
- return resolveDiscordChannelId$1(raw);
499
- } catch {
500
- return;
501
- }
502
- }
503
- function resolveDiscordPresentationRoute(context, runtime) {
504
- if (context.messageChannel !== "discord") return;
505
- const cfg = runtime.currentConfig();
506
- const account = resolveDiscordAccount({
507
- cfg,
508
- accountId: context.accountId ?? context.deliveryContext?.accountId
509
- });
510
- const channelId = resolveDiscordChannelId(context);
511
- const activityAccount = runtime.resolveAccount(account.accountId, cfg);
512
- if (!channelId || !activityAccount) return;
513
- return {
514
- account: activityAccount,
515
- cfg,
516
- channelId
517
- };
518
- }
519
- /** Presents a canonical core widget document in the active Discord channel. */
520
- function createDiscordWidgetPresenter(runtime) {
521
- return {
522
- target: "current_channel",
523
- description: "Post an Activity launch button in the current Discord channel",
524
- capabilities: {
525
- sourceKinds: ["html"],
526
- maxSourceBytes: DISCORD_WIDGET_HTML_MAX_BYTES
527
- },
528
- match: (context) => resolveDiscordPresentationRoute(context, runtime) !== void 0,
529
- async availability(context) {
530
- return resolveDiscordPresentationRoute(context, runtime) ? {
531
- ok: true,
532
- value: { available: true }
533
- } : {
534
- ok: false,
535
- error: {
536
- code: "unavailable",
537
- message: "Discord Activities are unavailable for the current channel and account."
538
- }
539
- };
540
- },
541
- async present({ context, document, title }) {
542
- const route = resolveDiscordPresentationRoute(context, runtime);
543
- if (!route) return {
544
- ok: false,
545
- error: {
546
- code: "unavailable",
547
- message: "Discord Activities are unavailable for the current channel and account."
548
- }
549
- };
550
- if (Array.from(title).length > 80) return {
551
- ok: false,
552
- error: {
553
- code: "presentation_error",
554
- message: "title must be 80 characters or fewer"
555
- }
556
- };
557
- const widgetId = await runtime.store.createWidget({
558
- html: document.html,
559
- title,
560
- channelId: route.channelId,
561
- accountId: route.account.accountId,
562
- createdAt: Date.now()
563
- });
564
- let result;
565
- let deliveredResult;
566
- let deliveryRecord;
567
- let deliveryRecordError;
568
- const recordDelivery = async (deliveryResult) => {
569
- deliveredResult = deliveryResult;
570
- deliveryRecord ??= runtime.store.markWidgetDelivered(widgetId, deliveryResult.messageId);
571
- try {
572
- await deliveryRecord;
573
- } catch (error) {
574
- deliveryRecordError ??= new Error("Discord widget was delivered, but its delivery state could not be saved", { cause: error });
575
- throw deliveryRecordError;
576
- }
577
- };
578
- try {
579
- const components = buildDiscordPresentationComponents({ blocks: [{
580
- type: "buttons",
581
- buttons: [{
582
- label: "Open widget",
583
- action: {
584
- type: "web-app",
585
- widgetId
586
- }
587
- }]
588
- }] });
589
- if (!components) throw new Error("Discord widget launch button could not be rendered");
590
- result = await sendDiscordComponentMessage(`channel:${route.channelId}`, {
591
- ...components,
592
- text: title
593
- }, {
594
- cfg: route.cfg,
595
- accountId: route.account.accountId,
596
- allowedMentions: { parse: [] },
597
- onDeliveryResult: recordDelivery
598
- });
599
- await recordDelivery(result);
600
- } catch (error) {
601
- if (deliveryRecordError) throw deliveryRecordError;
602
- if (!deliveredResult) {
603
- await runtime.store.deleteWidget(widgetId);
604
- throw error;
605
- }
606
- result = deliveredResult;
607
- }
608
- return {
609
- ok: true,
610
- value: {
611
- kind: "message",
612
- receipt: result.receipt
613
- }
614
- };
615
- }
616
- };
617
- }
618
- //#endregion
619
- //#region extensions/discord/src/activities/store.ts
620
- const DAY_MS = 864e5;
621
- const DISCORD_EPOCH_MS = 14200704e5;
622
- const WIDGET_TTL_MS = 7 * DAY_MS;
623
- const SESSION_TTL_MS = 9e5;
624
- const DOC_TOKEN_TTL_MS = 6e4;
625
- const PENDING_LAUNCH_TTL_MS = 12e4;
626
- function requireAtomicComparison(store) {
627
- if (!store.observe || !store.compareAndApply) throw new Error("Discord Activities require atomic plugin state comparisons");
628
- return store;
629
- }
630
- function openDiscordActivityStores(openKeyedStore) {
631
- return {
632
- widgets: requireAtomicComparison(openKeyedStore({
633
- namespace: "activities-widgets",
634
- maxEntries: 64,
635
- overflowPolicy: "evict-oldest",
636
- defaultTtlMs: WIDGET_TTL_MS
637
- })),
638
- sessions: openKeyedStore({
639
- namespace: "activities-sessions",
640
- maxEntries: 256,
641
- overflowPolicy: "evict-oldest",
642
- defaultTtlMs: SESSION_TTL_MS
643
- }),
644
- docTokens: openKeyedStore({
645
- namespace: "activities-doc-tokens",
646
- maxEntries: 256,
647
- overflowPolicy: "evict-oldest",
648
- defaultTtlMs: DOC_TOKEN_TTL_MS
649
- }),
650
- launches: requireAtomicComparison(openKeyedStore({
651
- namespace: "activities-launches",
652
- maxEntries: 256,
653
- overflowPolicy: "evict-oldest",
654
- defaultTtlMs: PENDING_LAUNCH_TTL_MS
655
- }))
656
- };
657
- }
658
- function pendingLaunchKey(accountId, channelId, discordUserId) {
659
- return `${accountId}:${channelId}:${discordUserId}`;
660
- }
661
- async function applyActivityStoreIntent(store, key, intent) {
662
- let observed = await store.observe(key);
663
- while (true) {
664
- const result = await store.compareAndApply(key, observed.comparison, intent(observed.value));
665
- if (result.status !== "conflict") return result.status;
666
- observed = result.current;
667
- }
668
- }
669
- function pendingLaunchForWidget(existing, widgetId, createdAt) {
670
- return existing && (existing.state === "ambiguous" || existing.widgetId !== widgetId) ? {
671
- state: "ambiguous",
672
- createdAt
673
- } : {
674
- state: "single",
675
- widgetId,
676
- createdAt
677
- };
678
- }
679
- var DiscordActivityStore = class {
680
- constructor(stores) {
681
- this.stores = stores;
682
- this.lastWidgetCreatedAt = 0;
683
- }
684
- async createWidget(value) {
685
- const id = randomBytes(16).toString("base64url");
686
- const createdAt = Math.max(value.createdAt, this.lastWidgetCreatedAt + 1);
687
- this.lastWidgetCreatedAt = createdAt;
688
- await this.stores.widgets.register(id, {
689
- ...value,
690
- createdAt,
691
- deliveredMessageId: null
692
- });
693
- return id;
694
- }
695
- async markWidgetDelivered(id, messageId) {
696
- if (!/^\d+$/u.test(messageId)) throw new Error("Discord Activity delivery returned an invalid message ID");
697
- if (await applyActivityStoreIntent(this.stores.widgets, id, (widget) => widget ? {
698
- operation: "update",
699
- action: "set",
700
- value: {
701
- ...widget,
702
- deliveredMessageId: messageId
703
- }
704
- } : {
705
- operation: "update",
706
- action: "keep"
707
- }) === "unchanged") throw new Error("Discord Activity widget disappeared before delivery was recorded");
708
- }
709
- async deleteWidget(id) {
710
- await this.stores.widgets.delete(id);
711
- }
712
- async lookupWidget(id) {
713
- return await this.stores.widgets.lookup(id);
714
- }
715
- async latestPostedWidgetForChannel(accountId, channelId) {
716
- const entries = await this.stores.widgets.entries();
717
- let match;
718
- for (const entry of entries) {
719
- if (entry.value.accountId !== accountId || entry.value.channelId !== channelId) continue;
720
- if (entry.value.deliveredMessageId === null) continue;
721
- const deliveryOrder = entry.value.deliveredMessageId ? BigInt(entry.value.deliveredMessageId) : BigInt(Math.max(0, Math.trunc(entry.value.createdAt - DISCORD_EPOCH_MS))) << 22n;
722
- if (!match || deliveryOrder > match.deliveryOrder) match = {
723
- entry,
724
- deliveryOrder
725
- };
726
- }
727
- return match ? {
728
- id: match.entry.key,
729
- widget: match.entry.value
730
- } : null;
731
- }
732
- async createSession(value) {
733
- const token = randomBytes(32).toString("base64url");
734
- await this.stores.sessions.register(token, value);
735
- return token;
736
- }
737
- async lookupSession(token) {
738
- return await this.stores.sessions.lookup(token);
739
- }
740
- async createDocToken(value) {
741
- const token = randomBytes(32).toString("base64url");
742
- await this.stores.docTokens.register(token, value);
743
- return token;
744
- }
745
- async consumeDocToken(token) {
746
- return await this.stores.docTokens.consume(token);
747
- }
748
- async recordPendingLaunch(params) {
749
- const key = pendingLaunchKey(params.accountId, params.channelId, params.discordUserId);
750
- const { widgetId, createdAt } = params;
751
- await applyActivityStoreIntent(this.stores.launches, key, (existing) => ({
752
- operation: "update",
753
- action: "set",
754
- value: pendingLaunchForWidget(existing, widgetId, createdAt)
755
- }));
756
- }
757
- async retirePendingLaunch(accountId, channelId, discordUserId, widgetId) {
758
- const key = pendingLaunchKey(accountId, channelId, discordUserId);
759
- await applyActivityStoreIntent(this.stores.launches, key, (existing) => ({
760
- operation: "delete",
761
- action: existing?.state === "single" && existing.widgetId === widgetId ? "delete" : "keep"
762
- }));
763
- }
764
- async consumePendingLaunch(accountId, channelId, discordUserId) {
765
- const launch = await this.stores.launches.consume(pendingLaunchKey(accountId, channelId, discordUserId));
766
- return launch?.state === "single" ? launch : void 0;
767
- }
768
- };
769
- //#endregion
770
- //#region extensions/discord/src/activities/register.ts
771
- function registerDiscordActivities(api) {
772
- setDiscordActivitiesRuntime(void 0);
773
- const store = new DiscordActivityStore(openDiscordActivityStores(api.runtime.state.openKeyedStore.bind(api.runtime.state)));
774
- const runtime = new DiscordActivitiesRuntime(store, api.config, api.runtime.config?.current ? () => api.runtime.config.current() : void 0);
775
- setDiscordActivitiesRuntime(runtime);
776
- const http = createDiscordActivityHttpHandler({
777
- runtime,
778
- vendorAssetPath: api.resolvePath("assets/embedded-app-sdk.mjs")
779
- });
780
- api.registerHttpRoute({
781
- path: DISCORD_ACTIVITY_ROUTE_PREFIX,
782
- auth: "plugin",
783
- match: "prefix",
784
- handler: async (req, res) => await http.handleHttpRequest(req, res)
785
- });
786
- api.registerWidgetPresenter(createDiscordWidgetPresenter(runtime));
787
- }
788
- //#endregion
789
- export { registerDiscordActivities as t };