@minesa-org/mini-interaction 0.4.7 → 0.4.9

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.
@@ -80,6 +80,8 @@ export declare class MiniInteraction {
80
80
  private normalizeModuleExports;
81
81
  private normalizeExportValue;
82
82
  private walkFiles;
83
+ private getDefaultInitialResponse;
84
+ private isDeferredResponse;
83
85
  private isImportableModule;
84
86
  private isInteractionCommand;
85
87
  private getCommandName;
@@ -31,6 +31,14 @@ export class MiniInteraction {
31
31
  }
32
32
  createNodeHandler() {
33
33
  return async (req, res) => {
34
+ let responseSent = false;
35
+ const commitInitialResponse = (response) => {
36
+ if (responseSent)
37
+ return false;
38
+ this.sendJson(res, 200, response);
39
+ responseSent = true;
40
+ return true;
41
+ };
34
42
  try {
35
43
  const body = await this.readRawBody(req);
36
44
  const signature = this.getHeader(req.headers, "x-signature-ed25519");
@@ -58,17 +66,20 @@ export class MiniInteraction {
58
66
  this.sendJson(res, 200, { type: InteractionResponseType.Pong });
59
67
  return;
60
68
  }
61
- const response = await this.dispatch(interaction);
62
- this.sendJson(res, 200, response ?? {
63
- type: InteractionResponseType.DeferredChannelMessageWithSource,
64
- });
69
+ const response = await this.dispatch(interaction, commitInitialResponse);
70
+ if (!responseSent) {
71
+ this.sendJson(res, 200, response ?? this.getDefaultInitialResponse(interaction));
72
+ responseSent = true;
73
+ }
65
74
  }
66
75
  catch (error) {
67
76
  const message = error instanceof Error ? error.message : "[MiniInteraction] Unknown error";
68
77
  if (this.options.debug) {
69
78
  console.error("[MiniInteraction] createNodeHandler failed", error);
70
79
  }
71
- this.sendJson(res, 500, { error: message });
80
+ if (!responseSent) {
81
+ this.sendJson(res, 500, { error: message });
82
+ }
72
83
  }
73
84
  };
74
85
  }
@@ -156,29 +167,29 @@ export class MiniInteraction {
156
167
  }
157
168
  };
158
169
  }
159
- async dispatch(interaction) {
170
+ async dispatch(interaction, commitInitialResponse) {
160
171
  const modules = await this.loadModules();
161
172
  if (interaction.type === InteractionType.ApplicationCommand) {
162
173
  const command = modules.commands.find((candidate) => this.getCommandName(candidate) === interaction.data.name);
163
174
  if (!command)
164
175
  return undefined;
165
- return this.executeCommandHandler(command.handler, interaction);
176
+ return this.executeCommandHandler(command.handler, interaction, commitInitialResponse);
166
177
  }
167
178
  if (interaction.type === InteractionType.MessageComponent) {
168
179
  const component = modules.components.find((candidate) => candidate.customId === interaction.data.custom_id);
169
180
  if (!component)
170
181
  return undefined;
171
- return this.executeComponentHandler(component.handler, interaction);
182
+ return this.executeComponentHandler(component.handler, interaction, commitInitialResponse);
172
183
  }
173
184
  if (interaction.type === InteractionType.ModalSubmit) {
174
185
  const modal = modules.modals.find((candidate) => candidate.customId === interaction.data.custom_id);
175
186
  if (!modal)
176
187
  return undefined;
177
- return this.executeModalHandler(modal.handler, interaction);
188
+ return this.executeModalHandler(modal.handler, interaction, commitInitialResponse);
178
189
  }
179
190
  return undefined;
180
191
  }
181
- async executeCommandHandler(handler, interaction) {
192
+ async executeCommandHandler(handler, interaction, commitInitialResponse) {
182
193
  return this.runWithResponseLifecycle(interaction, async (helpers) => {
183
194
  switch (interaction.data.type) {
184
195
  case ApplicationCommandType.ChatInput:
@@ -193,17 +204,19 @@ export class MiniInteraction {
193
204
  }
194
205
  throw new Error(`[MiniInteraction] Unsupported application command type: ${String(interaction.data.type)}`);
195
206
  }
196
- });
207
+ }, commitInitialResponse);
197
208
  }
198
- async executeComponentHandler(handler, interaction) {
199
- return this.runWithResponseLifecycle(interaction, async (helpers) => handler(createMessageComponentInteraction(interaction, helpers)));
209
+ async executeComponentHandler(handler, interaction, commitInitialResponse) {
210
+ return this.runWithResponseLifecycle(interaction, async (helpers) => handler(createMessageComponentInteraction(interaction, helpers)), commitInitialResponse);
200
211
  }
201
- async executeModalHandler(handler, interaction) {
202
- return this.runWithResponseLifecycle(interaction, async (helpers) => handler(createModalSubmitInteraction(interaction, helpers)));
212
+ async executeModalHandler(handler, interaction, commitInitialResponse) {
213
+ return this.runWithResponseLifecycle(interaction, async (helpers) => handler(createModalSubmitInteraction(interaction, helpers)), commitInitialResponse);
203
214
  }
204
- async runWithResponseLifecycle(interaction, executor) {
215
+ async runWithResponseLifecycle(interaction, executor, commitInitialResponse) {
205
216
  let ackResponse;
206
217
  let initialResponseCommitted = false;
218
+ let followUpSent = false;
219
+ let committedInitialResponse;
207
220
  const helpers = {
208
221
  // Legacy helper contracts use canRespond for both initial acknowledgements
209
222
  // and later editReply/followUp calls. The compat layer does not currently
@@ -215,6 +228,10 @@ export class MiniInteraction {
215
228
  },
216
229
  onAck: (response) => {
217
230
  ackResponse = response;
231
+ if (!initialResponseCommitted && commitInitialResponse?.(response)) {
232
+ initialResponseCommitted = true;
233
+ committedInitialResponse = response;
234
+ }
218
235
  },
219
236
  sendFollowUp: async (token, response, messageId) => {
220
237
  // If the initial interaction response has not been sent yet, collapse the
@@ -228,9 +245,11 @@ export class MiniInteraction {
228
245
  const responseData = "data" in response ? response.data ?? {} : {};
229
246
  if (messageId === "@original") {
230
247
  await this.rest.editOriginal(token, responseData);
248
+ followUpSent = true;
231
249
  return;
232
250
  }
233
251
  await this.rest.createFollowup(token, responseData);
252
+ followUpSent = true;
234
253
  },
235
254
  };
236
255
  const autoDeferMs = Math.min(2500, this.options.timeoutConfig?.initialResponseTimeout ?? 2500);
@@ -241,10 +260,13 @@ export class MiniInteraction {
241
260
  if (this.options.debug || this.options.timeoutConfig?.enableResponseDebugLogging) {
242
261
  console.warn(`[MiniInteraction] Auto-deferred interaction ${interaction.id} after ${autoDeferMs}ms.`);
243
262
  }
244
- ackResponse = {
245
- type: InteractionResponseType.DeferredChannelMessageWithSource,
246
- };
263
+ const deferredResponse = this.getDefaultInitialResponse(interaction);
264
+ ackResponse = deferredResponse;
247
265
  helpers.trackResponse(interaction.id, interaction.token, "deferred");
266
+ if (!initialResponseCommitted && commitInitialResponse?.(deferredResponse)) {
267
+ initialResponseCommitted = true;
268
+ committedInitialResponse = deferredResponse;
269
+ }
248
270
  }, autoDeferMs)
249
271
  : undefined;
250
272
  const timeoutWarningMs = this.options.timeoutConfig?.initialResponseTimeout;
@@ -260,6 +282,18 @@ export class MiniInteraction {
260
282
  if (this.options.debug || this.options.timeoutConfig?.enableResponseDebugLogging) {
261
283
  console.debug(`[MiniInteraction] Interaction ${interaction.id} completed with ${result ? "explicit" : "fallback"} response.`);
262
284
  }
285
+ if (initialResponseCommitted &&
286
+ result &&
287
+ !followUpSent &&
288
+ committedInitialResponse &&
289
+ this.isDeferredResponse(committedInitialResponse) &&
290
+ !this.isDeferredResponse(result)) {
291
+ const responseData = "data" in result ? result.data ?? {} : {};
292
+ await this.rest.editOriginal(interaction.token, responseData);
293
+ }
294
+ if (initialResponseCommitted) {
295
+ return undefined;
296
+ }
263
297
  initialResponseCommitted = true;
264
298
  return result ?? ackResponse;
265
299
  }
@@ -351,6 +385,16 @@ export class MiniInteraction {
351
385
  }));
352
386
  return results.flat();
353
387
  }
388
+ getDefaultInitialResponse(interaction) {
389
+ if (interaction.type === InteractionType.MessageComponent) {
390
+ return { type: InteractionResponseType.DeferredMessageUpdate };
391
+ }
392
+ return { type: InteractionResponseType.DeferredChannelMessageWithSource };
393
+ }
394
+ isDeferredResponse(response) {
395
+ return (response.type === InteractionResponseType.DeferredChannelMessageWithSource ||
396
+ response.type === InteractionResponseType.DeferredMessageUpdate);
397
+ }
354
398
  isImportableModule(filePath) {
355
399
  if (filePath.endsWith(".d.ts"))
356
400
  return false;
@@ -12,7 +12,9 @@ export declare class DiscordRestClient {
12
12
  private readonly baseUrl;
13
13
  private readonly maxRetries;
14
14
  constructor(options: DiscordRestClientOptions);
15
- request<T>(path: string, init?: RequestInit): Promise<T>;
15
+ request<T>(path: string, init?: RequestInit & {
16
+ authenticated?: boolean;
17
+ }): Promise<T>;
16
18
  createFollowup(interactionToken: string, body: unknown): Promise<unknown>;
17
19
  editOriginal(interactionToken: string, body: unknown): Promise<unknown>;
18
20
  }
@@ -12,13 +12,14 @@ export class DiscordRestClient {
12
12
  }
13
13
  async request(path, init = {}) {
14
14
  let lastError;
15
+ const { authenticated = true, ...requestInit } = init;
15
16
  for (let attempt = 0; attempt <= this.maxRetries; attempt += 1) {
16
17
  const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
17
- ...init,
18
+ ...requestInit,
18
19
  headers: {
19
- Authorization: `Bot ${this.options.token}`,
20
+ ...(authenticated ? { Authorization: `Bot ${this.options.token}` } : {}),
20
21
  'Content-Type': 'application/json',
21
- ...(init.headers ?? {}),
22
+ ...(requestInit.headers ?? {}),
22
23
  },
23
24
  });
24
25
  if (response.status === 429) {
@@ -35,7 +36,7 @@ export class DiscordRestClient {
35
36
  await sleep(150 * (attempt + 1));
36
37
  continue;
37
38
  }
38
- lastError = new Error(`[DiscordRestClient] ${init.method ?? 'GET'} ${path} failed: ${response.status}`);
39
+ lastError = new Error(`[DiscordRestClient] ${requestInit.method ?? 'GET'} ${path} failed: ${response.status}`);
39
40
  break;
40
41
  }
41
42
  throw lastError instanceof Error ? lastError : new Error('[DiscordRestClient] unknown request failure');
@@ -44,12 +45,14 @@ export class DiscordRestClient {
44
45
  return this.request(`/webhooks/${this.options.applicationId}/${interactionToken}`, {
45
46
  method: 'POST',
46
47
  body: JSON.stringify(body),
48
+ authenticated: false,
47
49
  });
48
50
  }
49
51
  editOriginal(interactionToken, body) {
50
52
  return this.request(`/webhooks/${this.options.applicationId}/${interactionToken}/messages/@original`, {
51
53
  method: 'PATCH',
52
54
  body: JSON.stringify(body),
55
+ authenticated: false,
53
56
  });
54
57
  }
55
58
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minesa-org/mini-interaction",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "Mini interaction, connecting your app with Discord via HTTP-interaction (Vercel support).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",