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

470 lines (469 loc) • 26 kB
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; }