diff --git a/dist/index.js b/dist/index.js index b0383953a27e4426ce544128a26bf3c8d8964b06..9ae852cba707495c3e72b0e07523cb6fca2e197a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -163,7 +163,6 @@ var BridgeHttpAdapter = class { } async dispatch(request, options) { const body = await request.text(); - this.logger.debug("Teams webhook raw body", { body }); let parsedBody; try { parsedBody = JSON.parse(body); @@ -504,6 +503,52 @@ import { NetworkError as NetworkError2, PermissionError } from "@chat-adapter/shared"; +function teamsErrorRecord(value) { + return value && typeof value === "object" && !Array.isArray(value) ? value : void 0; +} +function teamsErrorBody(value) { + if (typeof value === "string") { + if (value.length > 16384) return void 0; + try { + return teamsErrorRecord(JSON.parse(value)); + } catch { + return void 0; + } + } + return teamsErrorRecord(value); +} +function teamsErrorCode(value) { + return typeof value === "string" && /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/.test(value) ? value : void 0; +} +function teamsErrorCodes(value, depth = 0, seen = /* @__PURE__ */ new Set()) { + if (depth > 4) return []; + const record = teamsErrorBody(value); + if (!record || seen.has(record)) return []; + seen.add(record); + const codes = [teamsErrorCode(record.code), teamsErrorCode(record.subCode), teamsErrorCode(record.subcode)].filter(Boolean); + for (const key of ["error", "innerError", "details", "body", "data"]) { + codes.push(...teamsErrorCodes(record[key], depth + 1, seen)); + } + return [...new Set(codes)].slice(0, 8); +} +function teamsPermissionError(error, operation, statusCode) { + const err = teamsErrorRecord(error) || {}; + const innerError = teamsErrorRecord(err.innerHttpError); + const response = teamsErrorRecord(innerError?.response) || teamsErrorRecord(err.response); + const bodies = [innerError, response, err]; + const providerCodes = [...new Set(bodies.flatMap((body) => teamsErrorCodes(body)))].slice(0, 8); + const permissionError = new PermissionError("teams", operation); + permissionError.status = statusCode; + permissionError.statusCode = statusCode; + permissionError.providerCodes = providerCodes; + permissionError.subCode = providerCodes.find((code) => code === "MessageWritesBlocked" || code === "ForbiddenOperationException"); + permissionError.details = { + providerStatus: statusCode, + providerCodes, + ...permissionError.subCode ? { providerSubCode: permissionError.subCode } : {} + }; + return permissionError; +} function handleTeamsError(error, operation) { if (error && typeof error === "object") { const err = error; @@ -516,7 +561,7 @@ function handleTeamsError(error, operation) { ); } if (statusCode === 403 || err.message && typeof err.message === "string" && err.message.toLowerCase().includes("permission")) { - throw new PermissionError("teams", operation); + throw teamsPermissionError(error, operation, statusCode); } if (statusCode === 404) { throw new NetworkError2( @@ -560,18 +605,15 @@ function encodeThreadId(platformData) { const encodedConversationId = Buffer.from( platformData.conversationId ).toString("base64url"); - const encodedServiceUrl = Buffer.from(platformData.serviceUrl).toString( - "base64url" - ); const conversationType = platformData.conversationType; const legacyIsDM = !platformData.conversationId.startsWith("19:"); const explicitIsDM = conversationType === "personal"; const needsConversationType = conversationType !== void 0 && explicitIsDM !== legacyIsDM; - return needsConversationType ? `teams:${encodedConversationId}:${encodedServiceUrl}:${conversationType}` : `teams:${encodedConversationId}:${encodedServiceUrl}`; + return needsConversationType ? `teams:${encodedConversationId}:${conversationType}` : `teams:${encodedConversationId}`; } function decodeThreadId(threadId) { const parts = threadId.split(":"); - const hasValidPartCount = parts.length === 3 || parts.length === 4; + const hasValidPartCount = parts.length >= 2 && parts.length <= 4; if (!hasValidPartCount || parts[0] !== "teams") { throw new ValidationError("teams", `Invalid Teams thread ID: ${threadId}`); } @@ -578,13 +620,16 @@ function decodeThreadId(threadId) { const conversationId = Buffer.from(parts[1], "base64url").toString( "utf-8" ); - const serviceUrl = Buffer.from(parts[2], "base64url").toString( - "utf-8" - ); - const rawConversationType = parts[3]; - if (!rawConversationType) { - return { conversationId, serviceUrl }; + if (!parts[2]) { + return { conversationId }; + } + const canonicalConversationType = parseConversationType(parts[2]); + if (parts.length === 3 && canonicalConversationType) { + return { conversationId, conversationType: canonicalConversationType }; } + const serviceUrl = Buffer.from(parts[2], "base64url").toString("utf-8"); + const rawConversationType = parts[3]; + if (!rawConversationType) return { conversationId, serviceUrl }; const conversationType = parseConversationType(rawConversationType); if (!conversationType) { throw new ValidationError( @@ -1668,7 +1713,8 @@ var TeamsAdapter = class { this.app = new App({ ...toAppOptions(config), client: { - headers: { "User-Agent": "Vercel.ChatSDK" } + headers: { "User-Agent": "Vercel.ChatSDK" }, + timeout: 45e3 }, httpServerAdapter: this.bridgeAdapter }); diff --git a/dist/index.d.ts b/dist/index.d.ts index 3f516b01355b28baaebc3d3540f85f22521bd4e4..a40328baaf4ee95a9db394001f4865fc662b6794 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -125,7 +125,7 @@ interface TeamsThreadId { conversationId: string; conversationType?: "channel" | "groupChat" | "personal"; replyToId?: string; - serviceUrl: string; + serviceUrl?: string; } /** Teams channel context extracted from activity.channelData */ interface TeamsChannelContext {