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

389 lines • 32 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SocketModeAdapter = void 0; const teams_common_1 = require("@microsoft/teams.common"); const envelope_1 = require("./envelope"); const geo_socket_1 = require("./geo-socket"); const negotiate_1 = require("./negotiate"); const socket_connection_1 = require("./socket-connection"); const types_1 = require("./types"); const DEFAULT_READINESS_TIMEOUT_MS = 30_000; const DEFAULT_STARTUP_TIMEOUT_MS = 30_000; /** Reconnect back-off bounds: capped exponential with full jitter. */ const RECONNECT_INITIAL_DELAY_MS = 1_000; const RECONNECT_MAX_DELAY_MS = 15_000; /** Renegotiate this long before the negotiate token is due to expire. */ const TOKEN_REFRESH_MARGIN_MS = 60_000; const SOCKET_MODE_NEGOTIATE_HOST = 'botapi.skype.com'; /** * Default geographies a bot connects to. One socket is opened per geo (the geo * is prefixed to the negotiate hostname), so a single bot has * inbound coverage across regions rather than only the geo it happened to dial. */ const DEFAULT_GEOS = ['amer', 'emea', 'apac']; /** Extract a service-provided Retry-After (ms) from a negotiate failure. */ function retryAfterFrom(error) { return error instanceof negotiate_1.NegotiateError ? error.retryAfterMs : undefined; } /** A 401/403 from negotiate cannot succeed on retry until credentials or access change. */ function isNonRetryableError(error) { return error instanceof negotiate_1.NegotiateError && error.isAuthError; } /** * The supplied geo is added as a subdomain before the negotiate path. */ function buildNegotiateUrl(geo) { return `https://${geo}.${SOCKET_MODE_NEGOTIATE_HOST}${types_1.SOCKET_MODE_NEGOTIATE_PATH}`; } /** * Inbound Socket Mode transport, implemented as an {@link IHttpServerAdapter}. * * With `new App({ socketMode: true })` the app receives activities over a * Teams backend service-negotiated Azure SignalR socket. Rather than being a * separate server, Socket Mode plugs in as the adapter *inside* the app's single * {@link HttpServer}. Inbound frames enter the same app activity pipeline as * HTTP after the connection-authenticated transport creates their normalized * token. Only inbound delivery moves to the socket; outbound sends stay on HTTP * and handlers stay transport-agnostic. * * No inbound HTTP adapter, listener, route, or public endpoint is started. The * socket is authenticated once at negotiate, so the adapter synthesizes the * {@link IToken} the pipeline expects without exposing transport trust through * the public HTTP request interface. Invoke activities return a real invoke * response over SignalR client results; one-way activities return a post-handler * ack. Not usable until Teams backend service's `SocketReady` frame arrives. * * Browser-oriented calls are no-ops: tabs, remote functions, OAuth callbacks, * and other non-messaging routes require an HTTP transport. * WebSocket is only recommended for use when developing agents. * * @experimental This API is in preview and may change in the future. */ class SocketModeAdapter { options; deps; /** * Lifecycle event surface for observing the socket without reaching into the * transport. Emits `ready`, `disconnected`, and `reconnected`. */ events = new teams_common_1.EventEmitter(); log; _lifecycle = 'idle'; transportSettings = { startupTimeoutMs: DEFAULT_STARTUP_TIMEOUT_MS, readinessTimeoutMs: DEFAULT_READINESS_TIMEOUT_MS, }; /** One independent supervised connection per geo. */ geoSockets = []; /** Aborts all in-flight negotiate/connect/back-off waits when stopping. */ abort; stopped = false; constructor(options = {}, deps) { this.options = options; this.deps = deps; this.log = deps.logger ?? new teams_common_1.ConsoleLogger('SocketModeAdapter'); } /** * Aggregate lifecycle status across all geo connections. `ready` means every * geo is ready; `connecting` while any geo is still (re)connecting and none is * mid-drop; `disconnected` when at least one geo has dropped and is recovering, * or has stopped reconnecting after a 401/403 negotiate rejection. */ get status() { if (this._lifecycle === 'idle') return 'idle'; if (this._lifecycle === 'stopped') return 'stopped'; const states = this.geoSockets.map((g) => g.status); if (states.length > 0 && states.every((s) => s === 'ready')) return 'ready'; if (states.some((s) => s === 'disconnected')) return 'disconnected'; return 'connecting'; } /** Per-geo status snapshot, for diagnostics/observability. */ get geoStatuses() { return this.geoSockets.map((g) => ({ geo: g.geo, status: g.status })); } /** The geos this server connects to (resolved from options/defaults). */ get geoList() { return this.resolveGeos(); } /** * {@link IHttpServerAdapter} route registration. The owning {@link HttpServer} * registers its inbound-activity route here as part of normal server * initialization. Socket Mode dispatches through its internal app callback, so * the messaging route is accepted as a no-op. Any other path (e.g. * `app.function()` POST routes) is also a no-op, but is warned because the * corresponding HTTP-only feature is unavailable. */ registerRoute(method, path, _handler) { if (method === 'POST' && path === this.deps.messagingEndpoint) { return; } const message = `socket-mode: ignoring ${method} ${path} — Socket Mode serves only the messaging endpoint. ` + 'Browser features (app.function()/app.tab()/OAuth routes) are unavailable.'; this.log.warn(message); } /** * {@link IHttpServerAdapter} static-file serving. No-op for Socket Mode — tabs * and other static assets require an HTTP transport, so the ignored mount is * warned. */ serveStatic(path, _directory) { const message = `socket-mode: ignoring serveStatic(${path}) — Socket Mode has no HTTP transport for static ` + 'files; app.tab() is unavailable.'; this.log.warn(message); } /** * {@link IHttpServerAdapter} lifecycle: open one socket per geo, resolving only * once every geo's readiness contract is satisfied. Called by {@link App.start} * via {@link HttpServer.start}. The `port` argument is unused — Socket Mode * dials out rather than listening. * * The initial connect for each geo is retried for up to 30 seconds before * `App.start()` rejects; once all geos are ready, each geo's supervisor keeps * it alive across drops and token expiry independently until {@link stop}. */ async start(_port) { const geos = this.resolveGeos(); const negotiateUrls = geos.map(buildNegotiateUrl); this.stopped = false; this._lifecycle = 'starting'; this.abort = new AbortController(); this.geoSockets = geos.map((geo, index) => this.createGeoSocket(geo, negotiateUrls[index])); this.log.info(`socket-mode: connecting to Socket Mode across ${geos.length} geo(s): ${geos.join(', ')}`); // Every geo must establish its first connection for start to succeed; a geo // that can't connect within the startup budget fails App.start(), which then // tears everything down. Post-startup drops are handled per-geo by supervisors. try { await Promise.all(this.geoSockets.map((g) => g.startInitial())); } catch (err) { await this.stop(); throw err; } this._lifecycle = 'started'; this.log.info('socket-mode: Socket Mode ready (inbound activities over WebSocket)'); for (const geoSocket of this.geoSockets) geoSocket.superviseInBackground(); } /** * {@link IHttpServerAdapter} lifecycle: stop admitting activities, abort any * reconnect/back-off in progress, then close every geo's socket. In-flight * handlers are not awaited: any activity whose reply doesn't make it out before * shutdown is redelivered by the Teams backend to another connection/instance. */ async stop() { if (this.stopped) return; this.stopped = true; this._lifecycle = 'stopped'; this.log.info('socket-mode: stopping Socket Mode'); this.abort?.abort(); await Promise.all(this.geoSockets.map((geoSocket) => geoSocket.stop().catch((err) => { this.log.debug(`socket-mode[${geoSocket.geo}]: failed to stop geo socket`, err); }))); } /** Build one geo supervisor from the adapter's narrow internal callbacks. */ createGeoSocket(geo, negotiateUrl) { return new geo_socket_1.GeoSocket({ isAccepting: () => !this.stopped, getAbortSignal: () => this.abort?.signal, createConnection: (negotiateUrl, handlers, getConnectionIdToReplace) => this.createConnection(negotiateUrl, handlers, getConnectionIdToReplace), dispatch: (envelope) => this.dispatch(envelope), retryAfterOf: (error) => this.retryAfterOf(error), isNonRetryable: (error) => isNonRetryableError(error), backoffDelay: (attempt) => this.backoffDelay(attempt), sleep: (ms) => this.sleep(ms), onReady: (frame) => this.emit('ready', { geo, frame }), onDisconnected: (error, terminal) => this.emit('disconnected', { geo, error, terminal }), onReconnected: () => this.emit('reconnected', { geo }), startupTimeoutMs: this.transportSettings.startupTimeoutMs, tokenRefreshMarginMs: TOKEN_REFRESH_MARGIN_MS, }, geo, negotiateUrl, this.log); } /** Build a connection for a geo. */ createConnection(negotiateUrl, handlers, getConnectionIdToReplace) { return new socket_connection_1.SignalRSocketConnection({ negotiateUrl, client: this.deps.client, getBotToken: () => this.acquireBotToken(), getConnectionIdToReplace, readinessTimeoutMs: this.transportSettings.readinessTimeoutMs, }, handlers, this.log); } /** Back-off delay (ms) for a reconnect attempt (shared policy across geos). */ backoffDelay(attempt) { const schedule = this.transportSettings.reconnectDelaysMs; if (schedule && schedule.length > 0) { return schedule[Math.min(attempt, schedule.length - 1)]; } const capped = Math.min(RECONNECT_INITIAL_DELAY_MS * 2 ** attempt, RECONNECT_MAX_DELAY_MS); return Math.floor(Math.random() * capped); // full jitter } /** Abortable delay; resolves `true` when it completed, `false` if aborted. */ sleep(ms) { if (ms <= 0) return Promise.resolve(true); return new Promise((resolve) => { const signal = this.abort?.signal; if (signal?.aborted) { resolve(false); return; } const timer = setTimeout(() => { signal?.removeEventListener('abort', onAbort); resolve(true); }, ms); const onAbort = () => { clearTimeout(timer); resolve(false); }; signal?.addEventListener('abort', onAbort, { once: true }); }); } retryAfterOf(error) { return retryAfterFrom(error); } /** Emit a lifecycle event without letting a throwing listener break state. */ emit(event, payload) { try { this.events.emit(event, payload); } catch (err) { this.log.warn(`socket-mode: a '${String(event)}' event listener threw`, err); } } /** * Dispatch one inbound envelope after the geo supervisor applies its * generation/readiness/admission fence. There is no concurrency cap or * admission queue: SignalR already invokes the handler per activity, the Teams * backend load-balances delivery across connections/instances, and each * activity carries a reply deadline, so an in-SDK cap would mostly produce * work whose reply is discarded. */ async dispatch(envelope) { return await this.handleEnvelope(envelope); } /** The configured geos, defaulting to amer/emea/apac. */ resolveGeos() { const geos = this.options.geos?.length ? this.options.geos : DEFAULT_GEOS; geos.forEach((geo, index) => { if (geo.length === 0) { throw new Error(`socketMode.geos[${index}] must not be empty.`); } }); if (new Set(geos.map((geo) => geo.toLowerCase())).size !== geos.length) { throw new Error('socketMode.geos must not contain duplicates.'); } return geos; } /** * Handle one inbound envelope: feed the embedded activity and an internally * synthesized token into the app pipeline, then return the reply frame to send * back over client results. * * Invoke activities return the pipeline's status/body; one-way activities * return a post-handler acknowledgement once the pipeline has run. */ async handleEnvelope(envelope) { const base = (0, envelope_1.replyFrameBase)(envelope, this.botId); // Reject an envelope declaring a protocol version newer than we support // BEFORE dispatch: a future major version may change the reply contract, so // running the handler and replying with our v1 frame could be misinterpreted. // Absent/lower versions are treated as current for backward compatibility. const declaredVersion = Number((0, envelope_1.readField)(envelope, 'protocolVersion')); if (Number.isFinite(declaredVersion) && declaredVersion > types_1.SOCKET_MODE_PROTOCOL_VERSION) { this.log.warn(`socket-mode: rejecting envelope with unsupported protocolVersion=${declaredVersion} ` + `(supported=${types_1.SOCKET_MODE_PROTOCOL_VERSION}) envelopeId=${base.envelopeId ?? ''}`); return { protocolVersion: types_1.SOCKET_MODE_PROTOCOL_VERSION, envelopeId: base.envelopeId, botKey: base.botKey, status: 400, body: { error: `unsupported protocolVersion ${declaredVersion}` }, recvAt: base.recvAt, ts: Date.now(), }; } const activity = (0, envelope_1.readEnvelopeActivity)(envelope); if (!activity) { this.log.warn('socket-mode: inbound envelope had no activity payload; dropping'); return undefined; } const invoke = (0, envelope_1.isInvokeEnvelope)(envelope); this.log.debug(`socket-mode: recv kind=${invoke ? 'invoke' : 'activity'} type=${activity.type} envelopeId=${base.envelopeId ?? ''}`); try { const response = await this.deps.processActivity({ body: activity, token: this.inboundToken(activity), }); const reply = invoke ? (0, envelope_1.buildInvokeReplyFrame)(base, response) : (0, envelope_1.buildAckReplyFrame)(base, response.status); this.log.debug(`socket-mode: reply produced kind=${invoke ? 'invoke' : 'ack'} status=${reply.status} envelopeId=${base.envelopeId ?? ''}`); return reply; } catch (error) { this.log.error(`socket-mode: failed to process an inbound activity type=${activity.type} envelopeId=${base.envelopeId ?? ''}`, error); await this.reportError(error); return { protocolVersion: types_1.SOCKET_MODE_PROTOCOL_VERSION, envelopeId: base.envelopeId, botKey: base.botKey, status: 500, body: invoke ? { error: 'bot handler error' } : undefined, recvAt: base.recvAt, ts: Date.now(), }; } } /** * Invoke the optional {@link SocketModeAdapterDeps.onError} hook, awaiting it * (behind an error boundary) so an async handler completes before we return * the reply frame, and a throwing/rejecting hook can't crash the transport. */ async reportError(error) { if (!this.deps.onError) return; try { await this.deps.onError(error); } catch (hookError) { this.log.warn('socket-mode: onError hook threw; ignoring', hookError); } } /** The bot's client id, echoed on reply frames for Teams backend service routing. */ get botId() { return this.deps.credentials?.clientId; } /** * Acquire the Bot Framework negotiate token (string form) via the app's token * provider, so Socket Mode reuses the same credential flow as the rest of the SDK. */ async acquireBotToken() { const token = await this.deps.tokenProvider.getAppToken(); if (token == null) { throw new Error('Socket Mode could not acquire a Bot Framework app token. Check that the app credentials are configured.'); } return token.toString(); } /** * Build the normalized token expected by the shared activity pipeline. Socket * authentication is established at negotiation, so there is no per-activity * bearer token to validate or forward. */ inboundToken(activity) { return { appId: this.botId ?? '', from: 'azure', fromId: '', serviceUrl: activity.serviceUrl ?? '', toString: () => '', isExpired: () => false, }; } } exports.SocketModeAdapter = SocketModeAdapter; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic29ja2V0LW1vZGUtYWRhcHRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zb2NrZXQtbW9kZS9zb2NrZXQtbW9kZS1hZGFwdGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUNBLDBEQU1pQztBQU1qQyx5Q0FPb0I7QUFDcEIsNkNBQXlDO0FBQ3pDLDJDQUE2QztBQUM3QywyREFBOEQ7QUFDOUQsbUNBVWlCO0FBRWpCLE1BQU0sNEJBQTRCLEdBQUcsTUFBTSxDQUFDO0FBQzVDLE1BQU0sMEJBQTBCLEdBQUcsTUFBTSxDQUFDO0FBRTFDLHNFQUFzRTtBQUN0RSxNQUFNLDBCQUEwQixHQUFHLEtBQUssQ0FBQztBQUN6QyxNQUFNLHNCQUFzQixHQUFHLE1BQU0sQ0FBQztBQUV0Qyx5RUFBeUU7QUFDekUsTUFBTSx1QkFBdUIsR0FBRyxNQUFNLENBQUM7QUFDdkMsTUFBTSwwQkFBMEIsR0FBRyxrQkFBa0IsQ0FBQztBQUV0RDs7OztHQUlHO0FBQ0gsTUFBTSxZQUFZLEdBQUcsQ0FBQyxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sQ0FBVSxDQUFDO0FBRXZELDRFQUE0RTtBQUM1RSxTQUFTLGNBQWMsQ0FBQyxLQUFjO0lBQ3BDLE9BQU8sS0FBSyxZQUFZLDBCQUFjLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztBQUMxRSxDQUFDO0FBRUQsMkZBQTJGO0FBQzNGLFNBQVMsbUJBQW1CLENBQUMsS0FBYztJQUN6QyxPQUFPLEtBQUssWUFBWSwwQkFBYyxJQUFJLEtBQUssQ0FBQyxXQUFXLENBQUM7QUFDOUQsQ0FBQztBQUVEOztHQUVHO0FBQ0gsU0FBUyxpQkFBaUIsQ0FBQyxHQUFXO0lBQ3BDLE9BQU8sV0FBVyxHQUFHLElBQUksMEJBQTBCLEdBQUcsa0NBQTBCLEVBQUUsQ0FBQztBQUNyRixDQUFDO0FBNkVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXVCRztBQUNI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