UNPKG

@microsoft/teams.apps

Version:

<p> <a href="https://www.npmjs.com/package/@microsoft/teams.apps" target="_blank"> <img src="https://img.shields.io/npm/v/@microsoft/teams.apps/latest" /> </a> <a href="https://www.npmjs.com/package/@microsoft/teams.apps?activeTab=code

143 lines • 11.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NegotiateError = exports.DEFAULT_NEGOTIATE_TIMEOUT_MS = void 0; exports.assertSecureNegotiateUrl = assertSecureNegotiateUrl; exports.negotiate = negotiate; const redact_1 = require("./redact"); /** Default negotiate request timeout — bounds a hung or misconfigured endpoint. */ exports.DEFAULT_NEGOTIATE_TIMEOUT_MS = 15_000; /** * Reject a negotiate URL that isn't HTTPS. The negotiate request carries a Bot * Framework JWT in the `authorization` header; sending it over plaintext `http` * would leak that token. A narrow exception is made for loopback hosts * (`localhost`/`127.0.0.1`/`::1`) so local test endpoints still work. */ function assertSecureNegotiateUrl(negotiateUrl) { let parsed; try { parsed = new URL(negotiateUrl); } catch { throw new Error(`Socket Mode negotiate URL is not a valid URL: ${negotiateUrl}`); } if (parsed.protocol === 'https:') return; const host = parsed.hostname; const isLoopback = host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '[::1]'; if (parsed.protocol === 'http:' && isLoopback) return; throw new Error(`Socket Mode negotiate URL must use https (got ${parsed.protocol}//${host}); ` + 'the negotiate request carries a bearer token.'); } /** * Error thrown by {@link negotiate} when the service returns a non-2xx status. * Carries the HTTP status and the parsed `Retry-After` (in milliseconds) when * the service asked the caller to back off — typically on `429` or `503` — so * the reconnect supervisor can honor it instead of using its own back-off. * Authentication and authorization failures (`401`/`403`) are not retried; * see {@link NegotiateError.isAuthError}. */ class NegotiateError extends Error { retryAfterMs; statusCode; constructor(message, /** Service-requested delay before retrying, when supplied. */ retryAfterMs, /** * HTTP status returned by the Socket Mode negotiate endpoint. `undefined` * when the error was constructed without a status. */ statusCode) { super(message); this.retryAfterMs = retryAfterMs; this.statusCode = statusCode; this.name = 'NegotiateError'; } /** * `true` when negotiate was rejected with HTTP `401` or `403`. Retrying * cannot fix these until the bot's credentials or Socket Mode access are * corrected, so the SDK stops retrying on startup and reconnect. */ get isAuthError() { return this.statusCode === 401 || this.statusCode === 403; } } exports.NegotiateError = NegotiateError; /** Build an actionable error message for a failed negotiate request. */ function negotiateErrorMessage(status, body) { const serviceError = `Socket Mode negotiate failed: HTTP ${status}${body ? ` ${body}` : ''}`; switch (status) { case 401: return (`${serviceError}. The bot could not be authenticated: verify the bot credentials ` + '(clientId/clientSecret, managed identity, or token provider) and restart the app after correcting them.'); case 403: return (`${serviceError}. This bot is not authorized to use ` + 'Socket Mode: verify the bot registration and Socket Mode access for this environment, then restart the app.'); default: return serviceError; } } /** * Parse an HTTP `Retry-After` header (delta-seconds or an HTTP date) into * milliseconds, returning `undefined` when absent or unparseable. */ function parseRetryAfterMs(res) { const headers = res.headers; const raw = headers?.get?.('retry-after') ?? headers?.['retry-after']; if (!raw) return undefined; const value = String(raw); const seconds = Number(value); if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000); const date = Date.parse(value); if (Number.isFinite(date)) return Math.max(0, date - Date.now()); return undefined; } /** * Perform the Teams backend service Socket Mode handshake: `POST {negotiateUrl}` authenticated * with a Bot Framework JWT minted from the bot's own credentials. Teams backend service returns * the Azure SignalR `url` + `accessToken` to open the socket against. * * @throws {NegotiateError} if Teams backend service returns a non-2xx response. * @throws if the bot token is missing (the app has no credentials configured). */ async function negotiate(deps) { const token = await deps.getBotToken(); if (!token) { throw new Error('Socket Mode negotiate has no bot token: configure app credentials ' + '(clientId/clientSecret, managed identity, or a token provider).'); } assertSecureNegotiateUrl(deps.negotiateUrl); deps.log?.debug(`socket-mode: negotiate POST ${(0, redact_1.sanitizeSocketModeUrl)(deps.negotiateUrl)}`); const replacesConnectionId = deps.getConnectionIdToReplace?.(); const res = await deps.client.post(deps.negotiateUrl, undefined, { headers: { ...(replacesConnectionId ? { 'x-ms-socket-replaces-connection-id': replacesConnectionId } : {}), authorization: `Bearer ${token}`, }, timeout: deps.timeoutMs ?? exports.DEFAULT_NEGOTIATE_TIMEOUT_MS, validateStatus: () => true, }); if (res.status < 200 || res.status >= 300) { const body = (0, redact_1.redactSocketModeSecrets)((typeof res.data === 'string' ? res.data : JSON.stringify(res.data ?? '')).slice(0, 500), [token]); // Logged at debug because this failure is thrown and reported by the caller // (App.onError); logging it here as error too would double-report it. deps.log?.debug(`socket-mode: negotiate failed status=${res.status} body=${body || '(empty)'}`); throw new NegotiateError(negotiateErrorMessage(res.status, body), parseRetryAfterMs(res), res.status); } const json = res.data; if (!json.url || !json.accessToken) { throw new Error('Socket Mode negotiate response missing url/accessToken'); } deps.log?.debug(`socket-mode: negotiate ok status=${res.status} expiresIn=${json.expiresIn ?? 0}`); return { url: json.url, accessToken: json.accessToken, expiresIn: json.expiresIn ?? 0, }; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmVnb3RpYXRlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NvY2tldC1tb2RlL25lZ290aWF0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUF1Q0EsNERBZUM7QUFnRkQsOEJBMkRDO0FBL0xELHFDQUEwRTtBQTRCMUUsbUZBQW1GO0FBQ3RFLFFBQUEsNEJBQTRCLEdBQUcsTUFBTSxDQUFDO0FBRW5EOzs7OztHQUtHO0FBQ0gsU0FBZ0Isd0JBQXdCLENBQUMsWUFBb0I7SUFDM0QsSUFBSSxNQUFXLENBQUM7SUFDaEIsSUFBSSxDQUFDO1FBQ0gsTUFBTSxHQUFHLElBQUksR0FBRyxDQUFDLFlBQVksQ0FBQyxDQUFDO0lBQ2pDLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCxNQUFNLElBQUksS0FBSyxDQUFDLGlEQUFpRCxZQUFZLEVBQUUsQ0FBQyxDQUFDO0lBQ25GLENBQUM7SUFDRCxJQUFJLE1BQU0sQ0FBQyxRQUFRLEtBQUssUUFBUTtRQUFFLE9BQU87SUFDekMsTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLFFBQVEsQ0FBQztJQUM3QixNQUFNLFVBQVUsR0FBRyxJQUFJLEtBQUssV0FBVyxJQUFJLElBQUksS0FBSyxXQUFXLElBQUksSUFBSSxLQUFLLEtBQUssSUFBSSxJQUFJLEtBQUssT0FBTyxDQUFDO0lBQ3RHLElBQUksTUFBTSxDQUFDLFFBQVEsS0FBSyxPQUFPLElBQUksVUFBVTtRQUFFLE9BQU87SUFDdEQsTUFBTSxJQUFJLEtBQUssQ0FDYixpREFBaUQsTUFBTSxDQUFDLFFBQVEsS0FBSyxJQUFJLEtBQUs7UUFDOUUsK0NBQStDLENBQ2hELENBQUM7QUFDSixDQUFDO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILE1BQWEsY0FBZSxTQUFRLEtBQUs7SUFJNUI7SUFLQTtJQVJYLFlBQ0UsT0FBZTtJQUNmLDhEQUE4RDtJQUNyRCxZQUFxQjtJQUM5Qjs7O09BR0c7SUFDTSxVQUFtQjtRQUU1QixLQUFLLENBQUMsT0FB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