shardwire 1.4.0 → 1.4.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/README.md +20 -2
- package/dist/index.d.mts +108 -2
- package/dist/index.d.ts +108 -2
- package/dist/index.js +283 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +278 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,7 +22,11 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
BridgeCapabilityError: () => BridgeCapabilityError,
|
|
24
24
|
connectBotBridge: () => connectBotBridge,
|
|
25
|
-
createBotBridge: () => createBotBridge
|
|
25
|
+
createBotBridge: () => createBotBridge,
|
|
26
|
+
createThreadThenSendMessage: () => createThreadThenSendMessage,
|
|
27
|
+
deferThenEditInteractionReply: () => deferThenEditInteractionReply,
|
|
28
|
+
deferUpdateThenEditInteractionReply: () => deferUpdateThenEditInteractionReply,
|
|
29
|
+
getShardwireCatalog: () => getShardwireCatalog
|
|
26
30
|
});
|
|
27
31
|
module.exports = __toCommonJS(index_exports);
|
|
28
32
|
|
|
@@ -104,6 +108,17 @@ var EVENT_REQUIRED_INTENTS = {
|
|
|
104
108
|
channelUpdate: ["Guilds"],
|
|
105
109
|
channelDelete: ["Guilds"]
|
|
106
110
|
};
|
|
111
|
+
var SUBSCRIPTION_FILTER_KEYS = [
|
|
112
|
+
"guildId",
|
|
113
|
+
"channelId",
|
|
114
|
+
"userId",
|
|
115
|
+
"commandName",
|
|
116
|
+
"customId",
|
|
117
|
+
"interactionKind",
|
|
118
|
+
"channelType",
|
|
119
|
+
"parentChannelId",
|
|
120
|
+
"threadId"
|
|
121
|
+
];
|
|
107
122
|
function getAvailableEvents(intents) {
|
|
108
123
|
const enabled = new Set(intents);
|
|
109
124
|
return BOT_EVENT_NAMES.filter(
|
|
@@ -2311,14 +2326,16 @@ function createBotBridgeWithRuntime(options, runtime) {
|
|
|
2311
2326
|
|
|
2312
2327
|
// src/discord/types.ts
|
|
2313
2328
|
var BridgeCapabilityError = class extends Error {
|
|
2314
|
-
constructor(kind, name, message) {
|
|
2329
|
+
constructor(kind, name, message, details) {
|
|
2315
2330
|
super(message ?? `Capability "${name}" is not available for ${kind}.`);
|
|
2316
2331
|
this.kind = kind;
|
|
2317
2332
|
this.name = name;
|
|
2333
|
+
this.details = details;
|
|
2318
2334
|
this.name = "BridgeCapabilityError";
|
|
2319
2335
|
}
|
|
2320
2336
|
kind;
|
|
2321
2337
|
name;
|
|
2338
|
+
details;
|
|
2322
2339
|
};
|
|
2323
2340
|
|
|
2324
2341
|
// src/bridge/transport/socket.ts
|
|
@@ -2327,6 +2344,172 @@ function createNodeWebSocket(url) {
|
|
|
2327
2344
|
return new import_ws2.WebSocket(url);
|
|
2328
2345
|
}
|
|
2329
2346
|
|
|
2347
|
+
// src/dx/explain-capability.ts
|
|
2348
|
+
var DENIED_REMEDIATION = "Add the required gateway intents on `createBotBridge({ intents })` and/or widen `server.secrets[].allow.events` or `allow.actions` for this secret.";
|
|
2349
|
+
function isBotEventName(name) {
|
|
2350
|
+
return BOT_EVENT_NAMES.includes(name);
|
|
2351
|
+
}
|
|
2352
|
+
function isBotActionName(name) {
|
|
2353
|
+
return BOT_ACTION_NAMES.includes(name);
|
|
2354
|
+
}
|
|
2355
|
+
function buildBridgeCapabilityErrorDetails(kind, name) {
|
|
2356
|
+
const base = {
|
|
2357
|
+
reasonCode: "not_in_capabilities",
|
|
2358
|
+
kind,
|
|
2359
|
+
name,
|
|
2360
|
+
remediation: DENIED_REMEDIATION
|
|
2361
|
+
};
|
|
2362
|
+
if (kind === "event" && isBotEventName(name)) {
|
|
2363
|
+
return { ...base, requiredIntents: EVENT_REQUIRED_INTENTS[name] };
|
|
2364
|
+
}
|
|
2365
|
+
return base;
|
|
2366
|
+
}
|
|
2367
|
+
function explainCapability(connected, capabilities, query) {
|
|
2368
|
+
const { kind, name } = query;
|
|
2369
|
+
const known = kind === "event" ? isBotEventName(name) : isBotActionName(name);
|
|
2370
|
+
if (!known) {
|
|
2371
|
+
return {
|
|
2372
|
+
kind,
|
|
2373
|
+
name,
|
|
2374
|
+
known: false,
|
|
2375
|
+
reasonCode: "unknown_name",
|
|
2376
|
+
remediation: "Use a built-in event or action name from `app.catalog()`."
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
if (!connected || !capabilities) {
|
|
2380
|
+
const base = {
|
|
2381
|
+
kind,
|
|
2382
|
+
name,
|
|
2383
|
+
known: true,
|
|
2384
|
+
reasonCode: "not_negotiated",
|
|
2385
|
+
remediation: "Call `await app.ready()` (or `await app.preflight()`) after connecting to see negotiated bridge access."
|
|
2386
|
+
};
|
|
2387
|
+
if (kind === "event") {
|
|
2388
|
+
return {
|
|
2389
|
+
...base,
|
|
2390
|
+
requiredIntents: EVENT_REQUIRED_INTENTS[name]
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
return base;
|
|
2394
|
+
}
|
|
2395
|
+
if (kind === "event") {
|
|
2396
|
+
const ev = name;
|
|
2397
|
+
const allowedByBridge2 = capabilities.events.includes(ev);
|
|
2398
|
+
return {
|
|
2399
|
+
kind: "event",
|
|
2400
|
+
name: ev,
|
|
2401
|
+
known: true,
|
|
2402
|
+
requiredIntents: EVENT_REQUIRED_INTENTS[ev],
|
|
2403
|
+
allowedByBridge: allowedByBridge2,
|
|
2404
|
+
reasonCode: allowedByBridge2 ? "allowed" : "denied_by_bridge",
|
|
2405
|
+
...!allowedByBridge2 ? { remediation: DENIED_REMEDIATION } : {}
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
const act = name;
|
|
2409
|
+
const allowedByBridge = capabilities.actions.includes(act);
|
|
2410
|
+
return {
|
|
2411
|
+
kind: "action",
|
|
2412
|
+
name: act,
|
|
2413
|
+
known: true,
|
|
2414
|
+
allowedByBridge,
|
|
2415
|
+
reasonCode: allowedByBridge ? "allowed" : "denied_by_bridge",
|
|
2416
|
+
...!allowedByBridge ? { remediation: DENIED_REMEDIATION } : {}
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
// src/dx/preflight.ts
|
|
2421
|
+
function parseAppBridgeUrl(url) {
|
|
2422
|
+
try {
|
|
2423
|
+
return new URL(url);
|
|
2424
|
+
} catch {
|
|
2425
|
+
return null;
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
function buildPreflightReport(input) {
|
|
2429
|
+
const issues = [];
|
|
2430
|
+
const { connected, capabilities, appUrl, desired, subscriptionCapabilityMessage, subscriptionCapabilityRemediation } = input;
|
|
2431
|
+
if (subscriptionCapabilityMessage) {
|
|
2432
|
+
issues.push({
|
|
2433
|
+
severity: "error",
|
|
2434
|
+
code: "subscription_event_not_negotiated",
|
|
2435
|
+
message: subscriptionCapabilityMessage,
|
|
2436
|
+
remediation: subscriptionCapabilityRemediation ?? "Remove `app.on` handlers for disallowed events or widen bot intents / secret scope."
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
const parsed = parseAppBridgeUrl(appUrl);
|
|
2440
|
+
if (parsed?.protocol === "ws:") {
|
|
2441
|
+
const host = parsed.hostname;
|
|
2442
|
+
const isLoopback = host === "localhost" || host === "127.0.0.1" || host === "::1";
|
|
2443
|
+
if (isLoopback) {
|
|
2444
|
+
issues.push({
|
|
2445
|
+
severity: "warning",
|
|
2446
|
+
code: "insecure_transport_local",
|
|
2447
|
+
message: "Using ws:// to a loopback host is fine for development.",
|
|
2448
|
+
remediation: "Use wss:// behind TLS for production deployments."
|
|
2449
|
+
});
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
if (!connected || !capabilities) {
|
|
2453
|
+
issues.push({
|
|
2454
|
+
severity: "warning",
|
|
2455
|
+
code: "not_connected",
|
|
2456
|
+
message: "Preflight ran before the app finished authenticating.",
|
|
2457
|
+
remediation: "Ensure the bridge URL and secret are correct; `preflight()` awaits authentication internally."
|
|
2458
|
+
});
|
|
2459
|
+
return {
|
|
2460
|
+
ok: issues.every((i) => i.severity !== "error"),
|
|
2461
|
+
connected,
|
|
2462
|
+
capabilities,
|
|
2463
|
+
issues
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
const allowedEvents = new Set(capabilities.events);
|
|
2467
|
+
const allowedActions = new Set(capabilities.actions);
|
|
2468
|
+
if (desired?.events?.length) {
|
|
2469
|
+
for (const ev of desired.events) {
|
|
2470
|
+
if (!allowedEvents.has(ev)) {
|
|
2471
|
+
issues.push({
|
|
2472
|
+
severity: "error",
|
|
2473
|
+
code: "desired_event_not_allowed",
|
|
2474
|
+
message: `Negotiated capabilities do not include event "${ev}".`,
|
|
2475
|
+
remediation: "Add gateway intents on the bot bridge and/or include this event in the scoped secret `allow.events`."
|
|
2476
|
+
});
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
if (desired?.actions?.length) {
|
|
2481
|
+
for (const act of desired.actions) {
|
|
2482
|
+
if (!allowedActions.has(act)) {
|
|
2483
|
+
issues.push({
|
|
2484
|
+
severity: "error",
|
|
2485
|
+
code: "desired_action_not_allowed",
|
|
2486
|
+
message: `Negotiated capabilities do not include action "${act}".`,
|
|
2487
|
+
remediation: "Widen `server.secrets[].allow.actions` or use a full-access secret for this app."
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
const hasError = issues.some((i) => i.severity === "error");
|
|
2493
|
+
return {
|
|
2494
|
+
ok: !hasError,
|
|
2495
|
+
connected,
|
|
2496
|
+
capabilities: { events: [...capabilities.events], actions: [...capabilities.actions] },
|
|
2497
|
+
issues
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
// src/dx/shardwire-catalog.ts
|
|
2502
|
+
function getShardwireCatalog() {
|
|
2503
|
+
return {
|
|
2504
|
+
events: BOT_EVENT_NAMES.map((name) => ({
|
|
2505
|
+
name,
|
|
2506
|
+
requiredIntents: EVENT_REQUIRED_INTENTS[name]
|
|
2507
|
+
})),
|
|
2508
|
+
actions: [...BOT_ACTION_NAMES],
|
|
2509
|
+
subscriptionFilters: [...SUBSCRIPTION_FILTER_KEYS]
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2330
2513
|
// src/utils/backoff.ts
|
|
2331
2514
|
function getBackoffDelay(attempt, config) {
|
|
2332
2515
|
const base = Math.min(config.maxDelayMs, config.initialDelayMs * 2 ** attempt);
|
|
@@ -2453,7 +2636,12 @@ function connectBotBridge(options) {
|
|
|
2453
2636
|
const allowedEvents = new Set(currentCapabilities.events);
|
|
2454
2637
|
const invalidEvents = [...eventHandlers.keys()].filter((eventName) => !allowedEvents.has(eventName));
|
|
2455
2638
|
const firstInvalid = invalidEvents[0];
|
|
2456
|
-
capabilityError = firstInvalid !== void 0 ? new BridgeCapabilityError(
|
|
2639
|
+
capabilityError = firstInvalid !== void 0 ? new BridgeCapabilityError(
|
|
2640
|
+
"event",
|
|
2641
|
+
firstInvalid,
|
|
2642
|
+
`Event "${firstInvalid}" is not available for this app.`,
|
|
2643
|
+
buildBridgeCapabilityErrorDetails("event", firstInvalid)
|
|
2644
|
+
) : null;
|
|
2457
2645
|
for (const [eventName, handlers] of eventHandlers.entries()) {
|
|
2458
2646
|
if (!allowedEvents.has(eventName)) {
|
|
2459
2647
|
continue;
|
|
@@ -2626,7 +2814,12 @@ function connectBotBridge(options) {
|
|
|
2626
2814
|
ts: Date.now(),
|
|
2627
2815
|
error: {
|
|
2628
2816
|
code: "FORBIDDEN",
|
|
2629
|
-
message: `Action "${name}" is not available for this app
|
|
2817
|
+
message: `Action "${name}" is not available for this app.`,
|
|
2818
|
+
details: {
|
|
2819
|
+
reasonCode: "action_not_in_capabilities",
|
|
2820
|
+
action: name,
|
|
2821
|
+
remediation: "Add gateway intents on the bot bridge and/or include this action in the scoped secret `allow.actions`."
|
|
2822
|
+
}
|
|
2630
2823
|
}
|
|
2631
2824
|
};
|
|
2632
2825
|
}
|
|
@@ -2773,9 +2966,49 @@ function connectBotBridge(options) {
|
|
|
2773
2966
|
actions: [...currentCapabilities.actions]
|
|
2774
2967
|
};
|
|
2775
2968
|
},
|
|
2969
|
+
catalog() {
|
|
2970
|
+
return getShardwireCatalog();
|
|
2971
|
+
},
|
|
2972
|
+
explainCapability(query) {
|
|
2973
|
+
const connected = Boolean(socket && socket.readyState === 1 && isAuthed);
|
|
2974
|
+
const caps = connected ? { events: [...currentCapabilities.events], actions: [...currentCapabilities.actions] } : null;
|
|
2975
|
+
return explainCapability(connected, caps, query);
|
|
2976
|
+
},
|
|
2977
|
+
async preflight(desired) {
|
|
2978
|
+
try {
|
|
2979
|
+
await connect();
|
|
2980
|
+
} catch (error) {
|
|
2981
|
+
return {
|
|
2982
|
+
ok: false,
|
|
2983
|
+
connected: false,
|
|
2984
|
+
capabilities: null,
|
|
2985
|
+
issues: [
|
|
2986
|
+
{
|
|
2987
|
+
severity: "error",
|
|
2988
|
+
code: "connection_failed",
|
|
2989
|
+
message: error instanceof Error ? error.message : "Failed to connect or authenticate.",
|
|
2990
|
+
remediation: "Verify `url` and `secret`, and ensure the bot bridge is running and reachable."
|
|
2991
|
+
}
|
|
2992
|
+
]
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
return buildPreflightReport({
|
|
2996
|
+
connected: Boolean(socket && socket.readyState === 1 && isAuthed),
|
|
2997
|
+
capabilities: isAuthed ? { events: [...currentCapabilities.events], actions: [...currentCapabilities.actions] } : null,
|
|
2998
|
+
appUrl: options.url,
|
|
2999
|
+
...desired !== void 0 ? { desired } : {},
|
|
3000
|
+
subscriptionCapabilityMessage: capabilityError?.message ?? null,
|
|
3001
|
+
subscriptionCapabilityRemediation: capabilityError?.details?.remediation ?? null
|
|
3002
|
+
});
|
|
3003
|
+
},
|
|
2776
3004
|
on(name, handler, filter) {
|
|
2777
3005
|
if (currentCapabilities.events.length > 0 && !currentCapabilities.events.includes(name)) {
|
|
2778
|
-
throw new BridgeCapabilityError(
|
|
3006
|
+
throw new BridgeCapabilityError(
|
|
3007
|
+
"event",
|
|
3008
|
+
name,
|
|
3009
|
+
`Event "${name}" is not available for this app.`,
|
|
3010
|
+
buildBridgeCapabilityErrorDetails("event", name)
|
|
3011
|
+
);
|
|
2779
3012
|
}
|
|
2780
3013
|
const casted = handler;
|
|
2781
3014
|
const subscription = filter ? { name, filter } : { name };
|
|
@@ -2826,10 +3059,54 @@ function connectBotBridge(options) {
|
|
|
2826
3059
|
}
|
|
2827
3060
|
};
|
|
2828
3061
|
}
|
|
3062
|
+
|
|
3063
|
+
// src/workflows/interaction.ts
|
|
3064
|
+
async function deferThenEditInteractionReply(app, args, options) {
|
|
3065
|
+
const { interactionId, defer, edit } = args;
|
|
3066
|
+
const deferResult = await app.actions.deferInteraction({ interactionId, ...defer }, options);
|
|
3067
|
+
if (!deferResult.ok) {
|
|
3068
|
+
return deferResult;
|
|
3069
|
+
}
|
|
3070
|
+
return app.actions.editInteractionReply(edit, options);
|
|
3071
|
+
}
|
|
3072
|
+
async function deferUpdateThenEditInteractionReply(app, args, options) {
|
|
3073
|
+
const { interactionId, edit } = args;
|
|
3074
|
+
const deferResult = await app.actions.deferUpdateInteraction({ interactionId }, options);
|
|
3075
|
+
if (!deferResult.ok) {
|
|
3076
|
+
return deferResult;
|
|
3077
|
+
}
|
|
3078
|
+
return app.actions.editInteractionReply(edit, options);
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
// src/workflows/thread.ts
|
|
3082
|
+
async function createThreadThenSendMessage(app, args, options) {
|
|
3083
|
+
const threadResult = await app.actions.createThread(args.thread, options);
|
|
3084
|
+
if (!threadResult.ok) {
|
|
3085
|
+
return {
|
|
3086
|
+
threadResult,
|
|
3087
|
+
messageResult: {
|
|
3088
|
+
ok: false,
|
|
3089
|
+
requestId: options?.requestId ?? "unknown",
|
|
3090
|
+
ts: Date.now(),
|
|
3091
|
+
error: {
|
|
3092
|
+
code: "INVALID_REQUEST",
|
|
3093
|
+
message: "Skipped send because createThread failed.",
|
|
3094
|
+
details: { skipped: true }
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
3099
|
+
const messageResult = await app.actions.sendMessage({ ...args.message, channelId: threadResult.data.id }, options);
|
|
3100
|
+
return { threadResult, messageResult };
|
|
3101
|
+
}
|
|
2829
3102
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2830
3103
|
0 && (module.exports = {
|
|
2831
3104
|
BridgeCapabilityError,
|
|
2832
3105
|
connectBotBridge,
|
|
2833
|
-
createBotBridge
|
|
3106
|
+
createBotBridge,
|
|
3107
|
+
createThreadThenSendMessage,
|
|
3108
|
+
deferThenEditInteractionReply,
|
|
3109
|
+
deferUpdateThenEditInteractionReply,
|
|
3110
|
+
getShardwireCatalog
|
|
2834
3111
|
});
|
|
2835
3112
|
//# sourceMappingURL=index.js.map
|