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

218 lines (217 loc) • 9.94 kB
import { Client as HttpClient } from '@microsoft/teams.common'; /** * The Socket Mode protocol version teams.ts speaks. Every reply frame must carry * it so Teams backend service can detect a mismatch. Bump only in lockstep * with Teams backend service's `SocketProtocol.CurrentVersion`. */ export declare const SOCKET_MODE_PROTOCOL_VERSION = 1; /** * The negotiate route appended to the Teams backend service base URL. Teams backend service exposes the Socket Mode * handshake at `POST {base}/v3/websockets/connect`. */ export declare const SOCKET_MODE_NEGOTIATE_PATH = "/v3/websockets/connect"; /** * Lifecycle status of the {@link SocketModeAdapter} socket. * * - `idle` — created but not yet started. * - `connecting` — negotiating and opening the socket, or reconnecting. * - `ready` — socket is open AND the Teams backend service `SocketReady` readiness contract has * been satisfied; inbound activities can be delivered. * - `disconnected` — the socket closed. A reconnect may be in progress, unless * negotiate was rejected with HTTP 401/403, in which case reconnecting stops * until the app is restarted with corrected credentials or access. * - `stopped` — the server was stopped and will not reconnect. * * @experimental This API is in preview and may change in the future. */ export type SocketModeStatus = 'idle' | 'connecting' | 'ready' | 'disconnected' | 'stopped'; /** * Options controlling the inbound Socket Mode transport, passed as * `new App({ socketMode: { ... } })`. Passing `socketMode: true` uses all * defaults. * WebSocket is only recommended for use when developing agents. * Socket Mode is currently only available for new bots. * * @experimental This API is in preview and may change in the future. */ export type SocketModeOptions = { /** * Geographies to connect to. One independent socket is opened per geo, with * the geo prefixed to the SDK-owned negotiate hostname * (`https://{geo}.botapi.skype.com/v3/websockets/connect`), so a single bot has * inbound coverage across regions — a conversation pinned to another geo is * still delivered over the socket instead of silently falling back to HTTP. * * Omitted or empty lists default to `['amer', 'emea', 'apac']`. Every listed * geo must connect for `App.start()` to succeed; each is then supervised * independently, so one geo dropping never affects delivery on the others. * Geo values must be non-empty and case-insensitively unique. * * @default ['amer', 'emea', 'apac'] */ readonly geos?: readonly string[]; }; /** * Result of a successful Teams backend service negotiate call. Mirrors the Teams backend service response body. */ export type NegotiateResult = { /** Azure SignalR URL to open the socket against. */ readonly url: string; /** Bearer access token for the Azure SignalR connection. */ readonly accessToken: string; /** Token lifetime in seconds. */ readonly expiresIn: number; }; /** * The `SocketReady` frame Teams backend service pushes once the connection is registered in the * bot's group and inbound delivery can begin. */ export type SocketReadyFrame = { readonly botKey?: string; readonly connectionId?: string; }; /** * The envelope Teams backend service delivers on the SignalR `Activity` client method. Field * casing may vary with the hub protocol, so always read via the * `readEnvelope*` helpers rather than by direct property access. */ export type SocketActivityEnvelope = { /** Protocol version Teams backend service used to encode this envelope. */ readonly protocolVersion?: number; /** Correlation id echoed back on the reply frame. */ readonly envelopeId?: string; /** `"invoke"` for invoke activities; the activity type otherwise. */ readonly type?: string; /** * Whether Teams backend service expects a delivery acknowledgement. `true` for one-way * activities; invoke activities expect a full result instead. */ readonly ackRequired?: boolean; /** The Bot Framework activity payload. */ readonly payload?: unknown; /** Optional activity payload alias used by some Teams backend service builds. */ readonly activity?: unknown; /** MS-CV correlation vector for log stitching. */ readonly cv?: string; /** * Bot key the backend service addressed this delivery to. Informational only: * reply frames always carry the locally configured bot's client id instead. */ readonly botKey?: string; /** * Nonnegative, finite reply budget in milliseconds from this message's arrival. * Bounds retired-socket draining until the SDK returns its reply/ack; the * five-second replacement-readiness grace always applies. Omitted or invalid * budgets do not extend that grace. This is not a handler-cancellation timeout. */ readonly deadlineMs?: number; /** Delivery headers forwarded by the backend service. */ readonly headers?: unknown; }; /** * The frame the bot RETURNS from its `Activity` handler (SignalR client * results). For an invoke it carries the invoke `status`/`body`; for a one-way * activity it is a minimal post-handler acknowledgement (status 200, no body). */ export type ReplyFrame = { /** Always {@link SOCKET_MODE_PROTOCOL_VERSION}. */ readonly protocolVersion: number; /** Echoes the inbound {@link SocketActivityEnvelope.envelopeId}. */ readonly envelopeId?: string; /** Bot's client id, echoed for Teams backend service routing/validation. */ readonly botKey?: string; /** HTTP-style status for the invoke result or delivery ack. */ readonly status: number; /** Invoke response body; omitted for a bare acknowledgement. */ readonly body?: unknown; /** When the reply was produced (epoch ms), for latency telemetry. */ readonly ts?: number; /** When the envelope was received (epoch ms), echoed for telemetry. */ readonly recvAt?: number; }; /** * Abstraction over a single underlying socket connection (one negotiate + * SignalR session) so the server can be unit-tested without a live SignalR * connection. The default implementation wraps `@microsoft/signalr`. * * A connection represents exactly one generation: it never reconnects itself. * When the socket drops it reports {@link SocketConnectionHandlers.onClosed} and * becomes terminal; the {@link SocketModeAdapter} supervisor decides whether to * negotiate a fresh connection. */ export interface ISocketConnection { /** * Open the connection and resolve once the readiness contract (`SocketReady`) * is satisfied. Rejects if the socket fails to open, readiness is not reached * within the configured timeout, or `signal` aborts first. * * @param signal aborts an in-progress negotiate/connect/readiness wait when * the server is stopping, so a late-completing connect can't come up after * shutdown. */ start(signal?: AbortSignal): Promise<void>; /** Close the connection. Safe to call more than once. */ stop(): Promise<void>; /** * Lifetime, in seconds, of the negotiate access token backing this * connection (from the negotiate response), or `undefined` when unknown. The * supervisor uses it to proactively renegotiate before the token expires. */ readonly expiresInSeconds?: number; /** * Local token-expiry deadline in epoch milliseconds, based on negotiate * response receipt plus its lifetime, before socket readiness. `undefined` * when unknown. Expiry disallows replacement headers, not inbound delivery. */ readonly expiresAtMs?: number; /** * Server identity from this socket's first accepted `SocketReady` frame, * available before `onReady` and retained after close. `undefined` until * readiness or when the server omits the ID. */ readonly connectionId?: string; /** Whether the transport is open. Only `true` permits a replacement header. */ readonly isOpen?: boolean; } /** * Callbacks the server supplies to the underlying {@link ISocketConnection} so * the connection can drive the server without the server knowing the transport. */ export type SocketConnectionHandlers = { /** * Handle one inbound envelope and resolve with the reply frame to return over * client results, or `undefined` to send no reply. Settlement ends SDK drain * tracking; it does not confirm transport transmission or service receipt. */ readonly onActivity: (envelope: SocketActivityEnvelope) => Promise<ReplyFrame | undefined>; /** Called when Teams backend service pushes the `SocketReady` readiness frame. */ readonly onReady: (frame: SocketReadyFrame) => void; /** * Called once when the connection terminates (socket closed or lost). The * connection is terminal after this — the supervisor handles any reconnect by * building a new connection. */ readonly onClosed: (error?: Error) => void; }; /** * Everything the {@link ISocketConnection} needs to negotiate and open a single * connection. */ export type SocketConnectionContext = { /** Resolved regional negotiate URL. */ readonly negotiateUrl: string; /** The app's shared HTTP client used for the negotiate request. */ readonly client: HttpClient; /** * Acquire the Bot Framework access token used to authenticate the negotiate * request. Reuses the app's existing credentials. */ readonly getBotToken: () => Promise<string>; /** * Planned-refresh predecessor's exact ID, checked just before the POST. * Return `undefined` if it has closed, its token expired, or its ID is unavailable. */ readonly getConnectionIdToReplace?: () => string | undefined; /** Readiness timeout in milliseconds. */ readonly readinessTimeoutMs: number; };