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