shardwire 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -1
- package/dist/index.d.mts +141 -8
- package/dist/index.d.ts +141 -8
- package/dist/index.js +528 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +528 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -36,8 +36,14 @@ var BOT_EVENT_NAMES = [
|
|
|
36
36
|
"messageDelete",
|
|
37
37
|
"messageReactionAdd",
|
|
38
38
|
"messageReactionRemove",
|
|
39
|
+
"guildCreate",
|
|
40
|
+
"guildDelete",
|
|
39
41
|
"guildMemberAdd",
|
|
40
|
-
"guildMemberRemove"
|
|
42
|
+
"guildMemberRemove",
|
|
43
|
+
"guildMemberUpdate",
|
|
44
|
+
"threadCreate",
|
|
45
|
+
"threadUpdate",
|
|
46
|
+
"threadDelete"
|
|
41
47
|
];
|
|
42
48
|
var BOT_ACTION_NAMES = [
|
|
43
49
|
"sendMessage",
|
|
@@ -45,7 +51,14 @@ var BOT_ACTION_NAMES = [
|
|
|
45
51
|
"deleteMessage",
|
|
46
52
|
"replyToInteraction",
|
|
47
53
|
"deferInteraction",
|
|
54
|
+
"deferUpdateInteraction",
|
|
48
55
|
"followUpInteraction",
|
|
56
|
+
"editInteractionReply",
|
|
57
|
+
"deleteInteractionReply",
|
|
58
|
+
"updateInteraction",
|
|
59
|
+
"showModal",
|
|
60
|
+
"fetchMessage",
|
|
61
|
+
"fetchMember",
|
|
49
62
|
"banMember",
|
|
50
63
|
"kickMember",
|
|
51
64
|
"addMemberRole",
|
|
@@ -67,8 +80,14 @@ var EVENT_REQUIRED_INTENTS = {
|
|
|
67
80
|
messageDelete: ["GuildMessages"],
|
|
68
81
|
messageReactionAdd: ["GuildMessageReactions"],
|
|
69
82
|
messageReactionRemove: ["GuildMessageReactions"],
|
|
83
|
+
guildCreate: ["Guilds"],
|
|
84
|
+
guildDelete: ["Guilds"],
|
|
70
85
|
guildMemberAdd: ["GuildMembers"],
|
|
71
|
-
guildMemberRemove: ["GuildMembers"]
|
|
86
|
+
guildMemberRemove: ["GuildMembers"],
|
|
87
|
+
guildMemberUpdate: ["GuildMembers"],
|
|
88
|
+
threadCreate: ["Guilds"],
|
|
89
|
+
threadUpdate: ["Guilds"],
|
|
90
|
+
threadDelete: ["Guilds"]
|
|
72
91
|
};
|
|
73
92
|
function getAvailableEvents(intents) {
|
|
74
93
|
const enabled = new Set(intents);
|
|
@@ -158,6 +177,7 @@ function serializeMessage(message) {
|
|
|
158
177
|
...message.reference.channelId ? { channelId: message.reference.channelId } : {},
|
|
159
178
|
...message.reference.guildId ? { guildId: message.reference.guildId } : {}
|
|
160
179
|
} : void 0;
|
|
180
|
+
const components = "components" in message && message.components && message.components.length > 0 ? message.components.map((row) => row.toJSON()) : void 0;
|
|
161
181
|
return {
|
|
162
182
|
id: message.id,
|
|
163
183
|
channelId: message.channelId,
|
|
@@ -175,9 +195,29 @@ function serializeMessage(message) {
|
|
|
175
195
|
size: attachment.size
|
|
176
196
|
})) : [],
|
|
177
197
|
embeds: serializeEmbeds(message),
|
|
198
|
+
...components ? { components } : {},
|
|
178
199
|
...reference ? { reference } : {}
|
|
179
200
|
};
|
|
180
201
|
}
|
|
202
|
+
function serializeGuild(guild) {
|
|
203
|
+
return {
|
|
204
|
+
id: guild.id,
|
|
205
|
+
name: guild.name,
|
|
206
|
+
...guild.icon !== null && guild.icon !== void 0 ? { icon: guild.icon } : { icon: null },
|
|
207
|
+
ownerId: guild.ownerId
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function serializeThread(thread) {
|
|
211
|
+
return {
|
|
212
|
+
id: thread.id,
|
|
213
|
+
guildId: thread.guildId,
|
|
214
|
+
parentId: thread.parentId,
|
|
215
|
+
name: thread.name,
|
|
216
|
+
type: thread.type,
|
|
217
|
+
...typeof thread.archived === "boolean" ? { archived: thread.archived } : {},
|
|
218
|
+
...typeof thread.locked === "boolean" ? { locked: thread.locked } : {}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
181
221
|
function serializeDeletedMessage(message) {
|
|
182
222
|
return {
|
|
183
223
|
id: message.id,
|
|
@@ -347,7 +387,10 @@ function toSendOptions(input) {
|
|
|
347
387
|
return {
|
|
348
388
|
...input.content !== void 0 ? { content: input.content } : {},
|
|
349
389
|
...input.embeds !== void 0 ? { embeds: input.embeds } : {},
|
|
350
|
-
...input.allowedMentions !== void 0 ? { allowedMentions: input.allowedMentions } : {}
|
|
390
|
+
...input.allowedMentions !== void 0 ? { allowedMentions: input.allowedMentions } : {},
|
|
391
|
+
...input.components !== void 0 ? { components: input.components } : {},
|
|
392
|
+
...input.flags !== void 0 ? { flags: input.flags } : {},
|
|
393
|
+
...input.stickerIds !== void 0 ? { stickers: input.stickerIds } : {}
|
|
351
394
|
};
|
|
352
395
|
}
|
|
353
396
|
function extractDiscordErrorDetails(error) {
|
|
@@ -371,14 +414,25 @@ function extractDiscordErrorDetails(error) {
|
|
|
371
414
|
function mapDiscordErrorToActionExecutionError(error) {
|
|
372
415
|
const details = extractDiscordErrorDetails(error);
|
|
373
416
|
const message = details.message ?? (error instanceof Error ? error.message : "Discord action failed.");
|
|
417
|
+
const detailPayload = {
|
|
418
|
+
...typeof details.status === "number" ? { discordStatus: details.status } : {},
|
|
419
|
+
...typeof details.code === "number" ? { discordCode: details.code } : {}
|
|
420
|
+
};
|
|
374
421
|
if (details.status === 403 || details.code !== void 0 && DISCORD_FORBIDDEN_CODES.has(details.code)) {
|
|
375
|
-
return new ActionExecutionError("FORBIDDEN", message);
|
|
422
|
+
return new ActionExecutionError("FORBIDDEN", message, detailPayload);
|
|
376
423
|
}
|
|
377
424
|
if (details.status === 404 || details.code !== void 0 && DISCORD_NOT_FOUND_CODES.has(details.code)) {
|
|
378
|
-
return new ActionExecutionError("NOT_FOUND", message);
|
|
425
|
+
return new ActionExecutionError("NOT_FOUND", message, detailPayload);
|
|
379
426
|
}
|
|
380
427
|
if (details.status === 400 || details.code !== void 0 && DISCORD_INVALID_REQUEST_CODES.has(details.code)) {
|
|
381
|
-
return new ActionExecutionError("INVALID_REQUEST", message);
|
|
428
|
+
return new ActionExecutionError("INVALID_REQUEST", message, detailPayload);
|
|
429
|
+
}
|
|
430
|
+
if (details.status === 429) {
|
|
431
|
+
return new ActionExecutionError("SERVICE_UNAVAILABLE", message, {
|
|
432
|
+
discordStatus: 429,
|
|
433
|
+
retryable: true,
|
|
434
|
+
...details.code !== void 0 ? { discordCode: details.code } : {}
|
|
435
|
+
});
|
|
382
436
|
}
|
|
383
437
|
return null;
|
|
384
438
|
}
|
|
@@ -399,7 +453,14 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
399
453
|
deleteMessage: (payload) => this.deleteMessage(payload),
|
|
400
454
|
replyToInteraction: (payload) => this.replyToInteraction(payload),
|
|
401
455
|
deferInteraction: (payload) => this.deferInteraction(payload),
|
|
456
|
+
deferUpdateInteraction: (payload) => this.deferUpdateInteraction(payload),
|
|
402
457
|
followUpInteraction: (payload) => this.followUpInteraction(payload),
|
|
458
|
+
editInteractionReply: (payload) => this.editInteractionReply(payload),
|
|
459
|
+
deleteInteractionReply: (payload) => this.deleteInteractionReply(payload),
|
|
460
|
+
updateInteraction: (payload) => this.updateInteraction(payload),
|
|
461
|
+
showModal: (payload) => this.showModal(payload),
|
|
462
|
+
fetchMessage: (payload) => this.fetchMessage(payload),
|
|
463
|
+
fetchMember: (payload) => this.fetchMember(payload),
|
|
403
464
|
banMember: (payload) => this.banMember(payload),
|
|
404
465
|
kickMember: (payload) => this.kickMember(payload),
|
|
405
466
|
addMemberRole: (payload) => this.addMemberRole(payload),
|
|
@@ -415,6 +476,10 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
415
476
|
actionHandlers;
|
|
416
477
|
readyPromise = null;
|
|
417
478
|
hasReady = false;
|
|
479
|
+
shardEnvelope() {
|
|
480
|
+
const shardId = this.client.shard?.ids?.[0];
|
|
481
|
+
return shardId !== void 0 ? { shardId } : {};
|
|
482
|
+
}
|
|
418
483
|
isReady() {
|
|
419
484
|
return this.hasReady && this.client.isReady();
|
|
420
485
|
}
|
|
@@ -463,6 +528,7 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
463
528
|
}
|
|
464
529
|
handler({
|
|
465
530
|
receivedAt: Date.now(),
|
|
531
|
+
...this.shardEnvelope(),
|
|
466
532
|
user: serializeUser(this.client.user)
|
|
467
533
|
});
|
|
468
534
|
};
|
|
@@ -476,11 +542,10 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
476
542
|
}
|
|
477
543
|
case "interactionCreate": {
|
|
478
544
|
const listener = (interaction) => {
|
|
479
|
-
|
|
480
|
-
this.interactionCache.set(interaction.id, interaction);
|
|
481
|
-
}
|
|
545
|
+
this.interactionCache.set(interaction.id, interaction);
|
|
482
546
|
handler({
|
|
483
547
|
receivedAt: Date.now(),
|
|
548
|
+
...this.shardEnvelope(),
|
|
484
549
|
interaction: serializeInteraction(interaction)
|
|
485
550
|
});
|
|
486
551
|
};
|
|
@@ -493,6 +558,7 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
493
558
|
const listener = (message) => {
|
|
494
559
|
handler({
|
|
495
560
|
receivedAt: Date.now(),
|
|
561
|
+
...this.shardEnvelope(),
|
|
496
562
|
message: serializeMessage(message)
|
|
497
563
|
});
|
|
498
564
|
};
|
|
@@ -505,6 +571,7 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
505
571
|
const listener = (oldMessage, newMessage) => {
|
|
506
572
|
handler({
|
|
507
573
|
receivedAt: Date.now(),
|
|
574
|
+
...this.shardEnvelope(),
|
|
508
575
|
oldMessage: serializeMessage(oldMessage),
|
|
509
576
|
message: serializeMessage(newMessage)
|
|
510
577
|
});
|
|
@@ -518,6 +585,7 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
518
585
|
const listener = (message) => {
|
|
519
586
|
handler({
|
|
520
587
|
receivedAt: Date.now(),
|
|
588
|
+
...this.shardEnvelope(),
|
|
521
589
|
message: serializeDeletedMessage(message)
|
|
522
590
|
});
|
|
523
591
|
};
|
|
@@ -530,6 +598,7 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
530
598
|
const listener = (reaction, user) => {
|
|
531
599
|
handler({
|
|
532
600
|
receivedAt: Date.now(),
|
|
601
|
+
...this.shardEnvelope(),
|
|
533
602
|
reaction: serializeMessageReaction(reaction, user)
|
|
534
603
|
});
|
|
535
604
|
};
|
|
@@ -542,6 +611,7 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
542
611
|
const listener = (reaction, user) => {
|
|
543
612
|
handler({
|
|
544
613
|
receivedAt: Date.now(),
|
|
614
|
+
...this.shardEnvelope(),
|
|
545
615
|
reaction: serializeMessageReaction(reaction, user)
|
|
546
616
|
});
|
|
547
617
|
};
|
|
@@ -554,6 +624,7 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
554
624
|
const listener = (member) => {
|
|
555
625
|
handler({
|
|
556
626
|
receivedAt: Date.now(),
|
|
627
|
+
...this.shardEnvelope(),
|
|
557
628
|
member: serializeGuildMember(member)
|
|
558
629
|
});
|
|
559
630
|
};
|
|
@@ -566,6 +637,7 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
566
637
|
const listener = (member) => {
|
|
567
638
|
handler({
|
|
568
639
|
receivedAt: Date.now(),
|
|
640
|
+
...this.shardEnvelope(),
|
|
569
641
|
member: serializeGuildMember(member)
|
|
570
642
|
});
|
|
571
643
|
};
|
|
@@ -574,6 +646,91 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
574
646
|
this.client.off(import_discord2.Events.GuildMemberRemove, listener);
|
|
575
647
|
};
|
|
576
648
|
}
|
|
649
|
+
case "guildMemberUpdate": {
|
|
650
|
+
const listener = (oldMember, newMember) => {
|
|
651
|
+
handler({
|
|
652
|
+
receivedAt: Date.now(),
|
|
653
|
+
...this.shardEnvelope(),
|
|
654
|
+
oldMember: serializeGuildMember(oldMember),
|
|
655
|
+
member: serializeGuildMember(newMember)
|
|
656
|
+
});
|
|
657
|
+
};
|
|
658
|
+
this.client.on(import_discord2.Events.GuildMemberUpdate, listener);
|
|
659
|
+
return () => {
|
|
660
|
+
this.client.off(import_discord2.Events.GuildMemberUpdate, listener);
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
case "guildCreate": {
|
|
664
|
+
const listener = (guild) => {
|
|
665
|
+
handler({
|
|
666
|
+
receivedAt: Date.now(),
|
|
667
|
+
...this.shardEnvelope(),
|
|
668
|
+
guild: serializeGuild(guild)
|
|
669
|
+
});
|
|
670
|
+
};
|
|
671
|
+
this.client.on(import_discord2.Events.GuildCreate, listener);
|
|
672
|
+
return () => {
|
|
673
|
+
this.client.off(import_discord2.Events.GuildCreate, listener);
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
case "guildDelete": {
|
|
677
|
+
const listener = (guild) => {
|
|
678
|
+
handler({
|
|
679
|
+
receivedAt: Date.now(),
|
|
680
|
+
...this.shardEnvelope(),
|
|
681
|
+
guild: {
|
|
682
|
+
id: guild.id,
|
|
683
|
+
name: guild.name ?? "Unknown",
|
|
684
|
+
icon: guild.icon,
|
|
685
|
+
...guild.ownerId ? { ownerId: guild.ownerId } : {}
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
};
|
|
689
|
+
this.client.on(import_discord2.Events.GuildDelete, listener);
|
|
690
|
+
return () => {
|
|
691
|
+
this.client.off(import_discord2.Events.GuildDelete, listener);
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
case "threadCreate": {
|
|
695
|
+
const listener = (thread) => {
|
|
696
|
+
handler({
|
|
697
|
+
receivedAt: Date.now(),
|
|
698
|
+
...this.shardEnvelope(),
|
|
699
|
+
thread: serializeThread(thread)
|
|
700
|
+
});
|
|
701
|
+
};
|
|
702
|
+
this.client.on(import_discord2.Events.ThreadCreate, listener);
|
|
703
|
+
return () => {
|
|
704
|
+
this.client.off(import_discord2.Events.ThreadCreate, listener);
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
case "threadUpdate": {
|
|
708
|
+
const listener = (oldThread, newThread) => {
|
|
709
|
+
handler({
|
|
710
|
+
receivedAt: Date.now(),
|
|
711
|
+
...this.shardEnvelope(),
|
|
712
|
+
oldThread: serializeThread(oldThread),
|
|
713
|
+
thread: serializeThread(newThread)
|
|
714
|
+
});
|
|
715
|
+
};
|
|
716
|
+
this.client.on(import_discord2.Events.ThreadUpdate, listener);
|
|
717
|
+
return () => {
|
|
718
|
+
this.client.off(import_discord2.Events.ThreadUpdate, listener);
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
case "threadDelete": {
|
|
722
|
+
const listener = (thread) => {
|
|
723
|
+
handler({
|
|
724
|
+
receivedAt: Date.now(),
|
|
725
|
+
...this.shardEnvelope(),
|
|
726
|
+
thread: serializeThread(thread)
|
|
727
|
+
});
|
|
728
|
+
};
|
|
729
|
+
this.client.on(import_discord2.Events.ThreadDelete, listener);
|
|
730
|
+
return () => {
|
|
731
|
+
this.client.off(import_discord2.Events.ThreadDelete, listener);
|
|
732
|
+
};
|
|
733
|
+
}
|
|
577
734
|
default:
|
|
578
735
|
return () => void 0;
|
|
579
736
|
}
|
|
@@ -616,6 +773,26 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
616
773
|
}
|
|
617
774
|
return interaction;
|
|
618
775
|
}
|
|
776
|
+
getReplyCapableInteraction(interactionId) {
|
|
777
|
+
const interaction = this.getInteraction(interactionId);
|
|
778
|
+
if (!isReplyCapableInteraction(interaction)) {
|
|
779
|
+
throw new ActionExecutionError(
|
|
780
|
+
"INVALID_REQUEST",
|
|
781
|
+
`Interaction "${interactionId}" does not support reply-style acknowledgements.`
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
return interaction;
|
|
785
|
+
}
|
|
786
|
+
getMessageComponentInteraction(interactionId) {
|
|
787
|
+
const interaction = this.getInteraction(interactionId);
|
|
788
|
+
if (!interaction.isMessageComponent()) {
|
|
789
|
+
throw new ActionExecutionError(
|
|
790
|
+
"INVALID_REQUEST",
|
|
791
|
+
`Interaction "${interactionId}" is not a message component interaction.`
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
return interaction;
|
|
795
|
+
}
|
|
619
796
|
async sendMessage(payload) {
|
|
620
797
|
const channel = await this.fetchSendableChannel(payload.channelId);
|
|
621
798
|
const message = await channel.send(toSendOptions(payload));
|
|
@@ -638,7 +815,7 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
638
815
|
};
|
|
639
816
|
}
|
|
640
817
|
async replyToInteraction(payload) {
|
|
641
|
-
const interaction = this.
|
|
818
|
+
const interaction = this.getReplyCapableInteraction(payload.interactionId);
|
|
642
819
|
if (interaction.replied || interaction.deferred) {
|
|
643
820
|
throw new ActionExecutionError("INVALID_REQUEST", `Interaction "${payload.interactionId}" has already been acknowledged.`);
|
|
644
821
|
}
|
|
@@ -650,7 +827,7 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
650
827
|
return serializeMessage(reply);
|
|
651
828
|
}
|
|
652
829
|
async deferInteraction(payload) {
|
|
653
|
-
const interaction = this.
|
|
830
|
+
const interaction = this.getReplyCapableInteraction(payload.interactionId);
|
|
654
831
|
if (interaction.replied) {
|
|
655
832
|
throw new ActionExecutionError("INVALID_REQUEST", `Interaction "${payload.interactionId}" has already been replied to.`);
|
|
656
833
|
}
|
|
@@ -664,8 +841,16 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
664
841
|
interactionId: payload.interactionId
|
|
665
842
|
};
|
|
666
843
|
}
|
|
844
|
+
async deferUpdateInteraction(payload) {
|
|
845
|
+
const interaction = this.getMessageComponentInteraction(payload.interactionId);
|
|
846
|
+
await interaction.deferUpdate();
|
|
847
|
+
return {
|
|
848
|
+
deferred: true,
|
|
849
|
+
interactionId: payload.interactionId
|
|
850
|
+
};
|
|
851
|
+
}
|
|
667
852
|
async followUpInteraction(payload) {
|
|
668
|
-
const interaction = this.
|
|
853
|
+
const interaction = this.getReplyCapableInteraction(payload.interactionId);
|
|
669
854
|
if (!interaction.replied && !interaction.deferred) {
|
|
670
855
|
throw new ActionExecutionError("INVALID_REQUEST", `Interaction "${payload.interactionId}" has not been acknowledged yet.`);
|
|
671
856
|
}
|
|
@@ -675,6 +860,52 @@ var DiscordJsRuntimeAdapter = class {
|
|
|
675
860
|
});
|
|
676
861
|
return serializeMessage(followUp);
|
|
677
862
|
}
|
|
863
|
+
async editInteractionReply(payload) {
|
|
864
|
+
const interaction = this.getReplyCapableInteraction(payload.interactionId);
|
|
865
|
+
const updated = await interaction.editReply({
|
|
866
|
+
...toSendOptions(payload),
|
|
867
|
+
fetchReply: true
|
|
868
|
+
});
|
|
869
|
+
return serializeMessage(updated);
|
|
870
|
+
}
|
|
871
|
+
async deleteInteractionReply(payload) {
|
|
872
|
+
const interaction = this.getReplyCapableInteraction(payload.interactionId);
|
|
873
|
+
await interaction.deleteReply();
|
|
874
|
+
return {
|
|
875
|
+
deleted: true,
|
|
876
|
+
interactionId: payload.interactionId
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
async updateInteraction(payload) {
|
|
880
|
+
const interaction = this.getMessageComponentInteraction(payload.interactionId);
|
|
881
|
+
const updated = await interaction.update({
|
|
882
|
+
...toSendOptions(payload),
|
|
883
|
+
fetchReply: true
|
|
884
|
+
});
|
|
885
|
+
return serializeMessage(updated);
|
|
886
|
+
}
|
|
887
|
+
async showModal(payload) {
|
|
888
|
+
const interaction = this.getMessageComponentInteraction(payload.interactionId);
|
|
889
|
+
await interaction.showModal({
|
|
890
|
+
title: payload.title,
|
|
891
|
+
customId: payload.customId,
|
|
892
|
+
components: payload.components
|
|
893
|
+
});
|
|
894
|
+
return {
|
|
895
|
+
shown: true,
|
|
896
|
+
interactionId: payload.interactionId
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
async fetchMessage(payload) {
|
|
900
|
+
const channel = await this.fetchMessageChannel(payload.channelId);
|
|
901
|
+
const message = await channel.messages.fetch(payload.messageId);
|
|
902
|
+
return serializeMessage(message);
|
|
903
|
+
}
|
|
904
|
+
async fetchMember(payload) {
|
|
905
|
+
const guild = await this.client.guilds.fetch(payload.guildId);
|
|
906
|
+
const member = await guild.members.fetch(payload.userId);
|
|
907
|
+
return serializeGuildMember(member);
|
|
908
|
+
}
|
|
678
909
|
async banMember(payload) {
|
|
679
910
|
const guild = await this.client.guilds.fetch(payload.guildId);
|
|
680
911
|
await guild.members.ban(payload.userId, {
|
|
@@ -744,7 +975,7 @@ function createDiscordJsRuntimeAdapter(options) {
|
|
|
744
975
|
return new DiscordJsRuntimeAdapter(options);
|
|
745
976
|
}
|
|
746
977
|
function isReplyCapableInteraction(interaction) {
|
|
747
|
-
return interaction.isRepliable() && typeof interaction.reply === "function" && typeof interaction.deferReply === "function" && typeof interaction.followUp === "function";
|
|
978
|
+
return interaction.isRepliable() && typeof interaction.reply === "function" && typeof interaction.deferReply === "function" && typeof interaction.followUp === "function" && typeof interaction.editReply === "function" && typeof interaction.deleteReply === "function";
|
|
748
979
|
}
|
|
749
980
|
|
|
750
981
|
// src/bridge/transport/server.ts
|
|
@@ -768,6 +999,14 @@ function normalizeStringList(value) {
|
|
|
768
999
|
const normalized = [...new Set(rawValues.filter((entry) => typeof entry === "string" && entry.length > 0))].sort();
|
|
769
1000
|
return normalized.length > 0 ? normalized : void 0;
|
|
770
1001
|
}
|
|
1002
|
+
function normalizeKindList(value) {
|
|
1003
|
+
if (value === void 0) {
|
|
1004
|
+
return void 0;
|
|
1005
|
+
}
|
|
1006
|
+
const rawValues = Array.isArray(value) ? value : [value];
|
|
1007
|
+
const normalized = [...new Set(rawValues.filter((entry) => typeof entry === "string"))].sort();
|
|
1008
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
1009
|
+
}
|
|
771
1010
|
function normalizeEventSubscriptionFilter(filter) {
|
|
772
1011
|
if (!filter) {
|
|
773
1012
|
return void 0;
|
|
@@ -777,6 +1016,8 @@ function normalizeEventSubscriptionFilter(filter) {
|
|
|
777
1016
|
const channelIds = normalizeStringList(filter.channelId);
|
|
778
1017
|
const userIds = normalizeStringList(filter.userId);
|
|
779
1018
|
const commandNames = normalizeStringList(filter.commandName);
|
|
1019
|
+
const customIds = normalizeStringList(filter.customId);
|
|
1020
|
+
const interactionKinds = normalizeKindList(filter.interactionKind);
|
|
780
1021
|
if (guildIds) {
|
|
781
1022
|
normalized.guildId = guildIds;
|
|
782
1023
|
}
|
|
@@ -789,6 +1030,12 @@ function normalizeEventSubscriptionFilter(filter) {
|
|
|
789
1030
|
if (commandNames) {
|
|
790
1031
|
normalized.commandName = commandNames;
|
|
791
1032
|
}
|
|
1033
|
+
if (customIds) {
|
|
1034
|
+
normalized.customId = customIds;
|
|
1035
|
+
}
|
|
1036
|
+
if (interactionKinds) {
|
|
1037
|
+
normalized.interactionKind = interactionKinds;
|
|
1038
|
+
}
|
|
792
1039
|
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
793
1040
|
}
|
|
794
1041
|
function normalizeEventSubscription(subscription) {
|
|
@@ -810,17 +1057,29 @@ function matchesField(value, allowed) {
|
|
|
810
1057
|
}
|
|
811
1058
|
return allowed.includes(value);
|
|
812
1059
|
}
|
|
1060
|
+
function matchesKind(value, allowed) {
|
|
1061
|
+
if (!allowed) {
|
|
1062
|
+
return true;
|
|
1063
|
+
}
|
|
1064
|
+
if (!value) {
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
return allowed.includes(value);
|
|
1068
|
+
}
|
|
813
1069
|
function eventMetadata(name, payload) {
|
|
814
1070
|
switch (name) {
|
|
815
1071
|
case "ready":
|
|
816
1072
|
return {};
|
|
817
1073
|
case "interactionCreate": {
|
|
818
1074
|
const interactionPayload = payload;
|
|
1075
|
+
const ix = interactionPayload.interaction;
|
|
819
1076
|
return {
|
|
820
|
-
...
|
|
821
|
-
...
|
|
822
|
-
userId:
|
|
823
|
-
...
|
|
1077
|
+
...ix.guildId ? { guildId: ix.guildId } : {},
|
|
1078
|
+
...ix.channelId ? { channelId: ix.channelId } : {},
|
|
1079
|
+
userId: ix.user.id,
|
|
1080
|
+
...ix.commandName ? { commandName: ix.commandName } : {},
|
|
1081
|
+
...ix.customId ? { customId: ix.customId } : {},
|
|
1082
|
+
interactionKind: ix.kind
|
|
824
1083
|
};
|
|
825
1084
|
}
|
|
826
1085
|
case "messageCreate": {
|
|
@@ -876,6 +1135,29 @@ function eventMetadata(name, payload) {
|
|
|
876
1135
|
userId: memberPayload.member.id
|
|
877
1136
|
};
|
|
878
1137
|
}
|
|
1138
|
+
case "guildMemberUpdate": {
|
|
1139
|
+
const memberPayload = payload;
|
|
1140
|
+
return {
|
|
1141
|
+
guildId: memberPayload.member.guildId,
|
|
1142
|
+
userId: memberPayload.member.id
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
case "guildCreate":
|
|
1146
|
+
case "guildDelete": {
|
|
1147
|
+
const guildPayload = payload;
|
|
1148
|
+
return {
|
|
1149
|
+
guildId: guildPayload.guild.id
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
case "threadCreate":
|
|
1153
|
+
case "threadUpdate":
|
|
1154
|
+
case "threadDelete": {
|
|
1155
|
+
const threadPayload = payload;
|
|
1156
|
+
return {
|
|
1157
|
+
guildId: threadPayload.thread.guildId,
|
|
1158
|
+
...threadPayload.thread.parentId ? { channelId: threadPayload.thread.parentId } : { channelId: threadPayload.thread.id }
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
879
1161
|
default:
|
|
880
1162
|
return {};
|
|
881
1163
|
}
|
|
@@ -886,7 +1168,7 @@ function matchesEventSubscription(subscription, payload) {
|
|
|
886
1168
|
return true;
|
|
887
1169
|
}
|
|
888
1170
|
const metadata = eventMetadata(normalized.name, payload);
|
|
889
|
-
return matchesField(metadata.guildId, normalized.filter.guildId) && matchesField(metadata.channelId, normalized.filter.channelId) && matchesField(metadata.userId, normalized.filter.userId) && matchesField(metadata.commandName, normalized.filter.commandName);
|
|
1171
|
+
return matchesField(metadata.guildId, normalized.filter.guildId) && matchesField(metadata.channelId, normalized.filter.channelId) && matchesField(metadata.userId, normalized.filter.userId) && matchesField(metadata.commandName, normalized.filter.commandName) && matchesField(metadata.customId, normalized.filter.customId) && matchesKind(metadata.interactionKind, normalized.filter.interactionKind);
|
|
890
1172
|
}
|
|
891
1173
|
|
|
892
1174
|
// src/bridge/transport/security.ts
|
|
@@ -922,22 +1204,79 @@ function stringifyEnvelope(envelope) {
|
|
|
922
1204
|
return JSON.stringify(envelope);
|
|
923
1205
|
}
|
|
924
1206
|
|
|
1207
|
+
// src/utils/semaphore.ts
|
|
1208
|
+
var AsyncSemaphore = class {
|
|
1209
|
+
constructor(max, acquireTimeoutMs) {
|
|
1210
|
+
this.max = max;
|
|
1211
|
+
this.acquireTimeoutMs = acquireTimeoutMs;
|
|
1212
|
+
}
|
|
1213
|
+
max;
|
|
1214
|
+
acquireTimeoutMs;
|
|
1215
|
+
active = 0;
|
|
1216
|
+
waiters = [];
|
|
1217
|
+
async run(fn) {
|
|
1218
|
+
const release = await this.acquire();
|
|
1219
|
+
try {
|
|
1220
|
+
return await fn();
|
|
1221
|
+
} finally {
|
|
1222
|
+
release();
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
acquire() {
|
|
1226
|
+
if (this.active < this.max) {
|
|
1227
|
+
this.active += 1;
|
|
1228
|
+
return Promise.resolve(() => this.release());
|
|
1229
|
+
}
|
|
1230
|
+
return new Promise((resolve, reject) => {
|
|
1231
|
+
const timeout = setTimeout(() => {
|
|
1232
|
+
const index = this.waiters.findIndex((entry) => entry.timeout === timeout);
|
|
1233
|
+
if (index >= 0) {
|
|
1234
|
+
this.waiters.splice(index, 1);
|
|
1235
|
+
}
|
|
1236
|
+
reject(new Error("ACTION_QUEUE_TIMEOUT"));
|
|
1237
|
+
}, this.acquireTimeoutMs);
|
|
1238
|
+
this.waiters.push({
|
|
1239
|
+
resolve: (release) => {
|
|
1240
|
+
clearTimeout(timeout);
|
|
1241
|
+
resolve(release);
|
|
1242
|
+
},
|
|
1243
|
+
reject,
|
|
1244
|
+
timeout
|
|
1245
|
+
});
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
release() {
|
|
1249
|
+
this.active -= 1;
|
|
1250
|
+
const next = this.waiters.shift();
|
|
1251
|
+
if (next) {
|
|
1252
|
+
clearTimeout(next.timeout);
|
|
1253
|
+
this.active += 1;
|
|
1254
|
+
next.resolve(() => this.release());
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
};
|
|
1258
|
+
|
|
925
1259
|
// src/bridge/transport/server.ts
|
|
926
1260
|
var CLOSE_AUTH_REQUIRED = 4001;
|
|
927
1261
|
var CLOSE_AUTH_FAILED = 4003;
|
|
928
1262
|
var CLOSE_INVALID_PAYLOAD = 4004;
|
|
1263
|
+
var CLOSE_SERVER_FULL = 4029;
|
|
1264
|
+
var CLOSE_AUTH_RATE_LIMITED = 4031;
|
|
929
1265
|
var BridgeTransportServer = class {
|
|
930
1266
|
constructor(config) {
|
|
931
1267
|
this.config = config;
|
|
932
1268
|
this.logger = withLogger(config.logger);
|
|
933
1269
|
this.heartbeatMs = config.options.server.heartbeatMs ?? 3e4;
|
|
1270
|
+
const maxConcurrent = config.options.server.maxConcurrentActions ?? 32;
|
|
1271
|
+
const queueTimeout = config.options.server.actionQueueTimeoutMs ?? 5e3;
|
|
1272
|
+
this.actionSemaphore = new AsyncSemaphore(maxConcurrent, queueTimeout);
|
|
934
1273
|
this.wss = new import_ws.WebSocketServer({
|
|
935
1274
|
host: config.options.server.host,
|
|
936
1275
|
port: config.options.server.port,
|
|
937
1276
|
path: config.options.server.path ?? "/shardwire",
|
|
938
1277
|
maxPayload: config.options.server.maxPayloadBytes ?? 65536
|
|
939
1278
|
});
|
|
940
|
-
this.wss.on("connection", (socket) => this.handleConnection(socket));
|
|
1279
|
+
this.wss.on("connection", (socket, request) => this.handleConnection(socket, request));
|
|
941
1280
|
this.wss.on("error", (error) => this.logger.error("Bridge transport server error.", { error: String(error) }));
|
|
942
1281
|
this.interval = setInterval(() => {
|
|
943
1282
|
this.checkHeartbeats();
|
|
@@ -951,6 +1290,10 @@ var BridgeTransportServer = class {
|
|
|
951
1290
|
interval;
|
|
952
1291
|
connections = /* @__PURE__ */ new Map();
|
|
953
1292
|
stickyEvents = /* @__PURE__ */ new Map();
|
|
1293
|
+
actionSemaphore;
|
|
1294
|
+
idempotencyCache = /* @__PURE__ */ new Map();
|
|
1295
|
+
idempotencyTtlMs = 12e4;
|
|
1296
|
+
authBuckets = /* @__PURE__ */ new Map();
|
|
954
1297
|
connectionCount() {
|
|
955
1298
|
let count = 0;
|
|
956
1299
|
for (const state of this.connections.values()) {
|
|
@@ -994,12 +1337,14 @@ var BridgeTransportServer = class {
|
|
|
994
1337
|
});
|
|
995
1338
|
});
|
|
996
1339
|
}
|
|
997
|
-
handleConnection(socket) {
|
|
1340
|
+
handleConnection(socket, request) {
|
|
1341
|
+
const remoteAddress = request?.socket?.remoteAddress ?? "unknown";
|
|
998
1342
|
const state = {
|
|
999
1343
|
id: createConnectionId(),
|
|
1000
1344
|
socket,
|
|
1001
1345
|
authenticated: false,
|
|
1002
1346
|
lastHeartbeatAt: Date.now(),
|
|
1347
|
+
remoteAddress,
|
|
1003
1348
|
subscriptions: /* @__PURE__ */ new Map()
|
|
1004
1349
|
};
|
|
1005
1350
|
this.connections.set(socket, state);
|
|
@@ -1050,6 +1395,21 @@ var BridgeTransportServer = class {
|
|
|
1050
1395
|
state.socket.close(CLOSE_AUTH_REQUIRED, "Authentication required.");
|
|
1051
1396
|
return;
|
|
1052
1397
|
}
|
|
1398
|
+
if (this.isAuthRateLimited(state.remoteAddress ?? "unknown")) {
|
|
1399
|
+
this.logger.warn("Bridge auth rate limited.", { connectionId: state.id, remoteAddress: state.remoteAddress });
|
|
1400
|
+
this.safeSend(
|
|
1401
|
+
state.socket,
|
|
1402
|
+
stringifyEnvelope(
|
|
1403
|
+
makeEnvelope("auth.error", {
|
|
1404
|
+
code: "UNAUTHORIZED",
|
|
1405
|
+
reason: "invalid_secret",
|
|
1406
|
+
message: "Too many authentication attempts. Try again later."
|
|
1407
|
+
})
|
|
1408
|
+
)
|
|
1409
|
+
);
|
|
1410
|
+
state.socket.close(CLOSE_AUTH_RATE_LIMITED, "Rate limited.");
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1053
1413
|
const authResult = this.config.authenticate(envelope.payload);
|
|
1054
1414
|
if (!authResult.ok) {
|
|
1055
1415
|
const message = authResult.reason === "ambiguous_secret" ? "Authentication failed: secret matches multiple configured scopes. Supply secretId or use unique secret values." : "Authentication failed.";
|
|
@@ -1066,6 +1426,25 @@ var BridgeTransportServer = class {
|
|
|
1066
1426
|
state.socket.close(CLOSE_AUTH_FAILED, "Invalid secret.");
|
|
1067
1427
|
return;
|
|
1068
1428
|
}
|
|
1429
|
+
const maxConnections = this.config.options.server.maxConnections;
|
|
1430
|
+
if (maxConnections !== void 0 && this.connectionCount() >= maxConnections) {
|
|
1431
|
+
this.logger.warn("Bridge connection rejected: server full.", {
|
|
1432
|
+
connectionId: state.id,
|
|
1433
|
+
maxConnections
|
|
1434
|
+
});
|
|
1435
|
+
this.safeSend(
|
|
1436
|
+
state.socket,
|
|
1437
|
+
stringifyEnvelope(
|
|
1438
|
+
makeEnvelope("auth.error", {
|
|
1439
|
+
code: "UNAUTHORIZED",
|
|
1440
|
+
reason: "invalid_secret",
|
|
1441
|
+
message: "Server is at maximum connection capacity."
|
|
1442
|
+
})
|
|
1443
|
+
)
|
|
1444
|
+
);
|
|
1445
|
+
state.socket.close(CLOSE_SERVER_FULL, "Server full.");
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1069
1448
|
state.authenticated = true;
|
|
1070
1449
|
state.lastHeartbeatAt = Date.now();
|
|
1071
1450
|
state.secret = authResult.secret;
|
|
@@ -1135,17 +1514,77 @@ var BridgeTransportServer = class {
|
|
|
1135
1514
|
if (!requestId || !payload || typeof payload.name !== "string") {
|
|
1136
1515
|
return;
|
|
1137
1516
|
}
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1517
|
+
const activeSecret = state.secret;
|
|
1518
|
+
const activeCapabilities = state.capabilities;
|
|
1519
|
+
if (!activeSecret || !activeCapabilities) {
|
|
1520
|
+
state.socket.close(CLOSE_AUTH_FAILED, "Invalid state.");
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
const idempotencyRaw = payload.idempotencyKey;
|
|
1524
|
+
const idempotencyKey = typeof idempotencyRaw === "string" && idempotencyRaw.length > 0 && idempotencyRaw.length <= 256 ? idempotencyRaw : void 0;
|
|
1525
|
+
const idempotencyCacheKey = idempotencyKey ? `${state.id}:${idempotencyKey}` : void 0;
|
|
1526
|
+
if (idempotencyCacheKey) {
|
|
1527
|
+
this.pruneIdempotencyCache(Date.now());
|
|
1528
|
+
const cached = this.idempotencyCache.get(idempotencyCacheKey);
|
|
1529
|
+
if (cached && cached.expires > Date.now()) {
|
|
1530
|
+
const replay = cached.result.ok ? { ok: true, requestId, ts: Date.now(), data: cached.result.data } : { ok: false, requestId, ts: Date.now(), error: cached.result.error };
|
|
1531
|
+
this.logger.info("Bridge action idempotent replay.", {
|
|
1532
|
+
connectionId: state.id,
|
|
1533
|
+
requestId,
|
|
1534
|
+
action: payload.name
|
|
1535
|
+
});
|
|
1536
|
+
this.safeSend(
|
|
1537
|
+
state.socket,
|
|
1538
|
+
stringifyEnvelope(
|
|
1539
|
+
makeEnvelope(replay.ok ? "action.result" : "action.error", replay, { requestId })
|
|
1540
|
+
)
|
|
1541
|
+
);
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
const started = Date.now();
|
|
1546
|
+
let result;
|
|
1547
|
+
try {
|
|
1548
|
+
result = await this.actionSemaphore.run(
|
|
1549
|
+
() => this.config.onActionRequest(
|
|
1550
|
+
{
|
|
1551
|
+
id: state.id,
|
|
1552
|
+
...state.appName ? { appName: state.appName } : {},
|
|
1553
|
+
secret: activeSecret,
|
|
1554
|
+
capabilities: activeCapabilities
|
|
1555
|
+
},
|
|
1556
|
+
payload.name,
|
|
1557
|
+
payload.data,
|
|
1558
|
+
requestId
|
|
1559
|
+
)
|
|
1560
|
+
);
|
|
1561
|
+
} catch (error) {
|
|
1562
|
+
const message = error instanceof Error ? error.message : "Action queue saturated.";
|
|
1563
|
+
result = {
|
|
1564
|
+
ok: false,
|
|
1565
|
+
requestId,
|
|
1566
|
+
ts: Date.now(),
|
|
1567
|
+
error: {
|
|
1568
|
+
code: "SERVICE_UNAVAILABLE",
|
|
1569
|
+
message,
|
|
1570
|
+
details: { retryable: true, reason: "action_queue" }
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
if (idempotencyCacheKey) {
|
|
1575
|
+
this.idempotencyCache.set(idempotencyCacheKey, {
|
|
1576
|
+
result,
|
|
1577
|
+
expires: Date.now() + this.idempotencyTtlMs
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
this.logger.info("Bridge action completed.", {
|
|
1581
|
+
connectionId: state.id,
|
|
1582
|
+
requestId,
|
|
1583
|
+
action: payload.name,
|
|
1584
|
+
durationMs: Date.now() - started,
|
|
1585
|
+
ok: result.ok,
|
|
1586
|
+
appName: state.appName
|
|
1587
|
+
});
|
|
1149
1588
|
this.safeSend(
|
|
1150
1589
|
state.socket,
|
|
1151
1590
|
stringifyEnvelope(makeEnvelope(result.ok ? "action.result" : "action.error", result, { requestId }))
|
|
@@ -1176,6 +1615,28 @@ var BridgeTransportServer = class {
|
|
|
1176
1615
|
socket.send(payload);
|
|
1177
1616
|
}
|
|
1178
1617
|
}
|
|
1618
|
+
isAuthRateLimited(remoteAddress) {
|
|
1619
|
+
const windowMs = 6e4;
|
|
1620
|
+
const limit = 40;
|
|
1621
|
+
const now = Date.now();
|
|
1622
|
+
let bucket = this.authBuckets.get(remoteAddress);
|
|
1623
|
+
if (!bucket || now > bucket.resetAt) {
|
|
1624
|
+
bucket = { count: 0, resetAt: now + windowMs };
|
|
1625
|
+
this.authBuckets.set(remoteAddress, bucket);
|
|
1626
|
+
}
|
|
1627
|
+
bucket.count += 1;
|
|
1628
|
+
return bucket.count > limit;
|
|
1629
|
+
}
|
|
1630
|
+
pruneIdempotencyCache(now) {
|
|
1631
|
+
if (this.idempotencyCache.size < 200) {
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
for (const [key, entry] of this.idempotencyCache.entries()) {
|
|
1635
|
+
if (entry.expires <= now) {
|
|
1636
|
+
this.idempotencyCache.delete(key);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1179
1640
|
};
|
|
1180
1641
|
function authenticateSecret(payload, secrets, resolver) {
|
|
1181
1642
|
if (!payload.secret) {
|
|
@@ -1289,6 +1750,15 @@ function assertBotBridgeOptions(options) {
|
|
|
1289
1750
|
if (options.server.maxPayloadBytes !== void 0) {
|
|
1290
1751
|
assertPositiveNumber("server.maxPayloadBytes", options.server.maxPayloadBytes);
|
|
1291
1752
|
}
|
|
1753
|
+
if (options.server.maxConnections !== void 0) {
|
|
1754
|
+
assertPositiveNumber("server.maxConnections", options.server.maxConnections);
|
|
1755
|
+
}
|
|
1756
|
+
if (options.server.maxConcurrentActions !== void 0) {
|
|
1757
|
+
assertPositiveNumber("server.maxConcurrentActions", options.server.maxConcurrentActions);
|
|
1758
|
+
}
|
|
1759
|
+
if (options.server.actionQueueTimeoutMs !== void 0) {
|
|
1760
|
+
assertPositiveNumber("server.actionQueueTimeoutMs", options.server.actionQueueTimeoutMs);
|
|
1761
|
+
}
|
|
1292
1762
|
if (!Array.isArray(options.server.secrets) || options.server.secrets.length === 0) {
|
|
1293
1763
|
throw new Error("server.secrets must contain at least one secret.");
|
|
1294
1764
|
}
|
|
@@ -1491,6 +1961,7 @@ var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
|
|
|
1491
1961
|
function connectBotBridge(options) {
|
|
1492
1962
|
assertAppBridgeOptions(options);
|
|
1493
1963
|
const logger = withLogger(options.logger);
|
|
1964
|
+
const metrics = options.metrics;
|
|
1494
1965
|
const reconnectEnabled = options.reconnect?.enabled ?? true;
|
|
1495
1966
|
const initialDelayMs = options.reconnect?.initialDelayMs ?? 500;
|
|
1496
1967
|
const maxDelayMs = options.reconnect?.maxDelayMs ?? 1e4;
|
|
@@ -1763,6 +2234,7 @@ function connectBotBridge(options) {
|
|
|
1763
2234
|
}
|
|
1764
2235
|
const requestId = sendOptions?.requestId ?? createRequestId();
|
|
1765
2236
|
const timeoutMs = sendOptions?.timeoutMs ?? requestTimeoutMs;
|
|
2237
|
+
const started = Date.now();
|
|
1766
2238
|
const promise = new Promise((resolve, reject) => {
|
|
1767
2239
|
const timer = setTimeout(() => {
|
|
1768
2240
|
pendingRequests.delete(requestId);
|
|
@@ -1780,16 +2252,32 @@ function connectBotBridge(options) {
|
|
|
1780
2252
|
"action.request",
|
|
1781
2253
|
{
|
|
1782
2254
|
name,
|
|
1783
|
-
data: payload
|
|
2255
|
+
data: payload,
|
|
2256
|
+
...sendOptions?.idempotencyKey ? { idempotencyKey: sendOptions.idempotencyKey } : {}
|
|
1784
2257
|
},
|
|
1785
2258
|
{ requestId }
|
|
1786
2259
|
)
|
|
1787
2260
|
)
|
|
1788
2261
|
);
|
|
1789
2262
|
try {
|
|
1790
|
-
|
|
2263
|
+
const result = await promise;
|
|
2264
|
+
metrics?.onActionComplete?.({
|
|
2265
|
+
name,
|
|
2266
|
+
requestId,
|
|
2267
|
+
durationMs: Date.now() - started,
|
|
2268
|
+
ok: result.ok,
|
|
2269
|
+
...!result.ok ? { errorCode: result.error.code } : {}
|
|
2270
|
+
});
|
|
2271
|
+
return result;
|
|
1791
2272
|
} catch (error) {
|
|
1792
2273
|
const code = error instanceof AppRequestError ? error.code : !socket || socket.readyState !== 1 ? "DISCONNECTED" : "TIMEOUT";
|
|
2274
|
+
metrics?.onActionComplete?.({
|
|
2275
|
+
name,
|
|
2276
|
+
requestId,
|
|
2277
|
+
durationMs: Date.now() - started,
|
|
2278
|
+
ok: false,
|
|
2279
|
+
errorCode: code
|
|
2280
|
+
});
|
|
1793
2281
|
return {
|
|
1794
2282
|
ok: false,
|
|
1795
2283
|
requestId,
|
|
@@ -1807,7 +2295,14 @@ function connectBotBridge(options) {
|
|
|
1807
2295
|
deleteMessage: (payload, sendOptions) => invokeAction("deleteMessage", payload, sendOptions),
|
|
1808
2296
|
replyToInteraction: (payload, sendOptions) => invokeAction("replyToInteraction", payload, sendOptions),
|
|
1809
2297
|
deferInteraction: (payload, sendOptions) => invokeAction("deferInteraction", payload, sendOptions),
|
|
2298
|
+
deferUpdateInteraction: (payload, sendOptions) => invokeAction("deferUpdateInteraction", payload, sendOptions),
|
|
1810
2299
|
followUpInteraction: (payload, sendOptions) => invokeAction("followUpInteraction", payload, sendOptions),
|
|
2300
|
+
editInteractionReply: (payload, sendOptions) => invokeAction("editInteractionReply", payload, sendOptions),
|
|
2301
|
+
deleteInteractionReply: (payload, sendOptions) => invokeAction("deleteInteractionReply", payload, sendOptions),
|
|
2302
|
+
updateInteraction: (payload, sendOptions) => invokeAction("updateInteraction", payload, sendOptions),
|
|
2303
|
+
showModal: (payload, sendOptions) => invokeAction("showModal", payload, sendOptions),
|
|
2304
|
+
fetchMessage: (payload, sendOptions) => invokeAction("fetchMessage", payload, sendOptions),
|
|
2305
|
+
fetchMember: (payload, sendOptions) => invokeAction("fetchMember", payload, sendOptions),
|
|
1811
2306
|
banMember: (payload, sendOptions) => invokeAction("banMember", payload, sendOptions),
|
|
1812
2307
|
kickMember: (payload, sendOptions) => invokeAction("kickMember", payload, sendOptions),
|
|
1813
2308
|
addMemberRole: (payload, sendOptions) => invokeAction("addMemberRole", payload, sendOptions),
|