@minesa-org/mini-interaction 0.2.21 → 0.2.23

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.
@@ -1370,8 +1370,13 @@ export class MiniInteraction {
1370
1370
  * Includes retry logic to handle race conditions where the ACK hasn't reached Discord yet.
1371
1371
  */
1372
1372
  async sendFollowUp(token, response, messageId = "@original", retryCount = 0) {
1373
- const MAX_RETRIES = 3;
1374
- const BASE_DELAY_MS = 250; // Start with 250ms delay
1373
+ const MAX_RETRIES = 5;
1374
+ const BASE_DELAY_MS = 200; // Start with 200ms delay
1375
+ const INITIAL_DELAY_MS = 100; // Wait for ACK to propagate to Discord
1376
+ // On first attempt, add a small delay to allow ACK to propagate
1377
+ if (retryCount === 0) {
1378
+ await new Promise(resolve => setTimeout(resolve, INITIAL_DELAY_MS));
1379
+ }
1375
1380
  const isEdit = messageId !== "";
1376
1381
  const url = isEdit
1377
1382
  ? `${DISCORD_BASE_URL}/webhooks/${this.applicationId}/${token}/messages/${messageId}`
@@ -1401,7 +1406,7 @@ export class MiniInteraction {
1401
1406
  const errorBody = await fetchResponse.text();
1402
1407
  // Check for "Unknown Webhook" error (10015) - this means ACK hasn't reached Discord yet
1403
1408
  if (fetchResponse.status === 404 && errorBody.includes("10015") && retryCount < MAX_RETRIES) {
1404
- const delayMs = BASE_DELAY_MS * Math.pow(2, retryCount); // Exponential backoff: 250, 500, 1000ms
1409
+ const delayMs = BASE_DELAY_MS * Math.pow(2, retryCount); // Exponential backoff: 200, 400, 800, 1600, 3200ms
1405
1410
  console.warn(`[MiniInteraction] Webhook not ready yet, retrying in ${delayMs}ms (attempt ${retryCount + 1}/${MAX_RETRIES})`);
1406
1411
  await new Promise(resolve => setTimeout(resolve, delayMs));
1407
1412
  return this.sendFollowUp(token, response, messageId, retryCount + 1);
@@ -1583,13 +1588,16 @@ function resolveOAuthConfig(provided) {
1583
1588
  }
1584
1589
  /**
1585
1590
  * Wraps a handler function with timeout detection and error handling.
1591
+ *
1592
+ * CRITICAL FOR HTTP INTERACTIONS:
1593
+ * When deferReply() is called, we MUST return the ACK to Discord immediately.
1594
+ * The handler continues executing and sends follow-up via webhook.
1595
+ * The ACK must reach Discord before any webhook PATCH requests can succeed.
1586
1596
  */
1587
1597
  function createTimeoutWrapper(handler, timeoutMs, handlerName, enableWarnings = true, ackPromise) {
1588
1598
  return async (...args) => {
1589
1599
  const startTime = Date.now();
1590
1600
  let timeoutId;
1591
- let ackResult = null;
1592
- let ackReceived = false;
1593
1601
  const timeoutPromise = new Promise((_, reject) => {
1594
1602
  timeoutId = setTimeout(() => {
1595
1603
  const elapsed = Date.now() - startTime;
@@ -1597,55 +1605,54 @@ function createTimeoutWrapper(handler, timeoutMs, handlerName, enableWarnings =
1597
1605
  reject(new Error(`Handler timeout: ${handlerName} exceeded ${timeoutMs}ms limit`));
1598
1606
  }, timeoutMs);
1599
1607
  });
1600
- // If we have an ackPromise, listen for it but DON'T return early
1601
- // Instead, capture the ACK result and continue waiting for handler
1608
+ // Start handler execution immediately (don't await yet)
1609
+ const handlerPromise = Promise.resolve(handler(...args));
1610
+ // If we have an ackPromise, race between ACK and timeout
1611
+ // When ACK is received, return IMMEDIATELY so Discord gets the response
1612
+ // Handler continues in background
1602
1613
  if (ackPromise) {
1603
- ackPromise.then((result) => {
1604
- ackResult = result;
1605
- ackReceived = true;
1606
- // Clear the timeout once we have an ACK - handler can now take longer
1614
+ try {
1615
+ const ackResult = await Promise.race([
1616
+ ackPromise,
1617
+ timeoutPromise,
1618
+ ]);
1619
+ // ACK received! Clear timeout and return immediately
1607
1620
  if (timeoutId) {
1608
1621
  clearTimeout(timeoutId);
1609
- timeoutId = undefined;
1610
1622
  }
1611
- }).catch(() => {
1612
- // ACK promise rejection is fine, we'll use handler result
1613
- });
1623
+ // Handler continues in background - attach error handler
1624
+ handlerPromise.catch((error) => {
1625
+ console.error(`[MiniInteraction] ${handlerName} background execution failed:`, error instanceof Error ? error.message : String(error));
1626
+ });
1627
+ return ackResult;
1628
+ }
1629
+ catch (error) {
1630
+ // Timeout occurred before ACK - fall through to check handler
1631
+ if (timeoutId) {
1632
+ clearTimeout(timeoutId);
1633
+ }
1634
+ throw error;
1635
+ }
1614
1636
  }
1637
+ // No ACK promise - wait for handler with timeout
1615
1638
  try {
1616
- // ALWAYS wait for handler to complete
1617
1639
  const handlerResult = await Promise.race([
1618
- handler(...args),
1640
+ handlerPromise,
1619
1641
  timeoutPromise,
1620
1642
  ]);
1621
1643
  if (timeoutId) {
1622
1644
  clearTimeout(timeoutId);
1623
1645
  }
1624
1646
  const elapsed = Date.now() - startTime;
1625
- if (enableWarnings && elapsed > timeoutMs * 0.8 && !ackReceived) {
1647
+ if (enableWarnings && elapsed > timeoutMs * 0.8) {
1626
1648
  console.warn(`[MiniInteraction] ${handlerName} completed in ${elapsed}ms (${Math.round((elapsed / timeoutMs) * 100)}% of timeout limit)`);
1627
1649
  }
1628
- // If we got an ACK, return that for the HTTP response
1629
- // The handler has completed at this point so all background work is done
1630
- if (ackReceived && ackResult !== null) {
1631
- return ackResult;
1632
- }
1633
1650
  return handlerResult;
1634
1651
  }
1635
1652
  catch (error) {
1636
1653
  if (timeoutId) {
1637
1654
  clearTimeout(timeoutId);
1638
1655
  }
1639
- // If handler timed out but we have an ACK, return the ACK
1640
- // This allows deferReply to work even if later operations are slow
1641
- if (error instanceof Error && error.message.includes("Handler timeout")) {
1642
- if (ackReceived && ackResult !== null) {
1643
- console.warn(`[MiniInteraction] ${handlerName} timed out but ACK was already captured. ` +
1644
- `Background work may not complete in serverless environments.`);
1645
- return ackResult;
1646
- }
1647
- throw error;
1648
- }
1649
1656
  console.error(`[MiniInteraction] ${handlerName} failed:`, error);
1650
1657
  throw error;
1651
1658
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minesa-org/mini-interaction",
3
- "version": "0.2.21",
3
+ "version": "0.2.23",
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",