@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
JavaScript
;
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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