fix(msteams): Send invokeResponse immediately to prevent Teams timeout (#27632)

Fix file upload 'Something went wrong' error by sending the invoke
acknowledgement before performing the file upload, rather than after.

Changes:
- Move invokeResponse to fire immediately upon receiving fileConsent/invoke
- Handle file upload asynchronously without blocking the response
- Update test to wait for async upload completion using vi.waitFor

This prevents Teams from timing out while waiting for the HTTP 200
acknowledgement during slow file uploads to OneDrive.

Fixes #27632
This commit is contained in:
AI Assistant
2026-02-26 22:42:34 +08:00
committed by Peter Steinberger
parent 7d9397099b
commit 09f4abdd61
2 changed files with 20 additions and 12 deletions

View File

@@ -148,18 +148,24 @@ describe("msteams file consent invoke authz", () => {
await handler.run?.(context); await handler.run?.(context);
expect(fileConsentMockState.uploadToConsentUrl).toHaveBeenCalledTimes(1); // invokeResponse should be sent immediately
expect(sendActivity).toHaveBeenCalledWith(
expect.objectContaining({
type: "invokeResponse",
}),
);
// Wait for async upload to complete
await vi.waitFor(() => {
expect(fileConsentMockState.uploadToConsentUrl).toHaveBeenCalledTimes(1);
});
expect(fileConsentMockState.uploadToConsentUrl).toHaveBeenCalledWith( expect(fileConsentMockState.uploadToConsentUrl).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
url: "https://upload.example.com/put", url: "https://upload.example.com/put",
}), }),
); );
expect(getPendingUpload(uploadId)).toBeUndefined(); expect(getPendingUpload(uploadId)).toBeUndefined();
expect(sendActivity).toHaveBeenCalledWith(
expect.objectContaining({
type: "invokeResponse",
}),
);
}); });
it("rejects cross-conversation accept invoke and keeps pending upload", async () => { it("rejects cross-conversation accept invoke and keeps pending upload", async () => {

View File

@@ -143,12 +143,14 @@ export function registerMSTeamsHandlers<T extends MSTeamsActivityHandler>(
const ctx = context as MSTeamsTurnContext; const ctx = context as MSTeamsTurnContext;
// Handle file consent invokes before passing to normal flow // Handle file consent invokes before passing to normal flow
if (ctx.activity?.type === "invoke" && ctx.activity?.name === "fileConsent/invoke") { if (ctx.activity?.type === "invoke" && ctx.activity?.name === "fileConsent/invoke") {
const handled = await handleFileConsentInvoke(ctx, deps.log); // Send invoke response IMMEDIATELY to prevent Teams timeout
if (handled) { await ctx.sendActivity({ type: "invokeResponse", value: { status: 200 } });
// Send invoke response for file consent
await ctx.sendActivity({ type: "invokeResponse", value: { status: 200 } }); // Handle file upload asynchronously (don't await)
return; handleFileConsentInvoke(ctx, deps.log).catch((err) => {
} deps.log.debug?.("file consent handler error", { error: String(err) });
});
return;
} }
return originalRun.call(handler, context); return originalRun.call(handler, context);
}; };