@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
TypeScript
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;
};