@minesa-org/mini-interaction 0.2.22 → 0.2.24
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.
|
@@ -37,6 +37,25 @@ export type InteractionHandlerResult = {
|
|
|
37
37
|
body: APIInteractionResponse | {
|
|
38
38
|
error: string;
|
|
39
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Promise that resolves when all background work (like editReply) completes.
|
|
42
|
+
* Pass this to Vercel's waitUntil() to prevent premature termination.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* // In Next.js App Router
|
|
47
|
+
* import { waitUntil } from '@vercel/functions';
|
|
48
|
+
*
|
|
49
|
+
* export async function POST(request: Request) {
|
|
50
|
+
* const result = await client.handleRequest({ ... });
|
|
51
|
+
* if (result.backgroundWork) {
|
|
52
|
+
* waitUntil(result.backgroundWork);
|
|
53
|
+
* }
|
|
54
|
+
* return Response.json(result.body, { status: result.status });
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
backgroundWork?: Promise<void>;
|
|
40
59
|
};
|
|
41
60
|
/** Configuration for interaction timeout handling. */
|
|
42
61
|
export type InteractionTimeoutConfig = {
|
|
@@ -1142,10 +1142,11 @@ export class MiniInteraction {
|
|
|
1142
1142
|
}
|
|
1143
1143
|
return resolvedResponse;
|
|
1144
1144
|
}, this.timeoutConfig.initialResponseTimeout, `Component "${customId}"`, this.timeoutConfig.enableTimeoutWarnings, ackPromise);
|
|
1145
|
-
const resolvedResponse = await timeoutWrapper();
|
|
1145
|
+
const { response: resolvedResponse, backgroundWork } = await timeoutWrapper();
|
|
1146
1146
|
return {
|
|
1147
1147
|
status: 200,
|
|
1148
1148
|
body: resolvedResponse,
|
|
1149
|
+
backgroundWork,
|
|
1149
1150
|
};
|
|
1150
1151
|
}
|
|
1151
1152
|
catch (error) {
|
|
@@ -1208,10 +1209,11 @@ export class MiniInteraction {
|
|
|
1208
1209
|
}
|
|
1209
1210
|
return resolvedResponse;
|
|
1210
1211
|
}, this.timeoutConfig.initialResponseTimeout, `Modal "${customId}"`, this.timeoutConfig.enableTimeoutWarnings, ackPromise);
|
|
1211
|
-
const resolvedResponse = await timeoutWrapper();
|
|
1212
|
+
const { response: resolvedResponse, backgroundWork } = await timeoutWrapper();
|
|
1212
1213
|
return {
|
|
1213
1214
|
status: 200,
|
|
1214
1215
|
body: resolvedResponse,
|
|
1216
|
+
backgroundWork,
|
|
1215
1217
|
};
|
|
1216
1218
|
}
|
|
1217
1219
|
catch (error) {
|
|
@@ -1324,7 +1326,7 @@ export class MiniInteraction {
|
|
|
1324
1326
|
}
|
|
1325
1327
|
return resolvedResponse;
|
|
1326
1328
|
}, this.timeoutConfig.initialResponseTimeout, `Command "${commandName}"`, this.timeoutConfig.enableTimeoutWarnings, ackPromise);
|
|
1327
|
-
const finalResponse = await timeoutWrapper();
|
|
1329
|
+
const { response: finalResponse, backgroundWork } = await timeoutWrapper();
|
|
1328
1330
|
if (this.timeoutConfig.enableResponseDebugLogging) {
|
|
1329
1331
|
console.log(`[MiniInteraction] handleApplicationCommand: initial response determined (type=${finalResponse?.type})`);
|
|
1330
1332
|
}
|
|
@@ -1344,6 +1346,7 @@ export class MiniInteraction {
|
|
|
1344
1346
|
return {
|
|
1345
1347
|
status: 200,
|
|
1346
1348
|
body: finalResponse,
|
|
1349
|
+
backgroundWork,
|
|
1347
1350
|
};
|
|
1348
1351
|
}
|
|
1349
1352
|
catch (error) {
|
|
@@ -1588,13 +1591,16 @@ function resolveOAuthConfig(provided) {
|
|
|
1588
1591
|
}
|
|
1589
1592
|
/**
|
|
1590
1593
|
* Wraps a handler function with timeout detection and error handling.
|
|
1594
|
+
*
|
|
1595
|
+
* CRITICAL FOR HTTP INTERACTIONS:
|
|
1596
|
+
* When deferReply() is called, we MUST return the ACK to Discord immediately.
|
|
1597
|
+
* The handler continues executing and sends follow-up via webhook.
|
|
1598
|
+
* The ACK must reach Discord before any webhook PATCH requests can succeed.
|
|
1591
1599
|
*/
|
|
1592
1600
|
function createTimeoutWrapper(handler, timeoutMs, handlerName, enableWarnings = true, ackPromise) {
|
|
1593
1601
|
return async (...args) => {
|
|
1594
1602
|
const startTime = Date.now();
|
|
1595
1603
|
let timeoutId;
|
|
1596
|
-
let ackResult = null;
|
|
1597
|
-
let ackReceived = false;
|
|
1598
1604
|
const timeoutPromise = new Promise((_, reject) => {
|
|
1599
1605
|
timeoutId = setTimeout(() => {
|
|
1600
1606
|
const elapsed = Date.now() - startTime;
|
|
@@ -1602,55 +1608,54 @@ function createTimeoutWrapper(handler, timeoutMs, handlerName, enableWarnings =
|
|
|
1602
1608
|
reject(new Error(`Handler timeout: ${handlerName} exceeded ${timeoutMs}ms limit`));
|
|
1603
1609
|
}, timeoutMs);
|
|
1604
1610
|
});
|
|
1605
|
-
//
|
|
1606
|
-
|
|
1611
|
+
// Start handler execution immediately (don't await yet)
|
|
1612
|
+
const handlerPromise = Promise.resolve(handler(...args));
|
|
1613
|
+
// Attach a default error handler to prevent unhandled rejections
|
|
1614
|
+
const backgroundWork = handlerPromise.catch((error) => {
|
|
1615
|
+
console.error(`[MiniInteraction] ${handlerName} background execution failed:`, error instanceof Error ? error.message : String(error));
|
|
1616
|
+
}).then(() => {
|
|
1617
|
+
// Ensure it always resolves to void
|
|
1618
|
+
});
|
|
1619
|
+
// If we have an ackPromise, race between ACK and timeout
|
|
1607
1620
|
if (ackPromise) {
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1621
|
+
try {
|
|
1622
|
+
const response = await Promise.race([
|
|
1623
|
+
ackPromise,
|
|
1624
|
+
timeoutPromise,
|
|
1625
|
+
]);
|
|
1626
|
+
// ACK received! Clear timeout and return immediately
|
|
1612
1627
|
if (timeoutId) {
|
|
1613
1628
|
clearTimeout(timeoutId);
|
|
1614
|
-
timeoutId = undefined;
|
|
1615
1629
|
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1630
|
+
return { response, backgroundWork };
|
|
1631
|
+
}
|
|
1632
|
+
catch (error) {
|
|
1633
|
+
// Timeout occurred before ACK - fall through to check handler
|
|
1634
|
+
if (timeoutId) {
|
|
1635
|
+
clearTimeout(timeoutId);
|
|
1636
|
+
}
|
|
1637
|
+
throw error;
|
|
1638
|
+
}
|
|
1619
1639
|
}
|
|
1640
|
+
// No ACK promise - wait for handler with timeout
|
|
1620
1641
|
try {
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
handler(...args),
|
|
1642
|
+
const response = await Promise.race([
|
|
1643
|
+
handlerPromise,
|
|
1624
1644
|
timeoutPromise,
|
|
1625
1645
|
]);
|
|
1626
1646
|
if (timeoutId) {
|
|
1627
1647
|
clearTimeout(timeoutId);
|
|
1628
1648
|
}
|
|
1629
1649
|
const elapsed = Date.now() - startTime;
|
|
1630
|
-
if (enableWarnings && elapsed > timeoutMs * 0.8
|
|
1650
|
+
if (enableWarnings && elapsed > timeoutMs * 0.8) {
|
|
1631
1651
|
console.warn(`[MiniInteraction] ${handlerName} completed in ${elapsed}ms (${Math.round((elapsed / timeoutMs) * 100)}% of timeout limit)`);
|
|
1632
1652
|
}
|
|
1633
|
-
|
|
1634
|
-
// The handler has completed at this point so all background work is done
|
|
1635
|
-
if (ackReceived && ackResult !== null) {
|
|
1636
|
-
return ackResult;
|
|
1637
|
-
}
|
|
1638
|
-
return handlerResult;
|
|
1653
|
+
return { response, backgroundWork };
|
|
1639
1654
|
}
|
|
1640
1655
|
catch (error) {
|
|
1641
1656
|
if (timeoutId) {
|
|
1642
1657
|
clearTimeout(timeoutId);
|
|
1643
1658
|
}
|
|
1644
|
-
// If handler timed out but we have an ACK, return the ACK
|
|
1645
|
-
// This allows deferReply to work even if later operations are slow
|
|
1646
|
-
if (error instanceof Error && error.message.includes("Handler timeout")) {
|
|
1647
|
-
if (ackReceived && ackResult !== null) {
|
|
1648
|
-
console.warn(`[MiniInteraction] ${handlerName} timed out but ACK was already captured. ` +
|
|
1649
|
-
`Background work may not complete in serverless environments.`);
|
|
1650
|
-
return ackResult;
|
|
1651
|
-
}
|
|
1652
|
-
throw error;
|
|
1653
|
-
}
|
|
1654
1659
|
console.error(`[MiniInteraction] ${handlerName} failed:`, error);
|
|
1655
1660
|
throw error;
|
|
1656
1661
|
}
|
package/package.json
CHANGED