@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
470 lines (469 loc) • 26 kB
TypeScript
import { Activity, ActivityLike, ConversationReference, DeprecatedInputActivity, InvokeResponse, SentActivity, TokenStatus } from '@microsoft/teams.api';
import { Client as HttpClient, ILogger, IStorage } from '@microsoft/teams.common';
import { ApiClient, GraphClient } from '../api';
import { BotApiCredential, GraphCredential } from '../files/download';
import { IFilesAccessor } from '../files/types';
import { OAuthSignInOptions } from '../oauth';
import { Plan } from '../plan';
import { TurnStateContainer } from '../state';
import { IStreamer } from '../types';
import { IActivitySender } from '../types/plugin/sender';
/**
* Constructor arguments for ActivityContext
* Internal implementation details not exposed in public interface
*/
export interface IActivityContextConstructorArgs {
/**
* Graph credential used by the file path, selected per actor and resolved at fetch time.
*
* Constructor-only, and deliberately absent from {@link IBaseActivityContextOptions}: it exists so `ctx.files` can reach bytes the pre-authorized download URL cannot, and a handler has no reason to hold it. Declaring it here keeps it off the public context type and out of the instance, rather than advertising a property that is always `undefined`.
*/
filesCredential?: GraphCredential;
/**
* Bot API credential used by the file path to read inline images, resolved at fetch time.
*
* Constructor-only for the same reason as {@link filesCredential}.
*/
filesBotApiCredential?: BotApiCredential;
/**
* activity sender for sending activities and creating streams
*/
activitySender: IActivitySender;
/**
* call the next event/middleware handler
*/
next: (context?: IActivityContext) => (void | InvokeResponse) | Promise<void | InvokeResponse>;
/**
* Validates that a connection used by a deprecated OAuth helper is registered
* and was named when registered flows require it.
* @internal
*/
validateOAuthConnection?: (connectionName: string, connectionNameProvided: boolean) => void;
/**
* Records pending OAuth attribution after the deprecated `signin()` helper
* sends a card for a registered connection.
* @internal
*/
onOAuthSignInInitiated?: (context: IActivityContext, connectionName: string, supportsSso: boolean) => void | Promise<void>;
/**
* Gets corrected connection status through the app's OAuth registry.
* @internal
*/
getOAuthConnectionStatus?: (context: IActivityContext) => Promise<TokenStatus[]>;
}
/**
* Base activity context options
* These are the public properties exposed on the context
*/
export interface IBaseActivityContextOptions<T extends Activity = Activity> {
/**
* the app id of the bot
*/
appId: string;
/**
* the inbound activity
*/
activity: T;
/**
* the inbound activity conversation reference
*/
ref: ConversationReference;
/**
* the app logger instance
*/
log: ILogger;
/**
* the api client
*/
api: ApiClient;
/**
* the app's shared HTTP client, used for outbound calls that are not part of the Teams API surface
* (e.g. downloading an inbound file's bytes)
* They inherit the app's User-Agent, middleware, and configuration.
*/
client?: HttpClient;
/**
* the app graph client
*/
appGraph: GraphClient;
/**
* the user graph client
*
* @deprecated Use a registered OAuth flow to acquire the token needed by the
* Microsoft Graph operation. This property remains available for compatibility.
*/
userGraph: GraphClient;
/**
* app storage instance
*
* @deprecated Use `state` for conversation or user state. Applications that
* need general persistence should own and use their storage provider directly.
*/
storage: IStorage;
/**
* Conversation and user state loaded for this activity turn.
*
* This is undefined when state is disabled or the activity has no conversation ID.
* Do not retain the container after the handler completes; its scopes are sealed.
*/
state?: TurnStateContainer;
/**
* whether the user has provided
* their MSGraph credentials for use
* via `api.user.*`
*
* @deprecated Use `app.getOAuthFlow(connectionName).isSignedIn(ctx)`.
*/
isSignedIn?: boolean;
/**
* the default connection name to use for the app
* @default `graph`
* @deprecated Register and retain the required `OAuthFlow` explicitly.
*/
connectionName: string;
/**
* the user token for the activity context
*
* @deprecated Use `app.getOAuthFlow(connectionName).getToken(ctx)`.
*/
userToken?: string;
}
export type IActivityContextOptions<T extends Activity = Activity, TExtraCtx extends Record<string, any> = Record<string, any>> = IBaseActivityContextOptions<T> & TExtraCtx;
export interface IBaseActivityContext<T extends Activity = Activity, TExtraCtx extends Record<string, any> = Record<string, any>> extends IBaseActivityContextOptions<T> {
/**
* a stream that can emit activity chunks
*
* The first message of the stream goes to the reply chain (thread) where `send` would place a response.
* In a chat the stream starts in the thread when the inbound activity is in a thread, and at top level otherwise.
* In a channel the stream starts in the reply chain of the inbound message.
* In a channel, when the service answers HTTP 404, the first message of the stream is created in the inbound conversation instead.
* Without `channelData.replyChain.id` the stream is not placed in a reply chain.
*/
stream: IStreamer;
/**
* the uploaded files on the current inbound activity, i.e. `contentType: file.download.info` subset of `activity.attachments`, plus images pasted inline, mapped to `IncomingFile`. See {@link IFilesAccessor}.
*/
files: IFilesAccessor;
/**
* Get the Plan bound to this inbound activity. Use new Plan(options) for a saved prompt.
* In a chat thread, a standalone `complete()` is posted to the reply chain, as `ctx.send` does.
* Typing updates, completions in a channel, and prompts outside a thread use the conversation.
*
* @experimental This API is in preview and may change in the future.
* Diagnostic: ExperimentalTeamsPlan
*/
plan(): Plan;
/**
* call the next event/middleware handler
*/
next: (context?: IActivityContext & TExtraCtx) => (void | InvokeResponse) | Promise<void | InvokeResponse>;
/**
* send an activity to the conversation
* @param activity activity to send
* @deprecated Use MessageActivityInput or TypingActivityInput instead.
*/
send(activity: DeprecatedInputActivity): Promise<SentActivity>;
/**
* send an activity to the conversation
*
* In a chat, sends into the reply chain (thread) when the inbound activity is inside one, and at top level otherwise.
* The inbound activity can be a message, a card action, an invoke, a reaction, an edit or a delete.
* In a channel, sends into the reply chain of the inbound message.
* In a channel, when the service answers HTTP 404, sends to the inbound conversation instead.
* Inbound activities without `channelData.replyChain.id`, and other inbound activities, are not placed, and the activity is sent to the inbound conversation.
*
* @param activity activity to send
*/
send(activity: ActivityLike): Promise<SentActivity>;
/**
* send an activity to the conversation
*
* In a chat, sends into the reply chain (thread) when the inbound activity is inside one, and at top level otherwise.
* The inbound activity can be a message, a card action, an invoke, a reaction, an edit or a delete.
* In a channel, sends into the reply chain of the inbound message.
* In a channel, when the service answers HTTP 404, sends to the inbound conversation instead.
* Inbound activities without `channelData.replyChain.id`, and other inbound activities, are not placed, and the activity is sent to the inbound conversation.
*
* @param activity activity to send
*/
send(activity: ActivityLike | DeprecatedInputActivity): Promise<SentActivity>;
/**
* send an activity to the conversation referenced by `conversationRef`
* @param activity activity to send
* @param conversationRef conversation reference to send to
* @deprecated Passing a `DeprecatedInputActivity` is deprecated. Use MessageActivityInput or TypingActivityInput instead.
* Passing a conversation reference is also deprecated and will be removed in a future release. Use `App.send()` to send to a different conversation.
* `App.send()` sends through the `serviceUrl` the app is configured with, unlike the context methods, which use the `serviceUrl` of the conversation reference (by default, the inbound activity's).
*/
send(activity: DeprecatedInputActivity, conversationRef?: ConversationReference): Promise<SentActivity>;
/**
* send an activity to the conversation referenced by `conversationRef`
* @param activity activity to send
* @param conversationRef conversation reference to send to
* @deprecated Passing a conversation reference is deprecated and will be removed in a future release. Use `App.send()` to send to a different conversation.
* `App.send()` sends through the `serviceUrl` the app is configured with, unlike the context methods, which use the `serviceUrl` of the conversation reference (by default, the inbound activity's).
*/
send(activity: ActivityLike, conversationRef?: ConversationReference): Promise<SentActivity>;
/**
* send an activity to the conversation referenced by `conversationRef`
* @param activity activity to send
* @param conversationRef conversation reference to send to
* @deprecated Passing a conversation reference is deprecated and will be removed in a future release. Use `App.send()` to send to a different conversation.
* `App.send()` sends through the `serviceUrl` the app is configured with, unlike the context methods, which use the `serviceUrl` of the conversation reference (by default, the inbound activity's).
*/
send(activity: ActivityLike | DeprecatedInputActivity, conversationRef?: ConversationReference): Promise<SentActivity>;
/**
* @deprecated Use MessageActivityInput or TypingActivityInput instead.
*/
reply(activity: DeprecatedInputActivity): Promise<SentActivity>;
/**
* reply to the inbound activity, automatically quoting the inbound message
*
* In a chat, sends into the reply chain (thread) when the inbound activity is inside one, and at top level otherwise.
* The inbound activity can be a message, a card action, an invoke, a reaction, an edit or a delete.
* In a channel, sends into the reply chain of the inbound message.
* In a channel, when the service answers HTTP 404, sends to the inbound conversation instead.
* Inbound activities without `channelData.replyChain.id`, and other inbound activities, are not placed, and the activity is sent to the inbound conversation.
* The response is automatically quoted only when the inbound activity id is a plain numeric message id.
* Otherwise (for example an invoke or a card action with a service-generated `f:` id) the activity is sent without a quote, exactly like {@link send}.
* Explicit quotes through {@link quote} are unchanged.
*
* @param activity activity to send
*/
reply(activity: ActivityLike): Promise<SentActivity>;
/**
* reply to the inbound activity, automatically quoting the inbound message
*
* In a chat, sends into the reply chain (thread) when the inbound activity is inside one, and at top level otherwise.
* The inbound activity can be a message, a card action, an invoke, a reaction, an edit or a delete.
* In a channel, sends into the reply chain of the inbound message.
* In a channel, when the service answers HTTP 404, sends to the inbound conversation instead.
* Inbound activities without `channelData.replyChain.id`, and other inbound activities, are not placed, and the activity is sent to the inbound conversation.
* The response is automatically quoted only when the inbound activity id is a plain numeric message id.
* Otherwise (for example an invoke or a card action with a service-generated `f:` id) the activity is sent without a quote, exactly like {@link send}.
* Explicit quotes through {@link quote} are unchanged.
*
* @param activity activity to send
*/
reply(activity: ActivityLike | DeprecatedInputActivity): Promise<SentActivity>;
/**
* send a reply quoting a specific message by ID
* @param messageId the ID of the message to quote
* @param activity activity to send
*/
/**
* @deprecated Use MessageActivityInput or TypingActivityInput instead.
*/
quote(messageId: string, activity: DeprecatedInputActivity): Promise<SentActivity>;
quote(messageId: string, activity: ActivityLike): Promise<SentActivity>;
quote(messageId: string, activity: ActivityLike | DeprecatedInputActivity): Promise<SentActivity>;
/**
* trigger user signin flow for the activity sender
* @param options options for the signin flow
* @deprecated Register a connection with `app.addOAuthFlow(...)` and call
* `flow.signIn(ctx, options)` instead.
*/
signin: (options?: OAuthSignInOptions) => Promise<string | undefined>;
/**
* sign the activity sender out
* @param name auth connection name, defaults to `graph`
* @deprecated Register a connection with `app.addOAuthFlow(...)` and call
* `flow.signOut(ctx)` instead.
*/
signout: (name?: string) => Promise<void>;
/**
* Gets token status for every registered OAuth connection.
*
* Connections reported as disconnected after silent SSO are verified with a
* direct token lookup. A connected flow omitted by the status endpoint is
* returned with an empty `serviceProviderDisplayName` because that value is
* unavailable from the token response.
*/
getConnectionStatus: () => Promise<TokenStatus[]>;
}
export type IActivityContext<T extends Activity = Activity, TExtraContext = unknown> = IBaseActivityContext<T> & (TExtraContext extends Record<string, any> ? TExtraContext : {});
export declare class ActivityContext<T extends Activity = Activity, TExtraCtx extends {} = {}> implements IBaseActivityContext<T, TExtraCtx> {
appId: string;
activity: T;
ref: ConversationReference;
log: ILogger;
api: ApiClient;
appGraph: GraphClient;
userGraph: GraphClient;
/**
* @deprecated Use `state` for conversation or user state.
*/
storage: IStorage;
state?: TurnStateContainer;
stream: IStreamer;
files: IFilesAccessor;
isSignedIn?: boolean;
connectionName: string;
next: (context?: IActivityContext) => (void | InvokeResponse) | Promise<void | InvokeResponse>;
[key: string]: any;
private activitySender;
private _plan?;
private readonly validateOAuthConnection?;
private readonly onOAuthSignInInitiated?;
private readonly getOAuthConnectionStatus?;
constructor(value: IBaseActivityContextOptions & IActivityContextConstructorArgs);
/**
* Get the Plan bound to this inbound activity, scoping its client on first access.
* In a chat thread, a standalone `complete()` is posted to the reply chain, as `ctx.send` does.
* Typing updates, completions in a channel, and prompts outside a thread use the conversation.
*
* @experimental This API is in preview and may change in the future.
* Diagnostic: ExperimentalTeamsPlan
*/
plan(): Plan;
/**
* send an activity in the current conversation without quoting.
*
* @param activity the activity to send
* @deprecated Use MessageActivityInput or TypingActivityInput instead.
*/
send(activity: DeprecatedInputActivity): Promise<SentActivity>;
/**
* send an activity in the current conversation without quoting.
*
* In a chat, sends into the reply chain (thread) when the inbound activity is inside one, and at top level otherwise.
* The inbound activity can be a message, a card action, an invoke, a reaction, an edit or a delete.
* In a channel, sends into the reply chain of the inbound message.
* In a channel, when the service answers HTTP 404, sends to the inbound conversation instead.
* Inbound activities without `channelData.replyChain.id`, and other inbound activities, are not placed, and the activity is sent to the inbound conversation.
* To send with a visual quote of the inbound message, use {@link reply}.
*
* @param activity the activity to send
*/
send(activity: ActivityLike): Promise<SentActivity>;
/**
* send an activity in the current conversation without quoting.
*
* In a chat, sends into the reply chain (thread) when the inbound activity is inside one, and at top level otherwise.
* The inbound activity can be a message, a card action, an invoke, a reaction, an edit or a delete.
* In a channel, sends into the reply chain of the inbound message.
* In a channel, when the service answers HTTP 404, sends to the inbound conversation instead.
* Inbound activities without `channelData.replyChain.id`, and other inbound activities, are not placed, and the activity is sent to the inbound conversation.
* To send with a visual quote of the inbound message, use {@link reply}.
*
* @param activity the activity to send
*/
send(activity: ActivityLike | DeprecatedInputActivity): Promise<SentActivity>;
/**
* send an activity to the conversation referenced by `conversationRef`.
*
* @param activity the activity to send
* @param conversationRef conversation reference to send to
* @deprecated Passing a `DeprecatedInputActivity` is deprecated. Use MessageActivityInput or TypingActivityInput instead.
* Passing a conversation reference is also deprecated and will be removed in a future release. Use `App.send()` to send to a different conversation.
* `App.send()` sends through the `serviceUrl` the app is configured with, unlike the context methods, which use the `serviceUrl` of the conversation reference (by default, the inbound activity's).
*/
send(activity: DeprecatedInputActivity, conversationRef?: ConversationReference): Promise<SentActivity>;
/**
* send an activity to the conversation referenced by `conversationRef`.
*
* @param activity the activity to send
* @param conversationRef conversation reference to send to
* @deprecated Passing a conversation reference is deprecated and will be removed in a future release. Use `App.send()` to send to a different conversation.
* `App.send()` sends through the `serviceUrl` the app is configured with, unlike the context methods, which use the `serviceUrl` of the conversation reference (by default, the inbound activity's).
*/
send(activity: ActivityLike, conversationRef?: ConversationReference): Promise<SentActivity>;
/**
* send an activity to the conversation referenced by `conversationRef`.
*
* @param activity the activity to send
* @param conversationRef conversation reference to send to
* @deprecated Passing a conversation reference is deprecated and will be removed in a future release. Use `App.send()` to send to a different conversation.
* `App.send()` sends through the `serviceUrl` the app is configured with, unlike the context methods, which use the `serviceUrl` of the conversation reference (by default, the inbound activity's).
*/
send(activity: ActivityLike | DeprecatedInputActivity, conversationRef?: ConversationReference): Promise<SentActivity>;
/**
* @deprecated Use MessageActivityInput or TypingActivityInput instead.
*/
reply(activity: DeprecatedInputActivity): Promise<SentActivity>;
/**
* send an activity in the current conversation with a visual quote
* of the inbound message.
*
* In a chat, sends into the reply chain (thread) when the inbound activity is inside one, and at top level otherwise.
* The inbound activity can be a message, a card action, an invoke, a reaction, an edit or a delete.
* In a channel, sends into the reply chain of the inbound message.
* In a channel, when the service answers HTTP 404, sends to the inbound conversation instead.
* Inbound activities without `channelData.replyChain.id`, and other inbound activities, are not placed, and the activity is sent to the inbound conversation.
* The response is automatically quoted only when the inbound activity id is a plain numeric message id.
* Otherwise (for example an invoke or a card action with a service-generated `f:` id) the activity is sent without a quote, exactly like {@link send}.
* Explicit quotes through {@link quote} are unchanged.
* To send without quoting, use {@link send}.
*
* @param activity the activity to send
*/
reply(activity: ActivityLike): Promise<SentActivity>;
/**
* send an activity in the current conversation with a visual quote
* of the inbound message.
*
* In a chat, sends into the reply chain (thread) when the inbound activity is inside one, and at top level otherwise.
* The inbound activity can be a message, a card action, an invoke, a reaction, an edit or a delete.
* In a channel, sends into the reply chain of the inbound message.
* In a channel, when the service answers HTTP 404, sends to the inbound conversation instead.
* Inbound activities without `channelData.replyChain.id`, and other inbound activities, are not placed, and the activity is sent to the inbound conversation.
* The response is automatically quoted only when the inbound activity id is a plain numeric message id.
* Otherwise (for example an invoke or a card action with a service-generated `f:` id) the activity is sent without a quote, exactly like {@link send}.
* Explicit quotes through {@link quote} are unchanged.
* To send without quoting, use {@link send}.
*
* @param activity the activity to send
*/
reply(activity: ActivityLike | DeprecatedInputActivity): Promise<SentActivity>;
/**
* Send a message to the conversation with a quoted message reference prepended to the text.
* Teams renders the quoted message as a preview bubble above the response text.
* @param messageId - The ID of the message to quote
* @param activity - The activity to send — a quote placeholder for messageId will be prepended to its text
*/
/**
* @deprecated Use MessageActivityInput or TypingActivityInput instead.
*/
quote(messageId: string, activity: DeprecatedInputActivity): Promise<SentActivity>;
quote(messageId: string, activity: ActivityLike): Promise<SentActivity>;
quote(messageId: string, activity: ActivityLike | DeprecatedInputActivity): Promise<SentActivity>;
signin(options?: OAuthSignInOptions): Promise<string | undefined>;
signout(connectionName?: string): Promise<void>;
getConnectionStatus(): Promise<TokenStatus[]>;
toInterface(): IActivityContext;
private isIncomingTargeted;
private shouldOutboundBeAutoTargeted;
/**
* Finds the chat or channel and reply chain (thread) a response to the inbound activity is posted to.
*
* In a channel a response to a message always goes to the reply chain of the inbound message: `channelData.replyChain.id` is the root message of the thread, and it equals the inbound message's own id for a top-level post.
* The destination is the channel, `channelData.channel.id`.
* Other inbound activities in a channel are not placed.
*
* In a chat see `isInsideThread` for which activities are inside a thread.
* The destination is the chat, `channelData.chat.id`.
* When the inbound activity is on a top-level message the response is sent as a top-level message.
*
* Placement depends only on the payload: without `channelData.replyChain.id` and the chat or channel id the activity is not placed.
* Returns undefined when an id is missing or blank, so the send goes to the inbound conversation.
*/
private getInboundReplyChainPlacement;
/**
* Whether the inbound activity in a chat is inside a thread: `channelData.replyChain.id` differs from the id of the message the activity acts on.
* The message it acts on is:
* - `activity.id` for a message, an edit and a delete.
* - `replyToId` (the reacted message) for a reaction and for a message sent by a card button (a message with `channelData.source`).
* - `value.messagePayload.id` for a message action, and `replyToId` (the message the user acted on, for example a card or a message with feedback buttons) for any other invoke.
*
* An invoke that is not tied to a message (for example a compose-box extension or a `task/fetch` from a bot command) is inside a thread when `channelData.replyChain.id` is present.
* A `replyToId` or `messagePayload.id` that is not a plain number is never inside a thread, and neither are other inbound activities.
* A `source` without a non-empty string `name` and a `replyToId` that is null count as absent.
*/
private isInsideThread;
private getChatReplyChainPlacement;
private isSameConversation;
private applyTargetedRecipient;
private isTargetedOutbound;
private stripQuotedReplyMetadata;
private addTargetedMessageInfo;
}