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

207 lines • 16 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SignalRSocketConnection = void 0; const signalr_1 = require("@microsoft/signalr"); const negotiate_1 = require("./negotiate"); const redact_1 = require("./redact"); /** * A `@microsoft/signalr`-backed {@link ISocketConnection}. Negotiates through * Teams backend service, opens the Azure SignalR socket, and does not resolve {@link start} until * the Teams backend service `SocketReady` readiness frame arrives (or the readiness timeout * elapses). * * Returning a value from the `Activity` handler is what drives SignalR client * results: Teams backend service awaits the returned reply frame as the invocation result. */ class SignalRSocketConnection { context; handlers; log; /** Access-token lifetime from the negotiate response; drives proactive refresh. */ expiresInSeconds; /** Local token-expiry deadline; see {@link ISocketConnection.expiresAtMs}. */ expiresAtMs; /** This socket's server identity; see {@link ISocketConnection.connectionId}. */ connectionId; connection; stopped = false; /** * Whether the readiness gate has been settled (resolved or rejected). Guards * the `SocketReady` handler so a duplicate or late readiness frame — including * one that arrives after {@link stop} — cannot re-fire `onReady` or otherwise * act on an already-settled connection. */ settled = false; constructor(context, handlers, log) { this.context = context; this.handlers = handlers; this.log = log; } /** Whether SignalR is connected and stopping has not begun. */ get isOpen() { return !this.stopped && this.connection?.state === signalr_1.HubConnectionState.Connected; } async start(signal) { this.stopped = false; this.settled = false; this.throwIfAborted(signal); const neg = await (0, negotiate_1.negotiate)({ negotiateUrl: this.context.negotiateUrl, client: this.context.client, getBotToken: this.context.getBotToken, getConnectionIdToReplace: this.context.getConnectionIdToReplace, log: this.log, }); this.expiresInSeconds = neg.expiresIn || undefined; this.expiresAtMs = neg.expiresIn > 0 ? Date.now() + neg.expiresIn * 1000 : undefined; this.throwIfAborted(signal); // APX tokens are single-socket: disable transport fallback and leave automatic // and stateful reconnect off. The supervisor negotiates afresh for every retry. const connection = new signalr_1.HubConnectionBuilder() .withUrl(neg.url, { accessTokenFactory: () => neg.accessToken, transport: signalr_1.HttpTransportType.WebSockets, }) .configureLogging(this.signalRLogger([neg.accessToken])) .build(); this.connection = connection; // A readiness gate: the socket is not usable until Teams backend service // confirms the connection is registered in the bot's group via `SocketReady`. let markReady; let failReady; const ready = new Promise((resolve, reject) => { // Settle exactly once: the first outcome (ready, close, timeout, or abort) // wins, and every later signal — e.g. a duplicate `SocketReady` or a close // after readiness — becomes a no-op instead of re-firing observers. markReady = () => { if (this.settled) return; this.settled = true; resolve(); }; failReady = (err) => { if (this.settled) return; this.settled = true; reject(err); }; }); connection.on('Activity', (envelope) => this.handlers.onActivity(envelope)); connection.on('SocketReady', (frame) => { // Ignore a late/duplicate readiness frame once the gate has settled (e.g. // after stop or a prior SocketReady) so `onReady` fires at most once. if (this.settled || this.stopped) return; this.connectionId = frame?.connectionId; this.log?.debug('socket-mode: SocketReady received; readiness satisfied'); // Complete the internal readiness transition FIRST so a throwing observer // can't wedge the connection into a permanent not-ready state (readiness // would otherwise time out); only then notify the server, behind an error // boundary. markReady(); try { this.handlers.onReady(frame ?? {}); } catch (err) { this.log?.warn('socket-mode: onReady observer threw; connection is still ready', err); } }); connection.onclose((error) => { if (!this.stopped) { this.log?.warn('socket-mode: socket closed', error); } // A close before readiness rejects the gate; after readiness this is a // no-op. Either way the connection is terminal — report it once so the // supervisor can decide whether to negotiate a fresh connection. failReady(error ?? new Error('socket closed before readiness')); this.handlers.onClosed(error ?? undefined); }); let connected = false; // Stop immediately on abort because the supervisor cannot see this connection // until it reaches readiness. const onAbort = () => { if (connected) { failReady(new Error('Socket Mode connect aborted')); } void this.stop().catch((err) => { this.log?.warn('socket-mode: failed to stop an aborted connection', err); }); }; signal?.addEventListener('abort', onAbort, { once: true }); try { await connection.start(); connected = true; // An abort that landed during start won't have had a connection to stop; now // that one exists, honor it explicitly. if (signal?.aborted) { await this.stop(); throw new Error('Socket Mode connect aborted'); } this.log?.debug('socket-mode: socket connected; awaiting SocketReady'); const timeoutMs = this.context.readinessTimeoutMs; const timer = setTimeout(() => { this.log?.warn(`socket-mode: readiness timed out after ${timeoutMs}ms (no SocketReady frame)`); failReady(new Error(`Socket Mode readiness timed out after ${timeoutMs}ms (no SocketReady frame).`)); }, timeoutMs); try { await ready; } catch (err) { await this.stop(); throw err; } finally { clearTimeout(timer); } } finally { signal?.removeEventListener('abort', onAbort); } } async stop() { this.stopped = true; const connection = this.connection; this.connection = undefined; if (connection) { await connection.stop(); } } throwIfAborted(signal) { if (signal?.aborted) { throw new Error('Socket Mode connect aborted'); } } /** * Bridge the SignalR client's internal diagnostics into the app's * {@link ILogger} so they surface through the SDK's logging pipeline (and * respect its configured level) instead of SignalR's default console logger. */ signalRLogger(secrets = []) { const log = this.log; return { log: (level, message) => { const line = (0, redact_1.redactSocketModeSecrets)(`socket-mode signalr: ${message}`, secrets); switch (level) { case signalr_1.LogLevel.Critical: case signalr_1.LogLevel.Error: log?.error(line); break; case signalr_1.LogLevel.Warning: log?.warn(line); break; case signalr_1.LogLevel.Information: log?.info(line); break; case signalr_1.LogLevel.Debug: case signalr_1.LogLevel.Trace: log?.debug(line); break; default: break; } }, }; } } exports.SignalRSocketConnection = SignalRSocketConnection; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic29ja2V0LWNvbm5lY3Rpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc29ja2V0LW1vZGUvc29ja2V0LWNvbm5lY3Rpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsZ0RBQXFKO0FBR3JKLDJDQUF3QztBQUN4QyxxQ0FBbUQ7QUFTbkQ7Ozs7Ozs7O0dBUUc7QUFDSCxNQUFhLHVCQUF1QjtJQXFCZjtJQUNBO0lBQ0E7SUF0Qm5CLG1GQUFtRjtJQUNuRixnQkFBZ0IsQ0FBVTtJQUUxQiw4RUFBOEU7SUFDOUUsV0FBVyxDQUFVO0lBRXJCLGlGQUFpRjtJQUNqRixZQUFZLENBQVU7SUFFZCxVQUFVLENBQWlCO0lBQzNCLE9BQU8sR0FBRyxLQUFLLENBQUM7SUFDeEI7Ozs7O09BS0c7SUFDSyxPQUFPLEdBQUcsS0FBSyxDQUFDO0lBRXhCLFlBQ21CLE9BQWdDLEVBQ2hDLFFBQWtDLEVBQ2xDLEdBQWE7UUFGYixZQUFPLEdBQVAsT0FBTyxDQUF5QjtRQUNoQyxhQUFRLEdBQVIsUUFBUSxDQUEwQjtRQUNsQyxRQUFHLEdBQUgsR0FBRyxDQUFVO0lBQzdCLENBQUM7SUFFSiwrREFBK0Q7SUFDL0QsSUFBSSxNQUFNO1FBQ1IsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLElBQUksSUFBSSxDQUFDLFVBQVUsRUFBRSxLQUFLLEtBQUssNEJBQWtCLENBQUMsU0FBUyxDQUFDO0lBQ2xGLENBQUM7SUFFRCxLQUFLLENBQUMsS0FBSyxDQUFDLE1BQW9CO1FBQzlCLElBQUksQ0FBQyxPQUFPLEdBQUcsS0FBSyxDQUFDO1FBQ3JCLElBQUksQ0FBQyxPQUFPLEdBQUcsS0FBSyxDQUFDO1FBQ3JCLElBQUksQ0FBQyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUM7UUFFNUIsTUFBTSxHQUFHLEdBQUcsTUFBTSxJQUFBLHFCQUFTLEVBQUM7WUFDMUIsWUFBWSxFQUFFLElBQUksQ0FBQyxPQUFPLENBQUMsWUFBWTtZQUN2QyxNQUFNLEVB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