UNPKG

botbuilder-core

Version:

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

693 lines 28.4 kB
"use strict"; /** * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TurnContext = exports.BotCallbackHandlerKey = void 0; const _1 = require("."); const internal_1 = require("./internal"); const turnContextStateCollection_1 = require("./turnContextStateCollection"); const botframework_schema_1 = require("botframework-schema"); exports.BotCallbackHandlerKey = 'botCallbackHandler'; function getAppropriateReplyToId(source) { if (source.type !== botframework_schema_1.ActivityTypes.ConversationUpdate || (source.channelId !== botframework_schema_1.Channels.Directline && source.channelId !== botframework_schema_1.Channels.Webchat)) { return source.id; } return undefined; } /** * Provides context for a turn of a bot. * * @remarks * Context provides information needed to process an incoming activity. The context object is * created by a [BotAdapter](xref:botbuilder-core.BotAdapter) and persists for the length of the turn. */ // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging class TurnContext { /** * Creates an new instance of the [TurnContext](xref:xref:botbuilder-core.TurnContext) class. * * @param adapterOrContext The adapter creating the context or the context object to clone. * @param request Optional. The incoming activity for the turn. */ constructor(adapterOrContext, request) { this._respondedRef = { responded: false }; this._turnState = new turnContextStateCollection_1.TurnContextStateCollection(); this._onSendActivities = []; this._onUpdateActivity = []; this._onDeleteActivity = []; this._turn = 'turn'; this._locale = 'locale'; /** * List of activities to send when `context.activity.deliveryMode == 'expectReplies'`. */ this.bufferedReplyActivities = []; if (adapterOrContext instanceof TurnContext) { adapterOrContext.copyTo(this); } else { this._adapter = adapterOrContext; this._activity = request; } } /** * Removes at mentions for the activity's [recipient](xref:botframework-schema.Activity.recipient) * from the text of an activity and returns the updated text. * Use with caution; this function alters the activity's [text](xref:botframework-schema.Activity.text) property. * * @param activity The activity to remove at mentions from. * @returns The updated activity's text. * @remarks * Some channels, for example Microsoft Teams, add at-mention details to the text of a message activity. * * Use this helper method to modify the activity's [text](xref:botframework-schema.Activity.text) property. * It removes all at mentions of the activity's [recipient](xref:botframework-schema.Activity.recipient) * and then returns the updated property value. * * For example: * ```JavaScript * const updatedText = TurnContext.removeRecipientMention(turnContext.request); * ``` * **See also** * - [removeMentionText](xref:botbuilder-core.TurnContext.removeMentionText) */ static removeRecipientMention(activity) { return TurnContext.removeMentionText(activity, activity.recipient.id); } /** * Removes at mentions for a given ID from the text of an activity and returns the updated text. * Use with caution; this function alters the activity's [text](xref:botframework-schema.Activity.text) property. * * @param activity The activity to remove at mentions from. * @param id The ID of the user or bot to remove at mentions for. * @returns The updated activity's text. * @remarks * Some channels, for example Microsoft Teams, add at mentions to the text of a message activity. * * Use this helper method to modify the activity's [text](xref:botframework-schema.Activity.text) property. * It removes all at mentions for the given bot or user ID and then returns the updated property value. * * For example, when you remove mentions of **echoBot** from an activity containing the text "@echoBot Hi Bot", * the activity text is updated, and the method returns "Hi Bot". * * The format of a mention [entity](xref:botframework-schema.Entity) is channel-dependent. * However, the mention's [text](xref:botframework-schema.Mention.text) property should contain * the exact text for the user as it appears in the activity text. * * For example, whether the channel uses "<at>username</at>" or "@username", this string is in * the activity's text, and this method will remove all occurrences of that string from the text. * * For example: * ```JavaScript * const updatedText = TurnContext.removeMentionText(activity, activity.recipient.id); * ``` * **See also** * - [removeRecipientMention](xref:botbuilder-core.TurnContext.removeRecipientMention) */ static removeMentionText(activity, id) { const mentions = TurnContext.getMentions(activity); const mentionsFiltered = mentions.filter((mention) => mention.mentioned.id === id); if (mentionsFiltered.length) { activity.text = activity.text.replace(mentionsFiltered[0].text, '').trim(); } return activity.text; } /** * Gets all at-mention entities included in an activity. * * @param activity The activity. * @returns All the at-mention entities included in an activity. * @remarks * The activity's [entities](xref:botframework-schema.Activity.entities) property contains a flat * list of metadata objects pertaining to this activity and can contain * [mention](xref:botframework-schema.Mention) entities. This method returns all such entities * for a given activity. * * For example: * ```JavaScript * const mentions = TurnContext.getMentions(turnContext.request); * ``` */ static getMentions(activity) { const result = []; if (activity.entities !== undefined) { for (let i = 0; i < activity.entities.length; i++) { if (activity.entities[i].type.toLowerCase() === 'mention') { result.push(activity.entities[i]); } } } return result; } /** * Copies conversation reference information from an activity. * * @param activity The activity to get the information from. * @returns A conversation reference for the conversation that contains this activity. * @remarks * You can save the conversation reference as a JSON object and use it later to proactively message the user. * * For example: * ```JavaScript * const reference = TurnContext.getConversationReference(context.request); * ``` * * **See also** * * - [BotAdapter.continueConversation](xref:botbuilder-core.BotAdapter.continueConversation) */ static getConversationReference(activity) { return { activityId: getAppropriateReplyToId(activity), user: (0, internal_1.shallowCopy)(activity.from), bot: (0, internal_1.shallowCopy)(activity.recipient), conversation: (0, internal_1.shallowCopy)(activity.conversation), channelId: activity.channelId, locale: activity.locale, serviceUrl: activity.serviceUrl, }; } /** * Updates an activity with the delivery information from an existing conversation reference. * * @param activity The activity to update. * @param reference The conversation reference to copy delivery information from. * @param isIncoming Optional. `true` to treat the activity as an incoming activity, where the * bot is the recipient; otherwise, `false`. Default is `false`, and the activity will show * the bot as the sender. * @returns This activity, updated with the delivery information. * @remarks * Call the [getConversationReference](xref:botbuilder-core.TurnContext.getConversationReference) * method on an incoming activity to get a conversation reference that you can then use * to update an outgoing activity with the correct delivery information. */ static applyConversationReference(activity, reference, isIncoming = false) { var _a; activity.channelId = reference.channelId; (_a = activity.locale) !== null && _a !== void 0 ? _a : (activity.locale = reference.locale); activity.serviceUrl = reference.serviceUrl; activity.conversation = reference.conversation; if (isIncoming) { activity.from = reference.user; activity.recipient = reference.bot; if (reference.activityId) { activity.id = reference.activityId; } } else { activity.from = reference.bot; activity.recipient = reference.user; if (reference.activityId) { activity.replyToId = reference.activityId; } } return activity; } /** * Copies conversation reference information from a resource response for a sent activity. * * @param activity The sent activity. * @param reply The resource response for the activity, returned by the * [sendActivity](xref:botbuilder-core.TurnContext.sendActivity) or * [sendActivities](xref:botbuilder-core.TurnContext.sendActivities) method. * @returns A ConversationReference that can be stored and used later to delete or update the activity. * @remarks * You can save the conversation reference as a JSON object and use it later to update or delete the message. * * For example: * ```javascript * var reply = await context.sendActivity('Hi'); * var reference = TurnContext.getReplyConversationReference(context.activity, reply); * ``` * * **See also** * * - [deleteActivity](xref:botbuilder-core.TurnContext.deleteActivity) * - [updateActivity](xref:botbuilder-core.TurnContext.updateActivity) */ static getReplyConversationReference(activity, reply) { const reference = TurnContext.getConversationReference(activity); // Update the reference with the new outgoing Activity's id. reference.activityId = reply.id; return reference; } /** * Asynchronously sends an activity to the sender of the incoming activity. * * @param name The activity or text to send. * @param value Optional. The text to be spoken by your bot on a speech-enabled channel. * @param valueType Optional. Indicates whether your bot is accepting, expecting, or ignoring user * @param label Optional. Indicates whether your bot is accepting, expecting, or ignoring user * @returns A promise with a ResourceResponse. * @remarks * Creates and sends a Trace activity. Trace activities are only sent when the channel is the emulator. * * For example: * ```JavaScript * await context.sendTraceActivity(`The following exception was thrown ${msg}`); * ``` * * **See also** * * - [sendActivities](xref:botbuilder-core.TurnContext.sendActivities) */ sendTraceActivity(name, value, valueType, label) { const traceActivity = { type: botframework_schema_1.ActivityTypes.Trace, timestamp: new Date(), name: name, value: value, valueType: valueType, label: label, }; return this.sendActivity(traceActivity); } /** * Asynchronously sends an activity to the sender of the incoming activity. * * @param activityOrText The activity or text to send. * @param speak Optional. The text to be spoken by your bot on a speech-enabled channel. * @param inputHint Optional. Indicates whether your bot is accepting, expecting, or ignoring user * input after the message is delivered to the client. One of: 'acceptingInput', 'ignoringInput', * or 'expectingInput'. Default is 'acceptingInput'. * @returns A promise with a ResourceResponse. * @remarks * If the activity is successfully sent, results in a * [ResourceResponse](xref:botframework-schema.ResourceResponse) object containing the ID that the * receiving channel assigned to the activity. * * See the channel's documentation for limits imposed upon the contents of the **activityOrText** parameter. * * To control various characteristics of your bot's speech such as voice, rate, volume, pronunciation, * and pitch, specify **speak** in Speech Synthesis Markup Language (SSML) format. * * For example: * ```JavaScript * await context.sendActivity(`Hello World`); * ``` * * **See also** * * - [sendActivities](xref:botbuilder-core.TurnContext.sendActivities) * - [updateActivity](xref:botbuilder-core.TurnContext.updateActivity) * - [deleteActivity](xref:botbuilder-core.TurnContext.deleteActivity) */ sendActivity(activityOrText, speak, inputHint) { return __awaiter(this, void 0, void 0, function* () { let a; if (typeof activityOrText === 'string') { a = { text: activityOrText, inputHint: inputHint || botframework_schema_1.InputHints.AcceptingInput }; if (speak) { a.speak = speak; } } else { a = activityOrText; } const responses = (yield this.sendActivities([a])) || []; return responses[0]; }); } /** * Asynchronously sends a set of activities to the sender of the incoming activity. * * @param activities The activities to send. * @returns A promise with a ResourceResponse. * @remarks * If the activities are successfully sent, results in an array of * [ResourceResponse](xref:botframework-schema.ResourceResponse) objects containing the IDs that * the receiving channel assigned to the activities. * * Before they are sent, the delivery information of each outbound activity is updated based on the * delivery information of the inbound inbound activity. * * For example: * ```JavaScript * await context.sendActivities([ * { type: 'typing' }, * { type: 'delay', value: 2000 }, * { type: 'message', text: 'Hello... How are you?' } * ]); * ``` * * **See also** * * - [sendActivity](xref:botbuilder-core.TurnContext.sendActivity) * - [updateActivity](xref:botbuilder-core.TurnContext.updateActivity) * - [deleteActivity](xref:botbuilder-core.TurnContext.deleteActivity) */ sendActivities(activities) { let sentNonTraceActivity = false; const ref = TurnContext.getConversationReference(this.activity); const output = activities.map((activity) => { const result = TurnContext.applyConversationReference(Object.assign({}, activity), ref); if (!result.type) { result.type = botframework_schema_1.ActivityTypes.Message; } if (result.type !== botframework_schema_1.ActivityTypes.Trace) { sentNonTraceActivity = true; } if (result.id) { delete result.id; } return result; }); return this.emit(this._onSendActivities, output, () => __awaiter(this, void 0, void 0, function* () { if (this.activity.deliveryMode === botframework_schema_1.DeliveryModes.ExpectReplies) { // Append activities to buffer const responses = []; output.forEach((a) => { this.bufferedReplyActivities.push(a); // Ensure the TurnState has the InvokeResponseKey, since this activity // is not being sent through the adapter, where it would be added to TurnState. if (a.type === botframework_schema_1.ActivityTypes.InvokeResponse) { this.turnState.set(_1.INVOKE_RESPONSE_KEY, a); } responses.push({ id: undefined }); }); // Set responded flag if (sentNonTraceActivity) { this.responded = true; } return responses; } else { const responses = yield this.adapter.sendActivities(this, output); for (let index = 0; index < (responses === null || responses === void 0 ? void 0 : responses.length); index++) { const activity = output[index]; activity.id = responses[index].id; } // Set responded flag if (sentNonTraceActivity) { this.responded = true; } return responses; } })); } /** * Asynchronously updates a previously sent activity. * * @param activity The replacement for the original activity. * @returns A promise with a ResourceResponse. * @remarks * The [id](xref:botframework-schema.Activity.id) of the replacement activity indicates the activity * in the conversation to replace. * * For example: * ```JavaScript * const matched = /approve (.*)/i.exec(context.activity.text); * if (matched) { * const update = await approveExpenseReport(matched[1]); * await context.updateActivity(update); * } * ``` * * **See also** * * - [sendActivity](xref:botbuilder-core.TurnContext.sendActivity) * - [deleteActivity](xref:botbuilder-core.TurnContext.deleteActivity) * - [getReplyConversationReference](xref:botbuilder-core.TurnContext.getReplyConversationReference) */ updateActivity(activity) { const ref = TurnContext.getConversationReference(this.activity); const a = TurnContext.applyConversationReference(activity, ref); return this.emit(this._onUpdateActivity, a, () => this.adapter.updateActivity(this, a)); } /** * Asynchronously deletes a previously sent activity. * * @param idOrReference ID or conversation reference for the activity to delete. * @returns A promise representing the async operation. * @remarks * If an ID is specified, the conversation reference for the current request is used * to get the rest of the information needed. * * For example: * ```JavaScript * const matched = /approve (.*)/i.exec(context.activity.text); * if (matched) { * const savedId = await approveExpenseReport(matched[1]); * await context.deleteActivity(savedId); * } * ``` * * **See also** * * - [sendActivity](xref:botbuilder-core.TurnContext.sendActivity) * - [updateActivity](xref:botbuilder-core.TurnContext.updateActivity) * - [getReplyConversationReference](xref:botbuilder-core.TurnContext.getReplyConversationReference) */ deleteActivity(idOrReference) { let reference; if (typeof idOrReference === 'string') { reference = TurnContext.getConversationReference(this.activity); reference.activityId = idOrReference; } else { reference = idOrReference; } return this.emit(this._onDeleteActivity, reference, () => this.adapter.deleteActivity(this, reference)); } /** * Adds a response handler for send activity operations. * * @param handler The handler to add to the context object. * @returns The updated context object. * @remarks * This method returns a reference to the turn context object. * * When the [sendActivity](xref:botbuilder-core.TurnContext.sendActivity) or * [sendActivities](xref:botbuilder-core.TurnContext.sendActivities) method is called, * the registered handlers are called in the order in which they were added to the context object * before the activities are sent. * * This example shows how to listen for and log outgoing `message` activities. * * ```JavaScript * context.onSendActivities(async (ctx, activities, next) => { * // Log activities before sending them. * activities.filter(a => a.type === 'message').forEach(a => logSend(a)); * * // Allow the send process to continue. * next(); * }); * ``` */ onSendActivities(handler) { this._onSendActivities.push(handler); return this; } /** * Adds a response handler for update activity operations. * * @param handler The handler to add to the context object. * @returns The updated context object. * @remarks * This method returns a reference to the turn context object. * * When the [updateActivity](xref:botbuilder-core.TurnContext.updateActivity) method is called, * the registered handlers are called in the order in which they were added to the context object * before the activity is updated. * * This example shows how to listen for and log activity updates. * * ```JavaScript * context.onUpdateActivity(async (ctx, activity, next) => { * // Replace activity * await next(); * * // Log update * logUpdate(activity); * }); * ``` */ onUpdateActivity(handler) { this._onUpdateActivity.push(handler); return this; } /** * Adds a response handler for delete activity operations. * * @param handler The handler to add to the context object. * @returns The updated context object. * @remarks * This method returns a reference to the turn context object. * * When the [deleteActivity](xref:botbuilder-core.TurnContext.deleteActivity) method is called, * the registered handlers are called in the order in which they were added to the context object * before the activity is deleted. * * This example shows how to listen for and log activity deletions. * * ```JavaScript * context.onDeleteActivity(async (ctx, reference, next) => { * // Delete activity * await next(); * * // Log delete * logDelete(activity); * }); * ``` */ onDeleteActivity(handler) { this._onDeleteActivity.push(handler); return this; } /** * Called when this turn context object is passed into the constructor for a new turn context. * * @param context The new turn context object. * @remarks * This copies private members from this object to the new object. * All property values are copied by reference. * * Override this in a derived class to copy any additional members, as necessary. */ copyTo(context) { // Copy private members to other instance. [ '_adapter', '_activity', '_respondedRef', '_services', '_onSendActivities', '_onUpdateActivity', '_onDeleteActivity', ].forEach((prop) => (context[prop] = this[prop])); } /** * Gets the bot adapter that created this context object. * * @returns The bot adapter that created this context object. */ get adapter() { return this._adapter; } /** * Gets the activity associated with this turn. * * @returns The activity associated with this turn. * @remarks * This example shows how to get the users trimmed utterance from the activity: * * ```JavaScript * const utterance = (context.activity.text || '').trim(); * ``` */ get activity() { return this._activity; } /** * Indicates whether the bot has replied to the user this turn. * * @returns True if at least one response was sent for the current turn; otherwise, false. * @remarks * **true** if at least one response was sent for the current turn; otherwise, **false**. * Use this to determine if your bot needs to run fallback logic after other normal processing. * * Trace activities do not set this flag. * * for example: * ```JavaScript * await routeActivity(context); * if (!context.responded) { * await context.sendActivity(`I'm sorry. I didn't understand.`); * } * ``` */ get responded() { return this._respondedRef.responded; } /** * Sets the response flag on the current turn context. * * @remarks * The [sendActivity](xref:botbuilder-core.TurnContext.sendActivity) and * [sendActivities](xref:botbuilder-core.TurnContext.sendActivities) methods call this method to * update the responded flag. You can call this method directly to indicate that your bot has * responded appropriately to the incoming activity. */ set responded(value) { if (!value) { throw new Error("TurnContext: cannot set 'responded' to a value of 'false'."); } this._respondedRef.responded = true; } /** * Gets the locale stored in the turnState. * * @returns The locale stored in the turnState. */ get locale() { const turnObj = this._turnState[this._turn]; const locale = turnObj[this._locale]; if (typeof locale === 'string') { return locale; } return undefined; } /** * Sets the locale stored in the turnState. */ set locale(value) { const turnObj = this._turnState[this._turn]; if (turnObj) { turnObj[this._locale] = value; } else { this._turnState[this._turn] = { locale: value }; } } /** * Gets the services registered on this context object. * * @returns The services registered on this context object. * @remarks * Middleware, other components, and services will typically use this to cache information * that could be asked for by a bot multiple times during a turn. You can use this cache to * pass information between components of your bot. * * For example: * ```JavaScript * const cartKey = Symbol(); * const cart = await loadUsersShoppingCart(context); * context.turnState.set(cartKey, cart); * ``` * * > [!TIP] * > When creating middleware or a third-party component, use a unique symbol for your cache key * > to avoid state naming collisions with the bot or other middleware or components. */ get turnState() { return this._turnState; } /** * @private * Executes `handlers` as a chain, returning a promise that resolves to the final result. */ emit(handlers, arg, next) { const runHandlers = ([handler, ...remaining]) => { try { return handler ? handler(this, arg, () => runHandlers(remaining)) : Promise.resolve(next()); } catch (err) { return Promise.reject(err); } }; return runHandlers(handlers); } } exports.TurnContext = TurnContext; //# sourceMappingURL=turnContext.js.map