UNPKG

botbuilder

Version:

Bot Builder is a framework for building rich bots on virtually any platform.

876 lines • 80.7 kB
"use strict"; /** * @module botbuilder */ /** * 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()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BotFrameworkAdapter = exports.USER_AGENT = void 0; const z = require("zod"); const interfaces_1 = require("./interfaces"); const ms_rest_js_1 = require("@azure/ms-rest-js"); const zod_1 = require("./zod"); const os_1 = require("os"); const botbuilder_stdlib_1 = require("botbuilder-stdlib"); const activityValidator_1 = require("./activityValidator"); const botbuilder_core_1 = require("botbuilder-core"); const botframework_connector_1 = require("botframework-connector"); const botframework_streaming_1 = require("botframework-streaming"); const streaming_1 = require("./streaming"); // Retrieve additional information, i.e., host operating system, host OS release, architecture, Node.js version const ARCHITECTURE = os_1.arch(); const TYPE = os_1.type(); const RELEASE = os_1.release(); const NODE_VERSION = process.version; const pjson = require('../package.json'); // eslint-disable-line @typescript-eslint/no-var-requires exports.USER_AGENT = `Microsoft-BotFramework/3.1 BotBuilder/${pjson.version} (Node.js,Version=${NODE_VERSION}; ${TYPE} ${RELEASE}; ${ARCHITECTURE})`; const OAUTH_ENDPOINT = 'https://api.botframework.com'; const US_GOV_OAUTH_ENDPOINT = 'https://api.botframework.azure.us'; /** * A [BotAdapter](xref:botbuilder-core.BotAdapter) that can connect a bot to a service endpoint. * Implements [IUserTokenProvider](xref:botbuilder-core.IUserTokenProvider). * * @remarks * The bot adapter encapsulates authentication processes and sends activities to and receives * activities from the Bot Connector Service. When your bot receives an activity, the adapter * creates a turn context object, passes it to your bot application logic, and sends responses * back to the user's channel. * * The adapter processes and directs incoming activities in through the bot middleware pipeline to * your bot logic and then back out again. As each activity flows in and out of the bot, each * piece of middleware can inspect or act upon the activity, both before and after the bot logic runs. * Use the [use](xref:botbuilder-core.BotAdapter.use) method to add [Middleware](xref:botbuilder-core.Middleware) * objects to your adapter's middleware collection. * * For more information, see the articles on * [How bots work](https://docs.microsoft.com/azure/bot-service/bot-builder-basics) and * [Middleware](https://docs.microsoft.com/azure/bot-service/bot-builder-concept-middleware). * * For example: * ```JavaScript * const { BotFrameworkAdapter } = require('botbuilder'); * * const adapter = new BotFrameworkAdapter({ * appId: process.env.MicrosoftAppId, * appPassword: process.env.MicrosoftAppPassword * }); * * adapter.onTurnError = async (context, error) => { * // Catch-all logic for errors. * }; * ``` */ /** * @deprecated Use `CloudAdapter` instead. */ class BotFrameworkAdapter extends botbuilder_core_1.BotAdapter { /** * Creates a new instance of the [BotFrameworkAdapter](xref:botbuilder.BotFrameworkAdapter) class. * * @param settings Optional. The settings to use for this adapter instance. * * @remarks * If the `settings` parameter does not include * [channelService](xref:botbuilder.BotFrameworkAdapterSettings.channelService) or * [openIdMetadata](xref:botbuilder.BotFrameworkAdapterSettings.openIdMetadata) values, the * constructor checks the process' environment variables for these values. These values may be * set when a bot is provisioned on Azure and if so are required for the bot to work properly * in the global cloud or in a national cloud. * * The [BotFrameworkAdapterSettings](xref:botbuilder.BotFrameworkAdapterSettings) class defines * the available adapter settings. */ constructor(settings) { super(); // These keys are public to permit access to the keys from the adapter when it's a being // from library that does not have access to static properties off of BotFrameworkAdapter. // E.g. botbuilder-dialogs this.TokenApiClientCredentialsKey = Symbol('TokenApiClientCredentials'); this.settings = Object.assign({ appId: '', appPassword: '' }, settings); // If settings.certificateThumbprint & settings.certificatePrivateKey are provided, // use CertificateAppCredentials. if (this.settings.certificateThumbprint && this.settings.certificatePrivateKey) { this.credentials = new botframework_connector_1.CertificateAppCredentials(this.settings.appId, settings.certificateThumbprint, settings.certificatePrivateKey, this.settings.channelAuthTenant); this.credentialsProvider = new botframework_connector_1.SimpleCredentialProvider(this.credentials.appId, ''); } else { this.credentials = new botframework_connector_1.MicrosoftAppCredentials(this.settings.appId, this.settings.appPassword || '', this.settings.channelAuthTenant); this.credentialsProvider = new botframework_connector_1.SimpleCredentialProvider(this.credentials.appId, this.settings.appPassword || ''); } this.isEmulatingOAuthCards = false; // If no channelService or openIdMetadata values were passed in the settings, check the process' Environment Variables for values. // These values may be set when a bot is provisioned on Azure and if so are required for the bot to properly work in Public Azure or a National Cloud. this.settings.channelService = this.settings.channelService || process.env[botframework_connector_1.AuthenticationConstants.ChannelService]; this.settings.openIdMetadata = this.settings.openIdMetadata || process.env[botframework_connector_1.AuthenticationConstants.BotOpenIdMetadataKey]; this.authConfiguration = this.settings.authConfig || new botframework_connector_1.AuthenticationConfiguration(); if (this.settings.openIdMetadata) { botframework_connector_1.ChannelValidation.OpenIdMetadataEndpoint = this.settings.openIdMetadata; botframework_connector_1.GovernmentChannelValidation.OpenIdMetadataEndpoint = this.settings.openIdMetadata; } if (botframework_connector_1.JwtTokenValidation.isGovernment(this.settings.channelService)) { this.credentials.oAuthEndpoint = botframework_connector_1.GovernmentConstants.ToChannelFromBotLoginUrl; this.credentials.oAuthScope = botframework_connector_1.GovernmentConstants.ToChannelFromBotOAuthScope; } // If a NodeWebSocketFactoryBase was passed in, set it on the BotFrameworkAdapter. if (this.settings.webSocketFactory) { this.webSocketFactory = this.settings.webSocketFactory; } // Relocate the tenantId field used by MS Teams to a new location (from channelData to conversation) // This will only occur on activities from teams that include tenant info in channelData but NOT in conversation, // thus should be future friendly. However, once the the transition is complete. we can remove this. this.use((context, next) => __awaiter(this, void 0, void 0, function* () { if (context.activity.channelId === 'msteams' && context.activity && context.activity.conversation && !context.activity.conversation.tenantId && context.activity.channelData && context.activity.channelData.tenant) { context.activity.conversation.tenantId = context.activity.channelData.tenant.id; } yield next(); })); } /** * Used in streaming contexts to check if the streaming connection is still open for the bot to send activities. * * @returns True if the streaming connection is open, otherwise false. */ get isStreamingConnectionOpen() { var _a, _b; return (_b = (_a = this.streamingServer) === null || _a === void 0 ? void 0 : _a.isConnected) !== null && _b !== void 0 ? _b : false; } /** * @internal */ continueConversation(reference, oAuthScopeOrlogic, maybeLogic) { return __awaiter(this, void 0, void 0, function* () { let audience; if (zod_1.LogicT.check(oAuthScopeOrlogic)) { // Because the OAuthScope parameter was not provided, get the correct value via the channelService. // In this scenario, the ConnectorClient for the continued conversation can only communicate with // official channels, not with other bots. audience = botframework_connector_1.JwtTokenValidation.isGovernment(this.settings.channelService) ? botframework_connector_1.GovernmentConstants.ToChannelFromBotOAuthScope : botframework_connector_1.AuthenticationConstants.ToChannelFromBotOAuthScope; } else { audience = z.string().parse(oAuthScopeOrlogic); } const logic = zod_1.LogicT.check(oAuthScopeOrlogic) ? oAuthScopeOrlogic : zod_1.LogicT.parse(maybeLogic); let credentials = this.credentials; // For authenticated flows (where the bot has an AppId), the ConversationReference's serviceUrl needs // to be trusted for the bot to acquire a token when sending activities to the conversation. // For anonymous flows, the serviceUrl should not be trusted. if (credentials.appId) { // If the provided OAuthScope doesn't match the current one on the instance's credentials, create // a new AppCredentials with the correct OAuthScope. if (credentials.oAuthScope !== audience) { // The BotFrameworkAdapter JavaScript implementation supports one Bot per instance, so get // the botAppId from the credentials. credentials = yield this.buildCredentials(credentials.appId, audience); } } const connectorClient = this.createConnectorClientInternal(reference.serviceUrl, credentials); const request = botbuilder_core_1.TurnContext.applyConversationReference({ type: botbuilder_core_1.ActivityTypes.Event, name: botbuilder_core_1.ActivityEventNames.ContinueConversation }, reference, true); const context = this.createContext(request); context.turnState.set(this.OAuthScopeKey, audience); context.turnState.set(this.ConnectorClientKey, connectorClient); yield this.runMiddleware(context, logic); }); } /** * @internal */ createConversation(reference, parametersOrLogic, maybeLogic) { return __awaiter(this, void 0, void 0, function* () { if (!reference.serviceUrl) { throw new Error('BotFrameworkAdapter.createConversation(): missing serviceUrl.'); } const parameters = zod_1.LogicT.check(parametersOrLogic) ? {} : parametersOrLogic; const logic = zod_1.LogicT.check(parametersOrLogic) ? parametersOrLogic : zod_1.LogicT.parse(maybeLogic); // Create conversation parameters, taking care to provide defaults that can be // overridden by passed in parameters const conversationParameters = Object.assign({}, { bot: reference.bot, members: [reference.user], isGroup: false, activity: null, channelData: null, }, parameters); const client = this.createConnectorClient(reference.serviceUrl); // Mix in the tenant ID if specified. This is required for MS Teams. if (reference.conversation && reference.conversation.tenantId) { // Putting tenantId in channelData is a temporary solution while we wait for the Teams API to be updated conversationParameters.channelData = { tenant: { id: reference.conversation.tenantId } }; // Permanent solution is to put tenantId in parameters.tenantId conversationParameters.tenantId = reference.conversation.tenantId; } const response = yield client.conversations.createConversation(conversationParameters); // Initialize request and copy over new conversation ID and updated serviceUrl. const request = botbuilder_core_1.TurnContext.applyConversationReference({ type: botbuilder_core_1.ActivityTypes.Event, name: botbuilder_core_1.ActivityEventNames.CreateConversation }, reference, true); request.conversation = { id: response.id, isGroup: conversationParameters.isGroup, conversationType: null, tenantId: reference.conversation.tenantId, name: null, }; request.channelData = conversationParameters.channelData; if (response.serviceUrl) { request.serviceUrl = response.serviceUrl; } // Create context and run middleware const context = this.createContext(request); yield this.runMiddleware(context, logic); }); } /** * Asynchronously deletes an existing activity. * * This interface supports the framework and is not intended to be called directly for your code. * Use [TurnContext.deleteActivity](xref:botbuilder-core.TurnContext.deleteActivity) to delete * an activity from your bot code. * * @param context The context object for the turn. * @param reference Conversation reference information for the activity to delete. * * @remarks * Not all channels support this operation. For channels that don't, this call may throw an exception. */ deleteActivity(context, reference) { return __awaiter(this, void 0, void 0, function* () { if (!reference.serviceUrl) { throw new Error('BotFrameworkAdapter.deleteActivity(): missing serviceUrl'); } if (!reference.conversation || !reference.conversation.id) { throw new Error('BotFrameworkAdapter.deleteActivity(): missing conversation or conversation.id'); } if (!reference.activityId) { throw new Error('BotFrameworkAdapter.deleteActivity(): missing activityId'); } const client = this.getOrCreateConnectorClient(context, reference.serviceUrl, this.credentials); yield client.conversations.deleteActivity(reference.conversation.id, reference.activityId); }); } /** * Asynchronously removes a member from the current conversation. * * @param context The context object for the turn. * @param memberId The ID of the member to remove from the conversation. * * @remarks * Remove a member's identity information from the conversation. * * Not all channels support this operation. For channels that don't, this call may throw an exception. */ deleteConversationMember(context, memberId) { return __awaiter(this, void 0, void 0, function* () { if (!context.activity.serviceUrl) { throw new Error('BotFrameworkAdapter.deleteConversationMember(): missing serviceUrl'); } if (!context.activity.conversation || !context.activity.conversation.id) { throw new Error('BotFrameworkAdapter.deleteConversationMember(): missing conversation or conversation.id'); } const serviceUrl = context.activity.serviceUrl; const conversationId = context.activity.conversation.id; const client = this.getOrCreateConnectorClient(context, serviceUrl, this.credentials); yield client.conversations.deleteConversationMember(conversationId, memberId); }); } /** * Asynchronously lists the members of a given activity. * * @param context The context object for the turn. * @param activityId Optional. The ID of the activity to get the members of. If not specified, the current activity ID is used. * * @returns An array of [ChannelAccount](xref:botframework-schema.ChannelAccount) objects for * the users involved in a given activity. * * @remarks * Returns an array of [ChannelAccount](xref:botframework-schema.ChannelAccount) objects for * the users involved in a given activity. * * This is different from [getConversationMembers](xref:botbuilder.BotFrameworkAdapter.getConversationMembers) * in that it will return only those users directly involved in the activity, not all members of the conversation. */ getActivityMembers(context, activityId) { return __awaiter(this, void 0, void 0, function* () { if (!activityId) { activityId = context.activity.id; } if (!context.activity.serviceUrl) { throw new Error('BotFrameworkAdapter.getActivityMembers(): missing serviceUrl'); } if (!context.activity.conversation || !context.activity.conversation.id) { throw new Error('BotFrameworkAdapter.getActivityMembers(): missing conversation or conversation.id'); } if (!activityId) { throw new Error('BotFrameworkAdapter.getActivityMembers(): missing both activityId and context.activity.id'); } const serviceUrl = context.activity.serviceUrl; const conversationId = context.activity.conversation.id; const client = this.getOrCreateConnectorClient(context, serviceUrl, this.credentials); return yield client.conversations.getActivityMembers(conversationId, activityId); }); } /** * Asynchronously lists the members of the current conversation. * * @param context The context object for the turn. * * @returns An array of [ChannelAccount](xref:botframework-schema.ChannelAccount) objects for * all users currently involved in a conversation. * * @remarks * Returns an array of [ChannelAccount](xref:botframework-schema.ChannelAccount) objects for * all users currently involved in a conversation. * * This is different from [getActivityMembers](xref:botbuilder.BotFrameworkAdapter.getActivityMembers) * in that it will return all members of the conversation, not just those directly involved in a specific activity. */ getConversationMembers(context) { return __awaiter(this, void 0, void 0, function* () { if (!context.activity.serviceUrl) { throw new Error('BotFrameworkAdapter.getConversationMembers(): missing serviceUrl'); } if (!context.activity.conversation || !context.activity.conversation.id) { throw new Error('BotFrameworkAdapter.getConversationMembers(): missing conversation or conversation.id'); } const serviceUrl = context.activity.serviceUrl; const conversationId = context.activity.conversation.id; const client = this.getOrCreateConnectorClient(context, serviceUrl, this.credentials); return yield client.conversations.getConversationMembers(conversationId); }); } /** * For the specified channel, asynchronously gets a page of the conversations in which this bot has participated. * * @param contextOrServiceUrl The URL of the channel server to query or a * [TurnContext](xref:botbuilder-core.TurnContext) object from a conversation on the channel. * @param continuationToken Optional. The continuation token from the previous page of results. * Omit this parameter or use `undefined` to retrieve the first page of results. * * @returns A [ConversationsResult](xref:botframework-schema.ConversationsResult) object containing a page of results * and a continuation token. * * @remarks * The the return value's [conversations](xref:botframework-schema.ConversationsResult.conversations) property contains a page of * [ConversationMembers](xref:botframework-schema.ConversationMembers) objects. Each object's * [id](xref:botframework-schema.ConversationMembers.id) is the ID of a conversation in which the bot has participated on this channel. * This method can be called from outside the context of a conversation, as only the bot's service URL and credentials are required. * * The channel batches results in pages. If the result's * [continuationToken](xref:botframework-schema.ConversationsResult.continuationToken) property is not empty, then * there are more pages to get. Use the returned token to get the next page of results. * If the `contextOrServiceUrl` parameter is a [TurnContext](xref:botbuilder-core.TurnContext), the URL of the channel server is * retrieved from * `contextOrServiceUrl`.[activity](xref:botbuilder-core.TurnContext.activity).[serviceUrl](xref:botframework-schema.Activity.serviceUrl). */ getConversations(contextOrServiceUrl, continuationToken) { return __awaiter(this, void 0, void 0, function* () { let client; if (typeof contextOrServiceUrl === 'object') { const context = contextOrServiceUrl; client = this.getOrCreateConnectorClient(context, context.activity.serviceUrl, this.credentials); } else { client = this.createConnectorClient(contextOrServiceUrl); } return yield client.conversations.getConversations(continuationToken ? { continuationToken: continuationToken } : undefined); }); } /** * Asynchronously attempts to retrieve the token for a user that's in a login flow. * * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn. * @param connectionName The name of the auth connection to use. * @param magicCode Optional. The validation code the user entered. * @param oAuthAppCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth. * * @returns A [TokenResponse](xref:botframework-schema.TokenResponse) object that contains the user token. */ getUserToken(context, connectionName, magicCode, oAuthAppCredentials) { return __awaiter(this, void 0, void 0, function* () { if (!context.activity.from || !context.activity.from.id) { throw new Error('BotFrameworkAdapter.getUserToken(): missing from or from.id'); } if (!connectionName) { throw new Error('getUserToken() requires a connectionName but none was provided.'); } this.checkEmulatingOAuthCards(context); const userId = context.activity.from.id; const url = this.oauthApiUrl(context); const client = this.createTokenApiClient(url, oAuthAppCredentials); context.turnState.set(this.TokenApiClientCredentialsKey, client); const result = yield client.userToken.getToken(userId, connectionName, { code: magicCode, channelId: context.activity.channelId }); if (!result || !result.token || result._response.status == 404) { return undefined; } else { return result; } }); } /** * Asynchronously signs out the user from the token server. * * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn. * @param connectionName Optional. The name of the auth connection to use. * @param userId Optional. The ID of the user to sign out. * @param oAuthAppCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth. */ signOutUser(context, connectionName, userId, oAuthAppCredentials) { return __awaiter(this, void 0, void 0, function* () { if (!context.activity.from || !context.activity.from.id) { throw new Error('BotFrameworkAdapter.signOutUser(): missing from or from.id'); } if (!userId) { userId = context.activity.from.id; } this.checkEmulatingOAuthCards(context); const url = this.oauthApiUrl(context); const client = this.createTokenApiClient(url, oAuthAppCredentials); context.turnState.set(this.TokenApiClientCredentialsKey, client); yield client.userToken.signOut(userId, { connectionName: connectionName, channelId: context.activity.channelId, }); }); } /** * Asynchronously gets a sign-in link from the token server that can be sent as part * of a [SigninCard](xref:botframework-schema.SigninCard). * * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn. * @param connectionName The name of the auth connection to use. * @param oAuthAppCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth. * @param userId Optional. The user id that will be associated with the token. * @param finalRedirect Optional. The final URL that the OAuth flow will redirect to. * @returns The sign in link. */ getSignInLink(context, connectionName, oAuthAppCredentials, userId, finalRedirect) { return __awaiter(this, void 0, void 0, function* () { if (userId && userId != context.activity.from.id) { throw new ReferenceError("cannot retrieve OAuth signin link for a user that's different from the conversation"); } this.checkEmulatingOAuthCards(context); const conversation = botbuilder_core_1.TurnContext.getConversationReference(context.activity); const url = this.oauthApiUrl(context); const client = this.createTokenApiClient(url, oAuthAppCredentials); context.turnState.set(this.TokenApiClientCredentialsKey, client); const state = { ConnectionName: connectionName, Conversation: conversation, MsAppId: client.credentials.appId, RelatesTo: context.activity.relatesTo, }; const finalState = Buffer.from(JSON.stringify(state)).toString('base64'); return (yield client.botSignIn.getSignInUrl(finalState, { channelId: context.activity.channelId, finalRedirect }))._response.bodyAsText; }); } /** * Asynchronously retrieves the token status for each configured connection for the given user. * * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn. * @param userId Optional. If present, the ID of the user to retrieve the token status for. * Otherwise, the ID of the user who sent the current activity is used. * @param includeFilter Optional. A comma-separated list of connection's to include. If present, * the `includeFilter` parameter limits the tokens this method returns. * @param oAuthAppCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth. * * @returns The [TokenStatus](xref:botframework-connector.TokenStatus) objects retrieved. */ getTokenStatus(context, userId, includeFilter, oAuthAppCredentials) { return __awaiter(this, void 0, void 0, function* () { if (!userId && (!context.activity.from || !context.activity.from.id)) { throw new Error('BotFrameworkAdapter.getTokenStatus(): missing from or from.id'); } this.checkEmulatingOAuthCards(context); userId = userId || context.activity.from.id; const url = this.oauthApiUrl(context); const client = this.createTokenApiClient(url, oAuthAppCredentials); context.turnState.set(this.TokenApiClientCredentialsKey, client); return (yield client.userToken.getTokenStatus(userId, { channelId: context.activity.channelId, include: includeFilter, }))._response.parsedBody; }); } /** * Asynchronously signs out the user from the token server. * * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn. * @param connectionName The name of the auth connection to use. * @param resourceUrls The list of resource URLs to retrieve tokens for. * @param oAuthAppCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth. * * @returns A map of the [TokenResponse](xref:botframework-schema.TokenResponse) objects by resource URL. */ getAadTokens(context, connectionName, resourceUrls, oAuthAppCredentials) { return __awaiter(this, void 0, void 0, function* () { if (!context.activity.from || !context.activity.from.id) { throw new Error('BotFrameworkAdapter.getAadTokens(): missing from or from.id'); } this.checkEmulatingOAuthCards(context); const userId = context.activity.from.id; const url = this.oauthApiUrl(context); const client = this.createTokenApiClient(url, oAuthAppCredentials); context.turnState.set(this.TokenApiClientCredentialsKey, client); return (yield client.userToken.getAadTokens(userId, connectionName, { resourceUrls: resourceUrls }, { channelId: context.activity.channelId }))._response.parsedBody; }); } /** * Asynchronously Get the raw signin resource to be sent to the user for signin. * * @param context The context object for the turn. * @param connectionName The name of the auth connection to use. * @param userId The user id that will be associated with the token. * @param finalRedirect The final URL that the OAuth flow will redirect to. * @param appCredentials Optional. The CoreAppCredentials for OAuth. * @returns The [BotSignInGetSignInResourceResponse](xref:botframework-connector.BotSignInGetSignInResourceResponse) object. */ getSignInResource(context, connectionName, userId, finalRedirect, appCredentials) { return __awaiter(this, void 0, void 0, function* () { if (!connectionName) { throw new Error('getUserToken() requires a connectionName but none was provided.'); } if (!context.activity.from || !context.activity.from.id) { throw new Error('BotFrameworkAdapter.getSignInResource(): missing from or from.id'); } // The provided userId doesn't match the from.id on the activity. (same for finalRedirect) if (userId && context.activity.from.id !== userId) { throw new Error('BotFrameworkAdapter.getSiginInResource(): cannot get signin resource for a user that is different from the conversation'); } const url = this.oauthApiUrl(context); const credentials = appCredentials; const client = this.createTokenApiClient(url, credentials); const conversation = botbuilder_core_1.TurnContext.getConversationReference(context.activity); const state = { ConnectionName: connectionName, Conversation: conversation, relatesTo: context.activity.relatesTo, MSAppId: client.credentials.appId, }; const finalState = Buffer.from(JSON.stringify(state)).toString('base64'); const options = { finalRedirect: finalRedirect }; return yield client.botSignIn.getSignInResource(finalState, options); }); } /** * Asynchronously Performs a token exchange operation such as for single sign-on. * * @param context The [TurnContext](xref:botbuilder-core.TurnContext) for the turn. * @param connectionName Name of the auth connection to use. * @param userId The user id that will be associated with the token. * @param tokenExchangeRequest The [TokenExchangeRequest](xref:botbuilder-schema.TokenExchangeRequest), either a token to exchange or a uri to exchange. * @param appCredentials Optional. [AppCredentials](xref:botframework-connector.AppCredentials) for OAuth. * @returns A `Promise` representing the exchanged [TokenResponse](xref:botframework-schema.TokenResponse). */ exchangeToken(context, connectionName, userId, tokenExchangeRequest, appCredentials) { return __awaiter(this, void 0, void 0, function* () { if (!connectionName) { throw new Error('exchangeToken() requires a connectionName but none was provided.'); } if (!userId) { throw new Error('exchangeToken() requires an userId but none was provided.'); } if (tokenExchangeRequest && !tokenExchangeRequest.token && !tokenExchangeRequest.uri) { throw new Error('BotFrameworkAdapter.exchangeToken(): Either a Token or Uri property is required on the TokenExchangeRequest'); } const url = this.oauthApiUrl(context); const client = this.createTokenApiClient(url, appCredentials); return (yield client.userToken.exchangeAsync(userId, connectionName, context.activity.channelId, tokenExchangeRequest))._response.parsedBody; }); } /** * Asynchronously sends an emulated OAuth card for a channel. * * This method supports the framework and is not intended to be called directly for your code. * * @param contextOrServiceUrl The URL of the emulator. * @param emulate `true` to send an emulated OAuth card to the emulator; or `false` to not send the card. * * @remarks * When testing a bot in the Bot Framework Emulator, this method can emulate the OAuth card interaction. */ emulateOAuthCards(contextOrServiceUrl, emulate) { return __awaiter(this, void 0, void 0, function* () { this.isEmulatingOAuthCards = emulate; const url = this.oauthApiUrl(contextOrServiceUrl); yield botframework_connector_1.EmulatorApiClient.emulateOAuthCards(this.credentials, url, emulate); }); } /** * Asynchronously creates a turn context and runs the middleware pipeline for an incoming activity. * * @param req An Express or Restify style request object. * @param res An Express or Restify style response object. * @param logic The function to call at the end of the middleware pipeline. * * @remarks * This is the main way a bot receives incoming messages and defines a turn in the conversation. This method: * * 1. Parses and authenticates an incoming request. * - The activity is read from the body of the incoming request. An error will be returned * if the activity can't be parsed. * - The identity of the sender is authenticated as either the Emulator or a valid Microsoft * server, using the bot's `appId` and `appPassword`. The request is rejected if the sender's * identity is not verified. * 1. Creates a [TurnContext](xref:botbuilder-core.TurnContext) object for the received activity. * - This object is wrapped with a [revocable proxy](https://www.ecma-international.org/ecma-262/6.0/#sec-proxy.revocable). * - When this method completes, the proxy is revoked. * 1. Sends the turn context through the adapter's middleware pipeline. * 1. Sends the turn context to the `logic` function. * - The bot may perform additional routing or processing at this time. * Returning a promise (or providing an `async` handler) will cause the adapter to wait for any asynchronous operations to complete. * - After the `logic` function completes, the promise chain set up by the middleware is resolved. * * > [!TIP] * > If you see the error `TypeError: Cannot perform 'set' on a proxy that has been revoked` * > in your bot's console output, the likely cause is that an async function was used * > without using the `await` keyword. Make sure all async functions use await! * * Middleware can _short circuit_ a turn. When this happens, subsequent middleware and the * `logic` function is not called; however, all middleware prior to this point still run to completion. * For more information about the middleware pipeline, see the * [how bots work](https://docs.microsoft.com/azure/bot-service/bot-builder-basics) and * [middleware](https://docs.microsoft.com/azure/bot-service/bot-builder-concept-middleware) articles. * Use the adapter's [use](xref:botbuilder-core.BotAdapter.use) method to add middleware to the adapter. * * For example: * ```JavaScript * server.post('/api/messages', (req, res) => { * // Route received request to adapter for processing * adapter.processActivity(req, res, async (context) => { * // Process any messages received * if (context.activity.type === ActivityTypes.Message) { * await context.sendActivity(`Hello World`); * } * }); * }); * ``` */ processActivity(req, res, logic) { return __awaiter(this, void 0, void 0, function* () { let body; let status; let processError; try { // Parse body of request status = 400; const request = yield parseRequest(req); // Authenticate the incoming request status = 401; const authHeader = req.headers.authorization || req.headers.Authorization || ''; const identity = yield this.authenticateRequestInternal(request, authHeader); // Set the correct callerId value and discard values received over the wire request.callerId = yield this.generateCallerId(identity); // Process received activity status = 500; const context = this.createContext(request); context.turnState.set(this.BotIdentityKey, identity); const connectorClient = yield this.createConnectorClientWithIdentity(request.serviceUrl, identity); context.turnState.set(this.ConnectorClientKey, connectorClient); const oAuthScope = botframework_connector_1.SkillValidation.isSkillClaim(identity.claims) ? botframework_connector_1.JwtTokenValidation.getAppIdFromClaims(identity.claims) : this.credentials.oAuthScope; context.turnState.set(this.OAuthScopeKey, oAuthScope); context.turnState.set(botbuilder_core_1.BotCallbackHandlerKey, logic); yield this.runMiddleware(context, logic); // NOTE: The factoring of the code differs here when compared to C# as processActivity() returns Promise<void>. // This is due to the fact that the response to the incoming activity is sent from inside this implementation. // In C#, ProcessActivityAsync() returns Task<InvokeResponse> and ASP.NET handles sending of the response. if (request.deliveryMode === botbuilder_core_1.DeliveryModes.ExpectReplies) { // Handle "expectReplies" scenarios where all the activities have been buffered and sent back at once // in an invoke response. let activities = context.bufferedReplyActivities; // If the channel is not the emulator, do not send trace activities. // Fixes: https://github.com/microsoft/botbuilder-js/issues/2732 if (request.channelId !== botbuilder_core_1.Channels.Emulator) { activities = activities.filter((a) => a.type !== botbuilder_core_1.ActivityTypes.Trace); } body = { activities }; status = botbuilder_core_1.StatusCodes.OK; } else if (request.type === botbuilder_core_1.ActivityTypes.Invoke) { // Retrieve a cached Invoke response to handle Invoke scenarios. // These scenarios deviate from the request/request model as the Bot should return a specific body and status. const invokeResponse = context.turnState.get(botbuilder_core_1.INVOKE_RESPONSE_KEY); if (invokeResponse && invokeResponse.value) { const value = invokeResponse.value; status = value.status; body = value.body; } else { status = botbuilder_core_1.StatusCodes.NOT_IMPLEMENTED; } } else { status = botbuilder_core_1.StatusCodes.OK; } } catch (err) { // Catch the error to try and throw the stacktrace out of processActivity() processError = err; body = err.toString(); } // Return status res.status(status); if (body) { res.send(body); } res.end(); // Check for an error if (status >= 400) { if (processError && processError.stack) { throw new Error(`BotFrameworkAdapter.processActivity(): ${status} ERROR\n ${processError.stack}`); } else { throw new Error(`BotFrameworkAdapter.processActivity(): ${status} ERROR`); } } }); } /** * Asynchronously creates a turn context and runs the middleware pipeline for an incoming activity. * * @param activity The activity to process. * @param logic The function to call at the end of the middleware pipeline. * * @remarks * This is the main way a bot receives incoming messages and defines a turn in the conversation. This method: * * 1. Creates a [TurnContext](xref:botbuilder-core.TurnContext) object for the received activity. * - This object is wrapped with a [revocable proxy](https://www.ecma-international.org/ecma-262/6.0/#sec-proxy.revocable). * - When this method completes, the proxy is revoked. * 1. Sends the turn context through the adapter's middleware pipeline. * 1. Sends the turn context to the `logic` function. * - The bot may perform additional routing or processing at this time. * Returning a promise (or providing an `async` handler) will cause the adapter to wait for any asynchronous operations to complete. * - After the `logic` function completes, the promise chain set up by the middleware is resolved. * * Middleware can _short circuit_ a turn. When this happens, subsequent middleware and the * `logic` function is not called; however, all middleware prior to this point still run to completion. * For more information about the middleware pipeline, see the * [how bots work](https://docs.microsoft.com/azure/bot-service/bot-builder-basics) and * [middleware](https://docs.microsoft.com/azure/bot-service/bot-builder-concept-middleware) articles. * Use the adapter's [use](xref:botbuilder-core.BotAdapter.use) method to add middleware to the adapter. */ processActivityDirect(activity, logic) { return __awaiter(this, void 0, void 0, function* () { let processError; try { // Process activity const context = this.createContext(activity); context.turnState.set(botbuilder_core_1.BotCallbackHandlerKey, logic); yield this.runMiddleware(context, logic); } catch (err) { // Catch the error to try and throw the stacktrace out of processActivity() processError = err; } if (processError) { if (processError && processError.stack) { throw new Error(`BotFrameworkAdapter.processActivityDirect(): ERROR\n ${processError.stack}`); } else { throw new Error('BotFrameworkAdapter.processActivityDirect(): ERROR'); } } }); } /** * Asynchronously sends a set of outgoing activities to a channel server. * * This method supports the framework and is not intended to be called directly for your code. * Use the turn context's [sendActivity](xref:botbuilder-core.TurnContext.sendActivity) or * [sendActivities](xref:botbuilder-core.TurnContext.sendActivities) method from your bot code. * * @param context The context object for the turn. * @param activities The activities to send. * * @returns An array of [ResourceResponse](xref:) * * @remarks * The activities will be sent one after another in the order in which they're received. A * response object will be returned for each sent activity. For `message` activities this will * contain the ID of the delivered message. */ sendActivities(context, activities) { return __awaiter(this, void 0, void 0, function* () { const responses = []; for (let i = 0; i < activities.length; i++) { const activity = activities[i]; switch (activity.type) { case 'delay': yield botbuilder_stdlib_1.delay(typeof activity.value === 'number' ? activity.value : 1000); responses.push({}); break; case botbuilder_core_1.ActivityTypes.InvokeResponse: // Cache response to context object. This will be retrieved when turn completes. context.turnState.set(botbuilder_core_1.INVOKE_RESPONSE_KEY, activity); responses.push({}); break; default: { if (!activity.serviceUrl) { throw new Error('BotFrameworkAdapter.sendActivity(): missing serviceUrl.'); } if (!activity.conversation || !activity.conversation.id) { throw new Error('BotFrameworkAdapter.sendActivity(): missing conversation id.'); } if (activity && BotFrameworkAdapter.isStreamingServiceUrl(activity.serviceUrl)) { if (!this.isStreamingConnectionOpen) { throw new Error('BotFrameworkAdapter.sendActivities(): Unable to send activity as Streaming connection is closed.'); } streaming_1.TokenResolver.checkForOAuthCards(this, context, activity); } const client = this.getOrCreateConnectorClient(context, activity.serviceUrl, this.credentials); if (activity.type === botbuilder_core_1.ActivityTypes.Trace && activity.channelId !== botbuilder_core_1.Channels.Emulator) { // Just eat activity responses.push({}); } else if (activity.replyToId) { responses.push(yield client.conversations.replyToActivity(activity.conversation.id, activity.replyToId, activity)); } else { responses.push(yield client.conversations.sendToConversation(activity.conversation.id, activity)); } break; } } } return responses; }); } /** * Asynchronously replaces a previous activity with an updated version. * * This interface supports the framework and is not intended to be called directly for your code. * Use [TurnContext.updateActivity](xref:botbuilder-core.TurnContext.updateActivity) to update * an activity from your bot code. * *