UNPKG

botbuilder-core

Version:

Core components for Microsoft Bot Builder. Components in this library can run either in a browser or on the server.

1,092 lines (1,013 loc) 48.5 kB
/** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ import { ActivityHandlerBase } from './activityHandlerBase'; import { InvokeException } from './invokeException'; import { InvokeResponse } from './invokeResponse'; import { TurnContext } from './turnContext'; import { verifyStateOperationName, tokenExchangeOperationName, tokenResponseEventName } from './signInConstants'; import { Activity, AdaptiveCardInvokeResponse, AdaptiveCardInvokeValue, Channels, SearchInvokeResponse, SearchInvokeValue, MessageReaction, StatusCodes, } from 'botframework-schema'; /** * Describes a bot activity event handler, for use with an [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * * @remarks * **Parameters** * * | Name | Type | Description | * | :--- | :--- | :--- | * | `context` | [TurnContext](xref:botbuilder-core.TurnContext) | The context object for the turn. | * | `next` | () => Promise<void> | A continuation function for handling the activity. | * * **Returns** * * `any` * * The incoming activity is contained in the `context` object's [activity](xref:botbuilder-core.TurnContext.activity) property. * Call the `next` function to continue the processing of activity events. Not doing so will stop propagation of events for this activity. * * A bot activity handler can return a value, to support _invoke_ activities. */ export type BotHandler = (context: TurnContext, next: () => Promise<void>) => Promise<any>; /** * Event-emitting activity handler for bots. Extends [ActivityHandlerBase](xref:botbuilder-core.ActivityHandlerBase). * * @remarks * This provides an extensible class for handling incoming activities in an event-driven way. * You can register an arbitrary set of handlers for each event type. * * To register a handler for an event, use the corresponding _on event_ method. If multiple handlers are * registered for an event, they are run in the order in which they were registered. * * This object emits a series of _events_ as it processes an incoming activity. * A handler can stop the propagation of the event by not calling the continuation function. * * | Event type | Description | * | :--- | :--- | * | Turn | Emitted first for every activity. | * | Type-specific | Emitted for the specific activity type, before emitting an event for any sub-type. | * | Sub-type | Emitted for certain specialized events, based on activity content. | * | Dialog | Emitted as the final activity processing event. | * * For example: * * ```typescript * const bot = new ActivityHandler(); * * server.post('/api/messages', (req, res) => { * adapter.processActivity(req, res, async (context) => { * // Route to bot's activity logic. * await bot.run(context); * }); * }); * * bot.onTurn(async (context, next) => { * // Handle a "turn" event. * await context.sendActivity(`${ context.activity.type } activity received.`); * // Continue with further processing. * await next(); * }) * .onMessage(async (context, next) => { * // Handle a message activity. * await context.sendActivity(`Echo: ${ context.activity.text }`); * // Continue with further processing. * await next(); * }); * ``` * * **See also** * - The [Bot Framework Activity schema](https://aka.ms/botSpecs-activitySchema) */ export class ActivityHandler extends ActivityHandlerBase { protected readonly handlers: { [type: string]: BotHandler[] } = {}; /** * Registers an activity event handler for the _turn_ event, emitted for every incoming activity, regardless of type. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. */ onTurn(handler: BotHandler): this { return this.on('Turn', handler); } /** * Registers an activity event handler for the _message_ event, emitted for every incoming message activity. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * Message activities represent content intended to be shown within a conversational interface * and can contain text, speech, interactive cards, and binary or unknown attachments. * Not all message activities contain text, the activity's [text](xref:botframework-schema.Activity.text) * property can be `null` or `undefined`. */ onMessage(handler: BotHandler): this { return this.on('Message', handler); } /** * Registers an activity event handler for the _message update_ event, emitted for every incoming message activity. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * Message update activities represent an update of an existing message activity within a conversation. * The updated activity is referred to by the [id](xref:botbuilder-core.TurnContext.activity.id) and * [conversation](xref:botbuilder-core.TurnContext.activity.conversation) fields within the activity, and the * message update activity contains all fields in the revised message activity. * Message update activities are identified by a [type](xref:botbuilder-core.TurnContext.activity.type) value of * `messageUpdate`. */ onMessageUpdate(handler: BotHandler): this { return this.on('MessageUpdate', handler); } /** * Registers an activity event handler for the _message delete_ event, emitted for every incoming message activity. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * Message delete activities represent a deletion of an existing message activity within a conversation. * The deleted activity is referred to by the [id](xref:botbuilder-core.TurnContext.activity.id) and * [conversation](xref:botbuilder-core.TurnContext.activity.conversation) fields within the activity. * Message delete activities are identified by a [type](xref:botbuilder-core.TurnContext.activity.type) value of * `messageDelete`. */ onMessageDelete(handler: BotHandler): this { return this.on('MessageDelete', handler); } /** * Registers an activity event handler for the _conversation update_ event, emitted for every incoming * conversation update activity. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * Conversation update activities describe a changes to a conversation's metadata, such as title, participants, * or other channel-specific information. * * To handle when members are added to or removed from the conversation, use the * [onMembersAdded](xref:botbuilder-core.ActivityHandler.onMembersAdded) and * [onMembersRemoved](xref:botbuilder-core.ActivityHandler.onMembersRemoved) sub-type event handlers. */ onConversationUpdate(handler: BotHandler): this { return this.on('ConversationUpdate', handler); } /** * Registers an activity event handler for the _members added_ event, emitted for any incoming * conversation update activity that includes members added to the conversation. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * The activity's [membersAdded](xref:botframework-schema.Activity.membersAdded) property * contains the members added to the conversation, which can include the bot. * * To handle conversation update events in general, use the * [onConversationUpdate](xref:botbuilder-core.ActivityHandler.onConversationUpdate) type-specific event handler. */ onMembersAdded(handler: BotHandler): this { return this.on('MembersAdded', handler); } /** * Registers an activity event handler for the _members removed_ event, emitted for any incoming * conversation update activity that includes members removed from the conversation. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * The activity's [membersRemoved](xref:botframework-schema.Activity.membersRemoved) property * contains the members removed from the conversation, which can include the bot. * * To handle conversation update events in general, use the * [onConversationUpdate](xref:botbuilder-core.ActivityHandler.onConversationUpdate) type-specific event handler. */ onMembersRemoved(handler: BotHandler): this { return this.on('MembersRemoved', handler); } /** * Registers an activity event handler for the _message reaction_ event, emitted for every incoming * message reaction activity. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * Message reaction activities represent a social interaction on an existing message activity * within a conversation. The original activity is referred to by the message reaction activity's * [replyToId](xref:botframework-schema.Activity.replyToId) property. The * [from](xref:botframework-schema.Activity.from) property represents the source of the reaction, * such as the user that reacted to the message. * * To handle when reactions are added to or removed from messages in the conversation, use the * [onReactionsAdded](xref:botbuilder-core.ActivityHandler.onReactionsAdded) and * [onReactionsRemoved](xref:botbuilder-core.ActivityHandler.onReactionsRemoved) sub-type event handlers. */ onMessageReaction(handler: BotHandler): this { return this.on('MessageReaction', handler); } /** * Registers an activity event handler for the _reactions added_ event, emitted for any incoming * message reaction activity that describes reactions added to a message. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * The activity's [reactionsAdded](xref:botframework-schema.Activity.reactionsAdded) property * includes one or more reactions that were added. * * To handle message reaction events in general, use the * [onMessageReaction](xref:botbuilder-core.ActivityHandler.onMessageReaction) type-specific event handler. */ onReactionsAdded(handler: BotHandler): this { return this.on('ReactionsAdded', handler); } /** * Registers an activity event handler for the _reactions removed_ event, emitted for any incoming * message reaction activity that describes reactions removed from a message. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * The activity's [reactionsRemoved](xref:botframework-schema.Activity.reactionsRemoved) property * includes one or more reactions that were removed. * * To handle message reaction events in general, use the * [onMessageReaction](xref:botbuilder-core.ActivityHandler.onMessageReaction) type-specific event handler. */ onReactionsRemoved(handler: BotHandler): this { return this.on('ReactionsRemoved', handler); } /** * Registers an activity event handler for the _event_ event, emitted for every incoming event activity. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * Event activities communicate programmatic information from a client or channel to a bot. * The meaning of an event activity is defined by the activity's * [name](xref:botframework-schema.Activity.name) property, which is meaningful within the scope * of a channel. Event activities are designed to carry both interactive information (such as * button clicks) and non-interactive information (such as a notification of a client * automatically updating an embedded speech model). * * To handle a `tokens/response` event event, use the * [onTokenResponseEvent](xref:botbuilder-core.ActivityHandler.onTokenResponseEvent) sub-type * event handler. To handle other named events, add logic to this handler. */ onEvent(handler: BotHandler): this { return this.on('Event', handler); } /** * Registers an activity event handler for the _end of conversation_ activity. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * This activity is typically send from a Skill to a Skill caller indicating the end of that particular child conversation. * * To handle an End of Conversation, use the * [onEndOfConversation](xref:botbuilder-core.ActivityHandler.onEndOfConversation) type-specific event handler. */ onEndOfConversation(handler: BotHandler): this { return this.on('EndOfConversation', handler); } /** * Registers an activity event handler for the _typing_ activity. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * To handle a Typing event, use the * [onTyping](xref:botbuilder-core.ActivityHandler.onTyping) type-specific event handler. */ onTyping(handler: BotHandler): this { return this.on('Typing', handler); } /** * Registers an activity event handler for the _installationupdate_ activity. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * To handle a InstallationUpdate event, use the * [onInstallationUpdate](xref:botbuilder-core.ActivityHandler.onInstallationUpdate) type-specific event handler. */ onInstallationUpdate(handler: BotHandler): this { return this.on('InstallationUpdate', handler); } /** * Registers an activity event handler for the _installationupdate add_ activity. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * To handle a InstallationUpdateAdd event, use the * [onInstallationUpdateAdd](xref:botbuilder-core.ActivityHandler.onInstallationUpdateAdd) type-specific event handler. */ onInstallationUpdateAdd(handler: BotHandler): this { return this.on('InstallationUpdateAdd', handler); } /** * Registers an activity event handler for the _installationupdate remove_ activity. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * To handle a InstallationUpdateRemove event, use the * [onInstallationUpdateRemove](xref:botbuilder-core.ActivityHandler.onInstallationUpdateRemove) type-specific event handler. */ onInstallationUpdateRemove(handler: BotHandler): this { return this.on('InstallationUpdateRemove', handler); } /** * Registers an activity event handler for the _tokens-response_ event, emitted for any incoming * `tokens/response` event activity. These are generated as part of the OAuth authentication flow. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * The activity's [value](xref:botframework-schema.Activity.value) property contains the user token. * * If your bot handles authentication using an [OAuthPrompt](xref:botbuilder-dialogs.OAuthPrompt) * within a dialog, then the dialog will need to receive this activity to complete the authentication flow. * * To handle other named events and event events in general, use the * [onEvent](xref:botbuilder-core.ActivityHandler.onEvent) type-specific event handler. */ onTokenResponseEvent(handler: BotHandler): this { return this.on('TokenResponseEvent', handler); } /** * Registers an activity event handler for the _command_ activity. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * To handle a Command event, use the * [onCommand](xref:botbuilder-core.ActivityHandler.onCommand) type-specific event handler. */ onCommand(handler: BotHandler): this { return this.on('Command', handler); } /** * Registers an activity event handler for the _CommandResult_ activity. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * To handle a CommandResult event, use the * [onCommandResult](xref:botbuilder-core.ActivityHandler.onCommandResult) type-specific event handler. */ onCommandResult(handler: BotHandler): this { return this.on('CommandResult', handler); } /** * Registers an activity event handler for the _unrecognized activity type_ event, emitted for an * incoming activity with a type for which the [ActivityHandler](xref:botbuilder-core.ActivityHandler) * doesn't provide an event handler. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. * @remarks * The `ActivityHandler` does not define events for all activity types defined in the * [Bot Framework Activity schema](http://aka.ms/botSpecs-activitySchema). In addition, * channels and custom adapters can create [Activities](xref:botframework-schema.Activity) with * types not in the schema. When the activity handler receives such an event, it emits an unrecognized activity type event. * * The activity's [type](xref:botframework-schema.Activity.type) property contains the activity type. */ onUnrecognizedActivityType(handler: BotHandler): this { return this.on('UnrecognizedActivityType', handler); } /** * Registers an activity event handler for the _dialog_ event, emitted as the last event for an incoming activity. * * @param handler The event handler. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. */ onDialog(handler: BotHandler): this { return this.on('Dialog', handler); } /** * Called to initiate the event emission process. * * @param context The context object for the current turn. * @remarks * Typically, you would provide this method as the function handler that the adapter calls * to perform the bot's logic after the received activity has been pre-processed by the adapter * and routed through any middleware. * * For example: * ```javascript * server.post('/api/messages', (req, res) => { * adapter.processActivity(req, res, async (context) => { * // Route to bot's activity logic. * await bot.run(context); * }); * }); * ``` * * **See also** * - [BotFrameworkAdapter.processActivity](xref:botbuilder.BotFrameworkAdapter.processActivity) */ async run(context: TurnContext): Promise<void> { await super.run(context); } /** * Called at the start of the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to use custom logic for emitting events. * * The default logic is to call any handlers registered via [onTurn](xref:botbuilder-core.ActivityHandler.onTurn), * and then continue by calling [ActivityHandlerBase.onTurnActivity](xref:botbuilder-core.ActivityHandlerBase.onTurnActivity). */ protected async onTurnActivity(context: TurnContext): Promise<void> { await this.handle(context, 'Turn', async () => { await super.onTurnActivity(context); }); } /** * Runs all registered _message_ handlers and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onMessage](xref:botbuilder-core.ActivityHandler.onMessage), * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async onMessageActivity(context: TurnContext): Promise<void> { await this.handle(context, 'Message', this.defaultNextEvent(context)); } /** * Runs all registered _message update_ handlers and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onMessageUpdate](xref:botbuilder-core.ActivityHandler.onMessageUpdate), * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async onMessageUpdateActivity(context: TurnContext): Promise<void> { await this.handle(context, 'MessageUpdate', async () => { await this.dispatchMessageUpdateActivity(context); }); } /** * Runs all registered _message delete_ handlers and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onMessageDelete](xref:botbuilder-core.ActivityHandler.onMessageDelete), * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async onMessageDeleteActivity(context: TurnContext): Promise<void> { await this.handle(context, 'MessageDelete', async () => { await this.dispatchMessageDeleteActivity(context); }); } /** * Provides default behavior for invoke activities. * * @param context The context object for the current turn. * @returns {Promise<InvokeResponse>} An Invoke Response for the activity. * @remarks * Override this method to support channel-specific behavior across multiple channels. * The default logic is to check for a signIn invoke and handle that * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async onInvokeActivity(context: TurnContext): Promise<InvokeResponse> { try { switch (context.activity.name) { case 'application/search': { const invokeValue = this.getSearchInvokeValue(context.activity); const response = await this.onSearchInvoke(context, invokeValue); return { status: response.statusCode, body: response }; } case 'adaptiveCard/action': { const invokeValue = this.getAdaptiveCardInvokeValue(context.activity); const response = await this.onAdaptiveCardInvoke(context, invokeValue); return { status: response.statusCode, body: response }; } case verifyStateOperationName: case tokenExchangeOperationName: await this.onSignInInvoke(context); return { status: StatusCodes.OK }; default: throw new InvokeException(StatusCodes.NOT_IMPLEMENTED); } } catch (err) { if (err.message === 'NotImplemented') { return { status: StatusCodes.NOT_IMPLEMENTED }; } if (err instanceof InvokeException) { return err.createInvokeResponse(); } throw err; } finally { this.defaultNextEvent(context)(); } } /** * Handle _signin invoke activity type_. * * @param _context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels. */ protected async onSignInInvoke(_context: TurnContext): Promise<void> { throw new InvokeException(StatusCodes.NOT_IMPLEMENTED); } /** * Invoked when the bot is sent an Adaptive Card Action Execute. * * @param _context the context object for the current turn * @param _invokeValue incoming activity value * @returns {Promise<AdaptiveCardInvokeResponse>} An Adaptive Card Invoke Response for the activity. */ protected onAdaptiveCardInvoke( _context: TurnContext, _invokeValue: AdaptiveCardInvokeValue, ): Promise<AdaptiveCardInvokeResponse> { return Promise.reject(new InvokeException(StatusCodes.NOT_IMPLEMENTED)); } /** * Invoked when the bot is sent an invoke activity with name of 'application/search'. * * @param _context the context object for the current turn. * @param _invokeValue incoming activity value. * @returns {Promise<SearchInvokeResponse>} A Search Invoke Response for the activity. */ protected onSearchInvoke(_context: TurnContext, _invokeValue: SearchInvokeValue): Promise<SearchInvokeResponse> { return Promise.reject(new InvokeException(StatusCodes.NOT_IMPLEMENTED)); } /** * Runs all registered _endOfConversation_ handlers and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onEndOfConversationActivity](xref:botbuilder-core.ActivityHandler.onMessage), * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async onEndOfConversationActivity(context: TurnContext): Promise<void> { await this.handle(context, 'EndOfConversation', this.defaultNextEvent(context)); } /** * Runs all registered _typing_ handlers and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onTypingActivity](xref:botbuilder-core.ActivityHandler.onTypingActivity), * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async onTypingActivity(context: TurnContext): Promise<void> { await this.handle(context, 'Typing', this.defaultNextEvent(context)); } /** * Runs all registered _instllationupdate_ handlers and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onInstallationUpdateActivity](xref:botbuilder-core.ActivityHandler.onInstallationUpdateActivity), * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async onInstallationUpdateActivity(context: TurnContext): Promise<void> { await this.handle(context, 'InstallationUpdate', async () => { await this.dispatchInstallationUpdateActivity(context); }); } /** * Runs all registered _command_ handlers and then continues the event emission process. * * @param context The context object for the current turn. */ protected async onCommandActivity(context: TurnContext): Promise<void> { await this.handle(context, 'Command', this.defaultNextEvent(context)); } /** * Runs all registered _commandresult_ handlers and then continues the event emission process. * * @param context The context object for the current turn. */ protected async onCommandResultActivity(context: TurnContext): Promise<void> { await this.handle(context, 'CommandResult', this.defaultNextEvent(context)); } /** * Runs the _installation update_ sub-type handlers, as appropriate, and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels or to add * custom conversation update sub-type events. * * The default logic is: * - If any members were added, call handlers registered via [onMembersAdded](xref:botbuilder-core.ActivityHandler.onMembersAdded). * - If any members were removed, call handlers registered via [onMembersRemoved](xref:botbuilder-core.ActivityHandler.onMembersRemoved). * - Continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async dispatchInstallationUpdateActivity(context: TurnContext): Promise<void> { switch (context.activity.action) { case 'add': case 'add-upgrade': case 'remove': case 'remove-upgrade': await super.onInstallationUpdateActivity(context); break; default: await this.defaultNextEvent(context)(); } } /** * Runs all registered _installation update add_ handlers and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onInstallationUpdateAdd](xref:botbuilder-core.ActivityHandler.onInstallationUpdateAdd), * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async onInstallationUpdateAddActivity(context: TurnContext): Promise<void> { await this.handle(context, 'InstallationUpdateAdd', this.defaultNextEvent(context)); } /** * Runs all registered _installation update remove_ handlers and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onInstallationUpdateRemove](xref:botbuilder-core.ActivityHandler.onInstallationUpdateRemove), * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async onInstallationUpdateRemoveActivity(context: TurnContext): Promise<void> { await this.handle(context, 'InstallationUpdateRemove', this.defaultNextEvent(context)); } /** * Runs all registered _unrecognized activity type_ handlers and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onUnrecognizedActivityType](xref:botbuilder-core.ActivityHandler.onUnrecognizedActivityType), * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async onUnrecognizedActivity(context: TurnContext): Promise<void> { await this.handle(context, 'UnrecognizedActivityType', this.defaultNextEvent(context)); } private getSearchInvokeValue(activity: Activity): SearchInvokeValue { const { value }: { value?: SearchInvokeValue } = activity; if (!value) { const response = this.createAdaptiveCardInvokeErrorResponse( StatusCodes.BAD_REQUEST, 'BadRequest', 'Missing value property for search', ); throw new InvokeException(StatusCodes.BAD_REQUEST, response); } if (!value.kind) { if (activity.channelId === Channels.Msteams) { value.kind = 'search'; } else { const response = this.createAdaptiveCardInvokeErrorResponse( StatusCodes.BAD_REQUEST, 'BadRequest', 'Missing kind property for search.', ); throw new InvokeException(StatusCodes.BAD_REQUEST, response); } } if (!value.queryText) { const response = this.createAdaptiveCardInvokeErrorResponse( StatusCodes.BAD_REQUEST, 'BadRequest', 'Missing queryText for search.', ); throw new InvokeException(StatusCodes.BAD_REQUEST, response); } return value; } private getAdaptiveCardInvokeValue(activity: Activity): AdaptiveCardInvokeValue { const { value }: { value?: AdaptiveCardInvokeValue } = activity; if (!value) { const response = this.createAdaptiveCardInvokeErrorResponse( StatusCodes.BAD_REQUEST, 'BadRequest', 'Missing value property', ); throw new InvokeException(StatusCodes.BAD_REQUEST, response); } if (value.action.type !== 'Action.Execute') { const response = this.createAdaptiveCardInvokeErrorResponse( StatusCodes.BAD_REQUEST, 'NotSupported', `The action '${value.action.type}' is not supported.`, ); throw new InvokeException(StatusCodes.BAD_REQUEST, response); } const { action, authentication, state } = value; const { data, id: actionId, type, verb } = action ?? {}; const { connectionName, id: authenticationId, token } = authentication ?? {}; return { action: { data, id: actionId, type, verb, }, authentication: { connectionName, id: authenticationId, token, }, state, }; } private createAdaptiveCardInvokeErrorResponse( statusCode: StatusCodes, code: string, message: string, ): AdaptiveCardInvokeResponse { return { statusCode, type: 'application/vnd.microsoft.error', value: { code, message }, }; } /** * Runs all registered _conversation update_ handlers and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onConversationUpdate](xref:botbuilder-core.ActivityHandler.onConversationUpdate), * and then continue by calling * [dispatchConversationUpdateActivity](xref:botbuilder-core.ActivityHandler.dispatchConversationUpdateActivity). */ protected async onConversationUpdateActivity(context: TurnContext): Promise<void> { await this.handle(context, 'ConversationUpdate', async () => { await this.dispatchConversationUpdateActivity(context); }); } /** * Runs the _conversation update_ sub-type handlers, as appropriate, and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels or to add * custom conversation update sub-type events. * * The default logic is: * - If any members were added, call handlers registered via [onMembersAdded](xref:botbuilder-core.ActivityHandler.onMembersAdded). * - If any members were removed, call handlers registered via [onMembersRemoved](xref:botbuilder-core.ActivityHandler.onMembersRemoved). * - Continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async dispatchConversationUpdateActivity(context: TurnContext): Promise<void> { if (context.activity.membersAdded && context.activity.membersAdded.length > 0) { await this.handle(context, 'MembersAdded', this.defaultNextEvent(context)); } else if (context.activity.membersRemoved && context.activity.membersRemoved.length > 0) { await this.handle(context, 'MembersRemoved', this.defaultNextEvent(context)); } else { await this.defaultNextEvent(context)(); } } /** * Runs the _message update_ sub-type handlers, as appropriate, and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels or to add * custom conversation update sub-type events. * * The default logic to simple call [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async dispatchMessageUpdateActivity(context: TurnContext): Promise<void> { await this.defaultNextEvent(context)(); } /** * Runs the _message delete_ sub-type handlers, as appropriate, and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels or to add * custom conversation update sub-type events. * * The default logic to simple call [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async dispatchMessageDeleteActivity(context: TurnContext): Promise<void> { await this.defaultNextEvent(context)(); } /** * Runs all registered _message reaction_ handlers and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onMessageReaction](xref:botbuilder-core.ActivityHandler.onMessageReaction), * and then continue by calling * [dispatchMessageReactionActivity](xref:botbuilder-core.ActivityHandler.dispatchMessageReactionActivity). */ protected async onMessageReactionActivity(context: TurnContext): Promise<void> { await this.handle(context, 'MessageReaction', async () => { await this.dispatchMessageReactionActivity(context); }); } /** * Runs all registered _reactions added_ handlers and then continues the event emission process. * * @param reactionsAdded The list of reactions added. * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onReactionsAdded](xref:botbuilder-core.ActivityHandler.onReactionsAdded), * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async onReactionsAddedActivity(reactionsAdded: MessageReaction[], context: TurnContext): Promise<void> { await this.handle(context, 'ReactionsAdded', this.defaultNextEvent(context)); } /** * Runs all registered _reactions removed_ handlers and then continues the event emission process. * * @param reactionsRemoved The list of reactions removed. * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onReactionsRemoved](xref:botbuilder-core.ActivityHandler.onReactionsRemoved), * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async onReactionsRemovedActivity( reactionsRemoved: MessageReaction[], context: TurnContext, ): Promise<void> { await this.handle(context, 'ReactionsRemoved', this.defaultNextEvent(context)); } /** * Runs the _message reaction_ sub-type handlers, as appropriate, and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels or to add * custom message reaction sub-type events. * * The default logic is: * - If reactions were added, call handlers registered via [onReactionsAdded](xref:botbuilder-core.ActivityHandler.onReactionsAdded). * - If reactions were removed, call handlers registered via [onMembersRemoved](xref:botbuilder-core.ActivityHandler.onMembersRemoved). * - Continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async dispatchMessageReactionActivity(context: TurnContext): Promise<void> { if (context.activity.reactionsAdded || context.activity.reactionsRemoved) { await super.onMessageReactionActivity(context); } else { await this.defaultNextEvent(context)(); } } /** * Runs all registered event_ handlers and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels. * * The default logic is to call any handlers registered via * [onEvent](xref:botbuilder-core.ActivityHandler.onEvent), * and then continue by calling * [dispatchEventActivity](xref:botbuilder-core.ActivityHandler.dispatchEventActivity). */ protected async onEventActivity(context: TurnContext): Promise<void> { await this.handle(context, 'Event', async () => { await this.dispatchEventActivity(context); }); } /** * Runs the _event_ sub-type handlers, as appropriate, and then continues the event emission process. * * @param context The context object for the current turn. * @remarks * Override this method to support channel-specific behavior across multiple channels or to add custom event sub-type events. * For certain channels, such as Web Chat and custom Direct Line clients, developers can emit custom event activities from the client. * * The default logic is: * - If the activity is a 'tokens/response' event, call handlers registered via * [onTokenResponseEvent](xref:botbuilder-core.ActivityHandler.onTokenResponseEvent). * - Continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async dispatchEventActivity(context: TurnContext): Promise<void> { if (context.activity.name === tokenResponseEventName) { await this.handle(context, 'TokenResponseEvent', this.defaultNextEvent(context)); } else { await this.defaultNextEvent(context)(); } } /** * Called at the end of the event emission process. * * @param context The context object for the current turn. * @returns {Promise<void>} A promise representing the async operation. * @remarks * Override this method to use custom logic for emitting events. * * The default logic is to call any handlers registered via [onDialog](xref:botbuilder-core.ActivityHandler.onDialog), * and then complete the event emission process. */ protected defaultNextEvent(context: TurnContext): () => Promise<void> { const runDialogs = async (): Promise<void> => { await this.handle(context, 'Dialog', async () => { // noop }); }; return runDialogs; } /** * Registers a bot event handler to receive a specific event. * * @param type The identifier for the event type. * @param handler The event handler to register. * @returns A reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. */ protected on(type: string, handler: BotHandler) { if (!this.handlers[type]) { this.handlers[type] = [handler]; } else { this.handlers[type].push(handler); } return this; } /** * Emits an event and executes any registered handlers. * * @param context The context object for the current turn. * @param type The identifier for the event type. * @param onNext The continuation function to call after all registered handlers for this event complete. * @returns {Promise<any>} The handler's return value. * @remarks * Runs any registered handlers for this event type and then calls the continuation function. * * This optionally produces a return value, to support _invoke_ activities. If multiple handlers * produce a return value, the first one produced is returned. */ protected async handle(context: TurnContext, type: string, onNext: () => Promise<void>): Promise<any> { let returnValue: any = null; async function runHandler(index: number): Promise<void> { if (index < handlers.length) { const val = await handlers[index](context, () => runHandler(index + 1)); // if a value is returned, and we have not yet set the return value, // capture it. This is used to allow InvokeResponses to be returned. if (typeof val !== 'undefined' && returnValue === null) { returnValue = val; } } else { const val = await onNext(); if (typeof val !== 'undefined') { returnValue = val; } } } const handlers = this.handlers[type] || []; await runHandler(0); return returnValue; } /** * An [InvokeResponse](xref:botbuilder.InvokeResponse) factory that initializes the body to the parameter passed and status equal to OK. * * @param body JSON serialized content from a POST response. * @returns A new [InvokeResponse](xref:botbuilder.InvokeResponse) object. */ protected static createInvokeResponse(body?: any): InvokeResponse { return { status: 200, body }; } }