@minesa-org/mini-interaction 0.1.12 → 0.1.13
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.
|
@@ -14,6 +14,7 @@ export type MiniInteractionOptions = {
|
|
|
14
14
|
utilsDirectory?: string | false;
|
|
15
15
|
fetchImplementation?: typeof fetch;
|
|
16
16
|
verifyKeyImplementation?: VerifyKeyFunction;
|
|
17
|
+
timeoutConfig?: InteractionTimeoutConfig;
|
|
17
18
|
};
|
|
18
19
|
/** Payload structure for role connection metadata registration. */
|
|
19
20
|
export type RoleConnectionMetadataField = {
|
|
@@ -37,6 +38,15 @@ export type MiniInteractionHandlerResult = {
|
|
|
37
38
|
error: string;
|
|
38
39
|
};
|
|
39
40
|
};
|
|
41
|
+
/** Configuration for interaction timeout handling. */
|
|
42
|
+
export type InteractionTimeoutConfig = {
|
|
43
|
+
/** Maximum time in milliseconds to wait for initial response (default: 2800ms) */
|
|
44
|
+
initialResponseTimeout?: number;
|
|
45
|
+
/** Whether to enable timeout warnings (default: true) */
|
|
46
|
+
enableTimeoutWarnings?: boolean;
|
|
47
|
+
/** Whether to force deferReply for slow operations (default: true) */
|
|
48
|
+
autoDeferSlowOperations?: boolean;
|
|
49
|
+
};
|
|
40
50
|
/** Handler signature invoked for Discord button interactions. */
|
|
41
51
|
export type MiniInteractionButtonHandler = (interaction: ButtonInteraction) => Promise<APIInteractionResponse | void> | APIInteractionResponse | void;
|
|
42
52
|
/** Handler signature invoked for Discord string select menu interactions. */
|
|
@@ -147,6 +157,7 @@ export declare class MiniInteraction {
|
|
|
147
157
|
private readonly commandsDirectory;
|
|
148
158
|
private readonly componentsDirectory;
|
|
149
159
|
readonly utilsDirectory: string | null;
|
|
160
|
+
private readonly timeoutConfig;
|
|
150
161
|
private readonly commands;
|
|
151
162
|
private readonly componentHandlers;
|
|
152
163
|
private readonly modalHandlers;
|
|
@@ -160,7 +171,7 @@ export declare class MiniInteraction {
|
|
|
160
171
|
/**
|
|
161
172
|
* Creates a new MiniInteraction client with optional command auto-loading and custom runtime hooks.
|
|
162
173
|
*/
|
|
163
|
-
constructor({ applicationId, publicKey, commandsDirectory, componentsDirectory, utilsDirectory, fetchImplementation, verifyKeyImplementation, }: MiniInteractionOptions);
|
|
174
|
+
constructor({ applicationId, publicKey, commandsDirectory, componentsDirectory, utilsDirectory, fetchImplementation, verifyKeyImplementation, timeoutConfig, }: MiniInteractionOptions);
|
|
164
175
|
private normalizeCommandData;
|
|
165
176
|
private registerCommand;
|
|
166
177
|
/**
|
|
@@ -260,8 +271,8 @@ export declare class MiniInteraction {
|
|
|
260
271
|
* The following placeholders are available in the HTML file:
|
|
261
272
|
* - `{{username}}`, `{{discriminator}}`, `{{user_id}}`, `{{user_tag}}`
|
|
262
273
|
* - `{{access_token}}`, `{{refresh_token}}`, `{{token_type}}`, `{{scope}}`, `{{expires_at}}`
|
|
263
|
-
|
|
264
|
-
|
|
274
|
+
* - `{{state}}`
|
|
275
|
+
*/
|
|
265
276
|
connectedOAuthPage(filePath: string): DiscordOAuthCallbackTemplates["success"];
|
|
266
277
|
/**
|
|
267
278
|
* Loads an HTML file and returns an error template that can be reused for all failure cases.
|
|
@@ -277,7 +288,7 @@ export declare class MiniInteraction {
|
|
|
277
288
|
private loadHtmlTemplate;
|
|
278
289
|
/**
|
|
279
290
|
* Replaces placeholder tokens in a template with escaped HTML values.
|
|
280
|
-
|
|
291
|
+
*/
|
|
281
292
|
private renderHtmlTemplate;
|
|
282
293
|
/**
|
|
283
294
|
* Normalizes placeholder tokens to the bare key the HTML renderer expects.
|
|
@@ -30,6 +30,7 @@ export class MiniInteraction {
|
|
|
30
30
|
commandsDirectory;
|
|
31
31
|
componentsDirectory;
|
|
32
32
|
utilsDirectory;
|
|
33
|
+
timeoutConfig;
|
|
33
34
|
commands = new Map();
|
|
34
35
|
componentHandlers = new Map();
|
|
35
36
|
modalHandlers = new Map();
|
|
@@ -43,7 +44,7 @@ export class MiniInteraction {
|
|
|
43
44
|
/**
|
|
44
45
|
* Creates a new MiniInteraction client with optional command auto-loading and custom runtime hooks.
|
|
45
46
|
*/
|
|
46
|
-
constructor({ applicationId, publicKey, commandsDirectory, componentsDirectory, utilsDirectory, fetchImplementation, verifyKeyImplementation, }) {
|
|
47
|
+
constructor({ applicationId, publicKey, commandsDirectory, componentsDirectory, utilsDirectory, fetchImplementation, verifyKeyImplementation, timeoutConfig, }) {
|
|
47
48
|
if (!applicationId) {
|
|
48
49
|
throw new Error("[MiniInteraction] applicationId is required");
|
|
49
50
|
}
|
|
@@ -70,6 +71,12 @@ export class MiniInteraction {
|
|
|
70
71
|
utilsDirectory === false
|
|
71
72
|
? null
|
|
72
73
|
: this.resolveUtilsDirectory(utilsDirectory);
|
|
74
|
+
this.timeoutConfig = {
|
|
75
|
+
initialResponseTimeout: 2800, // Leave 200ms buffer before Discord's 3s limit
|
|
76
|
+
enableTimeoutWarnings: true,
|
|
77
|
+
autoDeferSlowOperations: true,
|
|
78
|
+
...timeoutConfig,
|
|
79
|
+
};
|
|
73
80
|
}
|
|
74
81
|
normalizeCommandData(data) {
|
|
75
82
|
if (typeof data === "object" && data !== null) {
|
|
@@ -94,12 +101,14 @@ export class MiniInteraction {
|
|
|
94
101
|
data: normalizedData,
|
|
95
102
|
};
|
|
96
103
|
this.commands.set(commandName, normalizedCommand);
|
|
97
|
-
if (normalizedCommand.components &&
|
|
104
|
+
if (normalizedCommand.components &&
|
|
105
|
+
Array.isArray(normalizedCommand.components)) {
|
|
98
106
|
for (const component of normalizedCommand.components) {
|
|
99
107
|
this.useComponent(component);
|
|
100
108
|
}
|
|
101
109
|
}
|
|
102
|
-
if (normalizedCommand.modals &&
|
|
110
|
+
if (normalizedCommand.modals &&
|
|
111
|
+
Array.isArray(normalizedCommand.modals)) {
|
|
103
112
|
for (const modal of normalizedCommand.modals) {
|
|
104
113
|
this.useModal(modal);
|
|
105
114
|
}
|
|
@@ -354,6 +363,7 @@ export class MiniInteraction {
|
|
|
354
363
|
* @param request - The request payload containing headers and body data.
|
|
355
364
|
*/
|
|
356
365
|
async handleRequest(request) {
|
|
366
|
+
const requestStartTime = Date.now();
|
|
357
367
|
const { body, signature, timestamp } = request;
|
|
358
368
|
if (!signature || !timestamp) {
|
|
359
369
|
return {
|
|
@@ -398,6 +408,15 @@ export class MiniInteraction {
|
|
|
398
408
|
if (interaction.type === InteractionType.ModalSubmit) {
|
|
399
409
|
return this.handleModalSubmit(interaction);
|
|
400
410
|
}
|
|
411
|
+
// Check total processing time
|
|
412
|
+
const totalProcessingTime = Date.now() - requestStartTime;
|
|
413
|
+
if (this.timeoutConfig.enableTimeoutWarnings &&
|
|
414
|
+
totalProcessingTime >
|
|
415
|
+
this.timeoutConfig.initialResponseTimeout * 0.9) {
|
|
416
|
+
console.warn(`[MiniInteraction] WARNING: Interaction processing took ${totalProcessingTime}ms ` +
|
|
417
|
+
`(${Math.round((totalProcessingTime / 3000) * 100)}% of Discord's 3-second limit). ` +
|
|
418
|
+
`Consider optimizing or using deferReply() for slow operations.`);
|
|
419
|
+
}
|
|
401
420
|
return {
|
|
402
421
|
status: 400,
|
|
403
422
|
body: {
|
|
@@ -511,8 +530,8 @@ export class MiniInteraction {
|
|
|
511
530
|
* The following placeholders are available in the HTML file:
|
|
512
531
|
* - `{{username}}`, `{{discriminator}}`, `{{user_id}}`, `{{user_tag}}`
|
|
513
532
|
* - `{{access_token}}`, `{{refresh_token}}`, `{{token_type}}`, `{{scope}}`, `{{expires_at}}`
|
|
514
|
-
|
|
515
|
-
|
|
533
|
+
* - `{{state}}`
|
|
534
|
+
*/
|
|
516
535
|
connectedOAuthPage(filePath) {
|
|
517
536
|
const template = this.loadHtmlTemplate(filePath);
|
|
518
537
|
return ({ user, tokens, state }) => {
|
|
@@ -569,7 +588,7 @@ export class MiniInteraction {
|
|
|
569
588
|
}
|
|
570
589
|
/**
|
|
571
590
|
* Replaces placeholder tokens in a template with escaped HTML values.
|
|
572
|
-
|
|
591
|
+
*/
|
|
573
592
|
renderHtmlTemplate(template, values, options) {
|
|
574
593
|
const rawKeys = options?.rawKeys instanceof Set
|
|
575
594
|
? options.rawKeys
|
|
@@ -666,7 +685,9 @@ export class MiniInteraction {
|
|
|
666
685
|
const location = typeof options.successRedirect === "function"
|
|
667
686
|
? options.successRedirect(authorizeContext)
|
|
668
687
|
: options.successRedirect;
|
|
669
|
-
if (location &&
|
|
688
|
+
if (location &&
|
|
689
|
+
!response.headersSent &&
|
|
690
|
+
!response.writableEnded) {
|
|
670
691
|
response.statusCode = 302;
|
|
671
692
|
response.setHeader("location", location);
|
|
672
693
|
response.end();
|
|
@@ -1057,27 +1078,32 @@ export class MiniInteraction {
|
|
|
1057
1078
|
}
|
|
1058
1079
|
try {
|
|
1059
1080
|
const interactionWithHelpers = createMessageComponentInteraction(interaction);
|
|
1060
|
-
|
|
1061
|
-
const
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1081
|
+
// Wrap component handler with timeout
|
|
1082
|
+
const timeoutWrapper = createTimeoutWrapper(async () => {
|
|
1083
|
+
const response = await handler(interactionWithHelpers);
|
|
1084
|
+
const resolvedResponse = response ?? interactionWithHelpers.getResponse();
|
|
1085
|
+
if (!resolvedResponse) {
|
|
1086
|
+
throw new Error(`Component "${customId}" did not return a response. ` +
|
|
1087
|
+
"Return an APIInteractionResponse to acknowledge the interaction.");
|
|
1088
|
+
}
|
|
1089
|
+
return resolvedResponse;
|
|
1090
|
+
}, this.timeoutConfig.initialResponseTimeout, `Component "${customId}"`, this.timeoutConfig.enableTimeoutWarnings);
|
|
1091
|
+
const resolvedResponse = await timeoutWrapper();
|
|
1071
1092
|
return {
|
|
1072
1093
|
status: 200,
|
|
1073
1094
|
body: resolvedResponse,
|
|
1074
1095
|
};
|
|
1075
1096
|
}
|
|
1076
1097
|
catch (error) {
|
|
1098
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1099
|
+
if (errorMessage.includes("Handler timeout")) {
|
|
1100
|
+
console.error(`[MiniInteraction] CRITICAL: Component "${customId}" timed out. ` +
|
|
1101
|
+
`This will result in "didn't respond in time" errors for users.`);
|
|
1102
|
+
}
|
|
1077
1103
|
return {
|
|
1078
1104
|
status: 500,
|
|
1079
1105
|
body: {
|
|
1080
|
-
error: `[MiniInteraction] Component "${customId}" failed: ${
|
|
1106
|
+
error: `[MiniInteraction] Component "${customId}" failed: ${errorMessage}`,
|
|
1081
1107
|
},
|
|
1082
1108
|
};
|
|
1083
1109
|
}
|
|
@@ -1107,27 +1133,32 @@ export class MiniInteraction {
|
|
|
1107
1133
|
}
|
|
1108
1134
|
try {
|
|
1109
1135
|
const interactionWithHelpers = createModalSubmitInteraction(interaction);
|
|
1110
|
-
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1136
|
+
// Wrap modal handler with timeout
|
|
1137
|
+
const timeoutWrapper = createTimeoutWrapper(async () => {
|
|
1138
|
+
const response = await handler(interactionWithHelpers);
|
|
1139
|
+
const resolvedResponse = response ?? interactionWithHelpers.getResponse();
|
|
1140
|
+
if (!resolvedResponse) {
|
|
1141
|
+
throw new Error(`Modal "${customId}" did not return a response. ` +
|
|
1142
|
+
"Return an APIInteractionResponse to acknowledge the interaction.");
|
|
1143
|
+
}
|
|
1144
|
+
return resolvedResponse;
|
|
1145
|
+
}, this.timeoutConfig.initialResponseTimeout, `Modal "${customId}"`, this.timeoutConfig.enableTimeoutWarnings);
|
|
1146
|
+
const resolvedResponse = await timeoutWrapper();
|
|
1121
1147
|
return {
|
|
1122
1148
|
status: 200,
|
|
1123
1149
|
body: resolvedResponse,
|
|
1124
1150
|
};
|
|
1125
1151
|
}
|
|
1126
1152
|
catch (error) {
|
|
1153
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1154
|
+
if (errorMessage.includes("Handler timeout")) {
|
|
1155
|
+
console.error(`[MiniInteraction] CRITICAL: Modal "${customId}" timed out. ` +
|
|
1156
|
+
`This will result in "didn't respond in time" errors for users.`);
|
|
1157
|
+
}
|
|
1127
1158
|
return {
|
|
1128
1159
|
status: 500,
|
|
1129
1160
|
body: {
|
|
1130
|
-
error: `[MiniInteraction] Modal "${customId}" failed: ${
|
|
1161
|
+
error: `[MiniInteraction] Modal "${customId}" failed: ${errorMessage}`,
|
|
1131
1162
|
},
|
|
1132
1163
|
};
|
|
1133
1164
|
}
|
|
@@ -1159,41 +1190,50 @@ export class MiniInteraction {
|
|
|
1159
1190
|
try {
|
|
1160
1191
|
let response;
|
|
1161
1192
|
let resolvedResponse = null;
|
|
1162
|
-
//
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
response
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1193
|
+
// Create a timeout wrapper for the command handler
|
|
1194
|
+
const timeoutWrapper = createTimeoutWrapper(async () => {
|
|
1195
|
+
// Check if it's a chat input (slash) command
|
|
1196
|
+
if (commandInteraction.data.type ===
|
|
1197
|
+
ApplicationCommandType.ChatInput) {
|
|
1198
|
+
const interactionWithHelpers = createCommandInteraction(commandInteraction);
|
|
1199
|
+
response = await command.handler(interactionWithHelpers);
|
|
1200
|
+
resolvedResponse =
|
|
1201
|
+
response ?? interactionWithHelpers.getResponse();
|
|
1202
|
+
}
|
|
1203
|
+
else if (commandInteraction.data.type ===
|
|
1204
|
+
ApplicationCommandType.User) {
|
|
1205
|
+
// User context menu command
|
|
1206
|
+
const interactionWithHelpers = createUserContextMenuInteraction(commandInteraction);
|
|
1207
|
+
response = await command.handler(interactionWithHelpers);
|
|
1208
|
+
resolvedResponse =
|
|
1209
|
+
response ?? interactionWithHelpers.getResponse();
|
|
1210
|
+
}
|
|
1211
|
+
else if (commandInteraction.data.type ===
|
|
1212
|
+
ApplicationCommandType.PrimaryEntryPoint) {
|
|
1213
|
+
const interactionWithHelpers = createAppCommandInteraction(commandInteraction);
|
|
1214
|
+
response = await command.handler(interactionWithHelpers);
|
|
1215
|
+
resolvedResponse =
|
|
1216
|
+
response ?? interactionWithHelpers.getResponse();
|
|
1217
|
+
}
|
|
1218
|
+
else if (commandInteraction.data.type ===
|
|
1219
|
+
ApplicationCommandType.Message) {
|
|
1220
|
+
// Message context menu command
|
|
1221
|
+
const interactionWithHelpers = createMessageContextMenuInteraction(commandInteraction);
|
|
1222
|
+
response = await command.handler(interactionWithHelpers);
|
|
1223
|
+
resolvedResponse =
|
|
1224
|
+
response ?? interactionWithHelpers.getResponse();
|
|
1225
|
+
}
|
|
1226
|
+
else {
|
|
1227
|
+
// Unknown command type
|
|
1228
|
+
response = await command.handler(commandInteraction);
|
|
1229
|
+
resolvedResponse = response ?? null;
|
|
1230
|
+
}
|
|
1231
|
+
}, this.timeoutConfig.initialResponseTimeout, `Command "${commandName}"`, this.timeoutConfig.enableTimeoutWarnings);
|
|
1232
|
+
await timeoutWrapper();
|
|
1196
1233
|
if (!resolvedResponse) {
|
|
1234
|
+
console.error(`[MiniInteraction] Command "${commandName}" did not return a response. ` +
|
|
1235
|
+
"This indicates the handler completed but no response was generated. " +
|
|
1236
|
+
"Check that deferReply(), reply(), showModal(), or a direct response is returned.");
|
|
1197
1237
|
return {
|
|
1198
1238
|
status: 500,
|
|
1199
1239
|
body: {
|
|
@@ -1209,10 +1249,18 @@ export class MiniInteraction {
|
|
|
1209
1249
|
};
|
|
1210
1250
|
}
|
|
1211
1251
|
catch (error) {
|
|
1252
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1253
|
+
// Check if this was a timeout error
|
|
1254
|
+
if (errorMessage.includes("Handler timeout")) {
|
|
1255
|
+
console.error(`[MiniInteraction] CRITICAL: Command "${commandName}" timed out before responding to Discord. ` +
|
|
1256
|
+
`This will result in "didn't respond in time" errors for users. ` +
|
|
1257
|
+
`Handler took longer than ${this.timeoutConfig.initialResponseTimeout}ms to complete. ` +
|
|
1258
|
+
`Consider using deferReply() for operations that take more than 3 seconds.`);
|
|
1259
|
+
}
|
|
1212
1260
|
return {
|
|
1213
1261
|
status: 500,
|
|
1214
1262
|
body: {
|
|
1215
|
-
error: `[MiniInteraction] Command "${commandName}" failed: ${
|
|
1263
|
+
error: `[MiniInteraction] Command "${commandName}" failed: ${errorMessage}`,
|
|
1216
1264
|
},
|
|
1217
1265
|
};
|
|
1218
1266
|
}
|
|
@@ -1382,3 +1430,45 @@ function resolveOAuthConfig(provided) {
|
|
|
1382
1430
|
redirectUri,
|
|
1383
1431
|
};
|
|
1384
1432
|
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Wraps a handler function with timeout detection and error handling.
|
|
1435
|
+
*/
|
|
1436
|
+
function createTimeoutWrapper(handler, timeoutMs, handlerName, enableWarnings = true) {
|
|
1437
|
+
return async (...args) => {
|
|
1438
|
+
const startTime = Date.now();
|
|
1439
|
+
let timeoutId;
|
|
1440
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1441
|
+
timeoutId = setTimeout(() => {
|
|
1442
|
+
const elapsed = Date.now() - startTime;
|
|
1443
|
+
console.error(`[MiniInteraction] ${handlerName} timed out after ${elapsed}ms (limit: ${timeoutMs}ms)`);
|
|
1444
|
+
reject(new Error(`Handler timeout: ${handlerName} exceeded ${timeoutMs}ms limit`));
|
|
1445
|
+
}, timeoutMs);
|
|
1446
|
+
});
|
|
1447
|
+
try {
|
|
1448
|
+
const result = await Promise.race([
|
|
1449
|
+
Promise.resolve(handler(...args)),
|
|
1450
|
+
timeoutPromise,
|
|
1451
|
+
]);
|
|
1452
|
+
if (timeoutId) {
|
|
1453
|
+
clearTimeout(timeoutId);
|
|
1454
|
+
}
|
|
1455
|
+
const elapsed = Date.now() - startTime;
|
|
1456
|
+
if (enableWarnings && elapsed > timeoutMs * 0.8) {
|
|
1457
|
+
console.warn(`[MiniInteraction] ${handlerName} completed in ${elapsed}ms (${Math.round((elapsed / timeoutMs) * 100)}% of timeout limit)`);
|
|
1458
|
+
}
|
|
1459
|
+
return result;
|
|
1460
|
+
}
|
|
1461
|
+
catch (error) {
|
|
1462
|
+
if (timeoutId) {
|
|
1463
|
+
clearTimeout(timeoutId);
|
|
1464
|
+
}
|
|
1465
|
+
// Re-throw the error with additional context
|
|
1466
|
+
if (error instanceof Error &&
|
|
1467
|
+
error.message.includes("Handler timeout")) {
|
|
1468
|
+
throw error;
|
|
1469
|
+
}
|
|
1470
|
+
console.error(`[MiniInteraction] ${handlerName} failed:`, error);
|
|
1471
|
+
throw error;
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
@@ -149,6 +149,7 @@ export interface CommandInteraction extends Omit<APIChatInputApplicationCommandI
|
|
|
149
149
|
showModal(data: APIModalInteractionResponseCallbackData | {
|
|
150
150
|
toJSON(): APIModalInteractionResponseCallbackData;
|
|
151
151
|
}): APIModalInteractionResponse;
|
|
152
|
+
withTimeoutProtection<T>(operation: () => Promise<T>, deferOptions?: DeferReplyOptions): Promise<T>;
|
|
152
153
|
}
|
|
153
154
|
/**
|
|
154
155
|
* Wraps a raw application command interaction with helper methods and option resolvers.
|
|
@@ -398,6 +398,40 @@ export function createCommandInteraction(interaction) {
|
|
|
398
398
|
data: resolvedData,
|
|
399
399
|
});
|
|
400
400
|
},
|
|
401
|
+
/**
|
|
402
|
+
* Creates a delayed response wrapper that automatically defers if the operation takes too long.
|
|
403
|
+
* Use this for operations that might exceed Discord's 3-second limit.
|
|
404
|
+
*
|
|
405
|
+
* @param operation - The async operation to perform
|
|
406
|
+
* @param deferOptions - Options for automatic deferral
|
|
407
|
+
*/
|
|
408
|
+
async withTimeoutProtection(operation, deferOptions) {
|
|
409
|
+
const startTime = Date.now();
|
|
410
|
+
let deferred = false;
|
|
411
|
+
// Set up a timer to auto-defer after 2.5 seconds
|
|
412
|
+
const deferTimer = setTimeout(async () => {
|
|
413
|
+
if (!deferred) {
|
|
414
|
+
console.warn("[MiniInteraction] Auto-deferring interaction due to slow operation. " +
|
|
415
|
+
"Consider using deferReply() explicitly for better user experience.");
|
|
416
|
+
this.deferReply(deferOptions);
|
|
417
|
+
deferred = true;
|
|
418
|
+
}
|
|
419
|
+
}, 2500);
|
|
420
|
+
try {
|
|
421
|
+
const result = await operation();
|
|
422
|
+
clearTimeout(deferTimer);
|
|
423
|
+
const elapsed = Date.now() - startTime;
|
|
424
|
+
if (elapsed > 2000 && !deferred) {
|
|
425
|
+
console.warn(`[MiniInteraction] Operation completed in ${elapsed}ms. ` +
|
|
426
|
+
"Consider using deferReply() for operations > 2 seconds.");
|
|
427
|
+
}
|
|
428
|
+
return result;
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
clearTimeout(deferTimer);
|
|
432
|
+
throw error;
|
|
433
|
+
}
|
|
434
|
+
},
|
|
401
435
|
};
|
|
402
436
|
return commandInteraction;
|
|
403
437
|
}
|
package/package.json
CHANGED