botbuilder
Version:
Bot Builder is a framework for building rich bots on virtually any platform.
1,126 lines (1,033 loc) • 96.6 kB
text/typescript
/**
* @module botbuilder
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import * as z from 'zod';
import { BotFrameworkHttpAdapter } from './botFrameworkHttpAdapter';
import { ConnectorClientBuilder, Request, Response, ResponseT, WebRequest, WebResponse } from './interfaces';
import { HttpClient, RequestPolicyFactory, userAgentPolicy } from '@azure/ms-rest-js';
import { INodeBufferT, INodeSocketT, LogicT } from './zod';
import { arch, release, type } from 'os';
import { delay, retry } from 'botbuilder-stdlib';
import { validateAndFixActivity } from './activityValidator';
import {
Activity,
ActivityEventNames,
ActivityTypes,
BotAdapter,
BotCallbackHandlerKey,
CallerIdConstants,
ChannelAccount,
Channels,
ConversationParameters,
ConversationReference,
ConversationsResult,
CoreAppCredentials,
DeliveryModes,
ExpectedReplies,
ExtendedUserTokenProvider,
INVOKE_RESPONSE_KEY,
InvokeResponse,
ResourceResponse,
StatusCodes,
TokenResponse,
TurnContext,
} from 'botbuilder-core';
import {
AppCredentials,
AuthenticationConfiguration,
AuthenticationConstants,
AuthenticationError,
CertificateAppCredentials,
ChannelValidation,
Claim,
ClaimsIdentity,
ConnectorClient,
ConnectorClientOptions,
EmulatorApiClient,
GovernmentChannelValidation,
GovernmentConstants,
JwtTokenValidation,
MicrosoftAppCredentials,
SignInUrlResponse,
SimpleCredentialProvider,
SkillValidation,
TokenApiClient,
TokenApiModels,
TokenExchangeRequest,
TokenStatus,
} from 'botframework-connector';
import {
INodeBuffer,
INodeSocket,
IReceiveRequest,
ISocket,
IStreamingTransportServer,
NamedPipeServer,
NodeWebSocketFactory,
NodeWebSocketFactoryBase,
RequestHandler,
StreamingResponse,
WebSocketServer,
} from 'botframework-streaming';
import {
defaultPipeName,
GET,
POST,
MESSAGES_PATH,
StreamingHttpClient,
TokenResolver,
VERSION_PATH,
} from './streaming';
/**
* @deprecated Use `CloudAdapter` with `ConfigurationBotFrameworkAuthentication` instead to configure bot runtime.
* Contains settings used to configure a [BotFrameworkAdapter](xref:botbuilder.BotFrameworkAdapter) instance.
*/
export interface BotFrameworkAdapterSettings {
/**
* The ID assigned to your bot in the [Bot Framework Portal](https://dev.botframework.com/).
*/
appId: string;
/**
* The password assigned to your bot in the [Bot Framework Portal](https://dev.botframework.com/).
*/
appPassword: string;
/**
* Optional. The tenant to acquire the bot-to-channel token from.
*/
channelAuthTenant?: string;
/**
* Optional. The OAuth API endpoint for your bot to use.
*/
oAuthEndpoint?: string;
/**
* Optional. The OpenID Metadata endpoint for your bot to use.
*/
openIdMetadata?: string;
/**
* Optional. The channel service option for this bot to validate connections from Azure or other channel locations.
*/
channelService?: string;
/**
* Optional. Used to pass in a NodeWebSocketFactoryBase instance.
*/
webSocketFactory?: NodeWebSocketFactoryBase;
/**
* Optional. Certificate thumbprint to authenticate the appId against AAD.
*/
certificateThumbprint?: string;
/**
* Optional. Certificate key to authenticate the appId against AAD.
*/
certificatePrivateKey?: string;
/**
* Optional. Used to require specific endorsements and verify claims. Recommended for Skills.
*/
authConfig?: AuthenticationConfiguration;
/**
* Optional. Used when creating new ConnectorClients.
*/
clientOptions?: ConnectorClientOptions;
}
// Retrieve additional information, i.e., host operating system, host OS release, architecture, Node.js version
const ARCHITECTURE = arch();
const TYPE = type();
const RELEASE = release();
const NODE_VERSION = process.version;
const pjson: Record<'version', string> = require('../package.json'); // eslint-disable-line @typescript-eslint/no-var-requires
export const 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.
*/
export class BotFrameworkAdapter
extends BotAdapter
implements BotFrameworkHttpAdapter, ConnectorClientBuilder, ExtendedUserTokenProvider, RequestHandler {
// 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
readonly TokenApiClientCredentialsKey: symbol = Symbol('TokenApiClientCredentials');
protected readonly credentials: AppCredentials;
protected readonly credentialsProvider: SimpleCredentialProvider;
protected readonly settings: BotFrameworkAdapterSettings;
private isEmulatingOAuthCards: boolean;
// Streaming-specific properties:
private logic: (context: TurnContext) => Promise<void>;
private streamingServer: IStreamingTransportServer;
private webSocketFactory: NodeWebSocketFactoryBase;
private authConfiguration: AuthenticationConfiguration;
private namedPipeName?: string;
/**
* 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?: Partial<BotFrameworkAdapterSettings>) {
super();
this.settings = { appId: '', appPassword: '', ...settings };
// If settings.certificateThumbprint & settings.certificatePrivateKey are provided,
// use CertificateAppCredentials.
if (this.settings.certificateThumbprint && this.settings.certificatePrivateKey) {
this.credentials = new CertificateAppCredentials(
this.settings.appId,
settings.certificateThumbprint,
settings.certificatePrivateKey,
this.settings.channelAuthTenant
);
this.credentialsProvider = new SimpleCredentialProvider(this.credentials.appId, '');
} else {
this.credentials = new MicrosoftAppCredentials(
this.settings.appId,
this.settings.appPassword || '',
this.settings.channelAuthTenant
);
this.credentialsProvider = new 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[AuthenticationConstants.ChannelService];
this.settings.openIdMetadata =
this.settings.openIdMetadata || process.env[AuthenticationConstants.BotOpenIdMetadataKey];
this.authConfiguration = this.settings.authConfig || new AuthenticationConfiguration();
if (this.settings.openIdMetadata) {
ChannelValidation.OpenIdMetadataEndpoint = this.settings.openIdMetadata;
GovernmentChannelValidation.OpenIdMetadataEndpoint = this.settings.openIdMetadata;
}
if (JwtTokenValidation.isGovernment(this.settings.channelService)) {
this.credentials.oAuthEndpoint = GovernmentConstants.ToChannelFromBotLoginUrl;
this.credentials.oAuthScope = 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(
async (context, next): Promise<void> => {
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;
}
await 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(): boolean {
return this.streamingServer?.isConnected ?? false;
}
/**
* Asynchronously resumes a conversation with a user, possibly after some time has gone by.
*
* @param reference A reference to the conversation to continue.
* @param oAuthScope The intended recipient of any sent activities.
* @param logic The asynchronous method to call after the adapter middleware runs.
*
* @remarks
* This is often referred to as a _proactive notification_, the bot can proactively
* send a message to a conversation or user without waiting for an incoming message.
* For example, a bot can use this method to send notifications or coupons to a user.
*
* To send a proactive message:
* 1. Save a copy of a [ConversationReference](xref:botframework-schema.ConversationReference)
* from an incoming activity. For example, you can store the conversation reference in a database.
* 1. Call this method to resume the conversation at a later time. Use the saved reference to access the conversation.
* 1. On success, the adapter generates a [TurnContext](xref:botbuilder-core.TurnContext) object and calls the `logic` function handler.
* Use the `logic` function to send the proactive message.
*
* To copy the reference from any incoming activity in the conversation, use the
* [TurnContext.getConversationReference](xref:botbuilder-core.TurnContext.getConversationReference) method.
*
* This method is similar to the [processActivity](xref:botbuilder.BotFrameworkAdapter.processActivity) method.
* The adapter creates a [TurnContext](xref:botbuilder-core.TurnContext) and routes it through
* its middleware before calling the `logic` handler. The created activity will have a
* [type](xref:botframework-schema.Activity.type) of 'event' and a
* [name](xref:botframework-schema.Activity.name) of 'continueConversation'.
*
* For example:
* ```JavaScript
* server.post('/api/notifyUser', async (req, res) => {
* // Lookup previously saved conversation reference.
* const reference = await findReference(req.body.refId);
*
* // Proactively notify the user.
* if (reference) {
* await adapter.continueConversation(reference, async (context) => {
* await context.sendActivity(req.body.message);
* });
* res.send(200);
* } else {
* res.send(404);
* }
* });
* ```
*/
async continueConversation(
reference: Partial<ConversationReference>,
logic: (context: TurnContext) => Promise<void>
): Promise<void>;
/**
* Asynchronously resumes a conversation with a user, possibly after some time has gone by.
*
* @param reference [ConversationReference](xref:botframework-schema.ConversationReference) of the conversation to continue.
* @param oAuthScope The intended recipient of any sent activities or the function to call to continue the conversation.
* @param logic Optional. The asynchronous method to call after the adapter middleware runs.
*/
async continueConversation(
reference: Partial<ConversationReference>,
oAuthScope: string,
logic: (context: TurnContext) => Promise<void>
): Promise<void>;
/**
* @internal
*/
async continueConversation(
reference: Partial<ConversationReference>,
oAuthScopeOrlogic: string | ((context: TurnContext) => Promise<void>),
maybeLogic?: (context: TurnContext) => Promise<void>
): Promise<void> {
let audience: string;
if (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 = JwtTokenValidation.isGovernment(this.settings.channelService)
? GovernmentConstants.ToChannelFromBotOAuthScope
: AuthenticationConstants.ToChannelFromBotOAuthScope;
} else {
audience = z.string().parse(oAuthScopeOrlogic);
}
const logic = LogicT.check(oAuthScopeOrlogic) ? oAuthScopeOrlogic : 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 = await this.buildCredentials(credentials.appId, audience);
}
}
const connectorClient = this.createConnectorClientInternal(reference.serviceUrl, credentials);
const request = TurnContext.applyConversationReference(
{ type: ActivityTypes.Event, name: ActivityEventNames.ContinueConversation },
reference,
true
);
const context = this.createContext(request);
context.turnState.set(this.OAuthScopeKey, audience);
context.turnState.set(this.ConnectorClientKey, connectorClient);
await this.runMiddleware(context, logic);
}
/**
* Asynchronously creates and starts a conversation with a user on a channel.
*
* @param {Partial<ConversationReference>} reference A reference for the conversation to create.
* @param {(context: TurnContext) => Promise<void>} logic The asynchronous method to call after the adapter middleware runs.
* @returns {Promise<void>} a promise representing the asynchronous operation
*
* @summary
* To use this method, you need both the bot's and the user's account information on a channel.
* The Bot Connector service supports the creating of group conversations; however, this
* method and most channels only support initiating a direct message (non-group) conversation.
*
* To create and start a new conversation:
* 1. Get a copy of a [ConversationReference](xref:botframework-schema.ConversationReference) from an incoming activity.
* 1. Set the [user](xref:botframework-schema.ConversationReference.user) property to the
* [ChannelAccount](xref:botframework-schema.ChannelAccount) value for the intended recipient.
* 1. Call this method to request that the channel create a new conversation with the specified user.
* 1. On success, the adapter generates a turn context and calls the `logic` function handler.
*
* To get the initial reference, use the
* [TurnContext.getConversationReference](xref:botbuilder-core.TurnContext.getConversationReference)
* method on any incoming activity in the conversation.
*
* If the channel establishes the conversation, the generated event activity's
* [conversation](xref:botframework-schema.Activity.conversation) property will contain the
* ID of the new conversation.
*
* This method is similar to the [processActivity](xref:botbuilder.BotFrameworkAdapter.processActivity) method.
* The adapter creates a [TurnContext](xref:botbuilder-core.TurnContext) and routes it through
* middleware before calling the `logic` handler. The created activity will have a
* [type](xref:botframework-schema.Activity.type) of 'event' and a
* [name](xref:botframework-schema.Activity.name) of 'createConversation'.
*
* For example:
* ```JavaScript
* // Get group members conversation reference
* const reference = TurnContext.getConversationReference(context.activity);
*
* // ...
* // Start a new conversation with the user
* await adapter.createConversation(reference, async (ctx) => {
* await ctx.sendActivity(`Hi (in private)`);
* });
* ```
*/
createConversation(
reference: Partial<ConversationReference>,
logic: (context: TurnContext) => Promise<void>
): Promise<void>;
/**
* Asynchronously creates and starts a conversation with a user on a channel.
*
* @param {Partial<ConversationReference>} reference A reference for the conversation to create.
* @param {Partial<ConversationParameters>} parameters Parameters used when creating the conversation
* @param {(context: TurnContext) => Promise<void>} logic The asynchronous method to call after the adapter middleware runs.
* @returns {Promise<void>} a promise representing the asynchronous operation
*/
createConversation(
reference: Partial<ConversationReference>,
parameters: Partial<ConversationParameters>,
logic: (context: TurnContext) => Promise<void>
): Promise<void>;
/**
* @internal
*/
async createConversation(
reference: Partial<ConversationReference>,
parametersOrLogic: Partial<ConversationParameters> | ((context: TurnContext) => Promise<void>),
maybeLogic?: (context: TurnContext) => Promise<void>
): Promise<void> {
if (!reference.serviceUrl) {
throw new Error('BotFrameworkAdapter.createConversation(): missing serviceUrl.');
}
const parameters = LogicT.check(parametersOrLogic) ? {} : parametersOrLogic;
const logic = LogicT.check(parametersOrLogic) ? parametersOrLogic : 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 = await client.conversations.createConversation(conversationParameters);
// Initialize request and copy over new conversation ID and updated serviceUrl.
const request = TurnContext.applyConversationReference(
{ type: ActivityTypes.Event, name: 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);
await 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.
*/
async deleteActivity(context: TurnContext, reference: Partial<ConversationReference>): Promise<void> {
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: ConnectorClient = this.getOrCreateConnectorClient(
context,
reference.serviceUrl,
this.credentials
);
await 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.
*/
async deleteConversationMember(context: TurnContext, memberId: string): Promise<void> {
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: string = context.activity.serviceUrl;
const conversationId: string = context.activity.conversation.id;
const client: ConnectorClient = this.getOrCreateConnectorClient(context, serviceUrl, this.credentials);
await 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.
*/
async getActivityMembers(context: TurnContext, activityId?: string): Promise<ChannelAccount[]> {
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: string = context.activity.serviceUrl;
const conversationId: string = context.activity.conversation.id;
const client: ConnectorClient = this.getOrCreateConnectorClient(context, serviceUrl, this.credentials);
return await 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.
*/
async getConversationMembers(context: TurnContext): Promise<ChannelAccount[]> {
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: string = context.activity.serviceUrl;
const conversationId: string = context.activity.conversation.id;
const client: ConnectorClient = this.getOrCreateConnectorClient(context, serviceUrl, this.credentials);
return await 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).
*/
async getConversations(
contextOrServiceUrl: TurnContext | string,
continuationToken?: string
): Promise<ConversationsResult> {
let client: ConnectorClient;
if (typeof contextOrServiceUrl === 'object') {
const context: TurnContext = contextOrServiceUrl as TurnContext;
client = this.getOrCreateConnectorClient(context, context.activity.serviceUrl, this.credentials);
} else {
client = this.createConnectorClient(contextOrServiceUrl as string);
}
return await 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 context object for the turn.
* @param connectionName The name of the auth connection to use.
* @param magicCode Optional. The validation code the user entered.
* @param oAuthAppCredentials AppCredentials for OAuth.
*
* @returns A [TokenResponse](xref:botframework-schema.TokenResponse) object that contains the user token.
*/
async getUserToken(context: TurnContext, connectionName: string, magicCode?: string): Promise<TokenResponse>;
async getUserToken(
context: TurnContext,
connectionName: string,
magicCode?: string,
oAuthAppCredentials?: CoreAppCredentials
): Promise<TokenResponse>;
/**
* 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.
*/
async getUserToken(
context: TurnContext,
connectionName: string,
magicCode?: string,
oAuthAppCredentials?: AppCredentials
): Promise<TokenResponse> {
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: string = context.activity.from.id;
const url: string = this.oauthApiUrl(context);
const client: TokenApiClient = this.createTokenApiClient(url, oAuthAppCredentials);
context.turnState.set(this.TokenApiClientCredentialsKey, client);
const result: TokenApiModels.UserTokenGetTokenResponse = await client.userToken.getToken(
userId,
connectionName,
{ code: magicCode, channelId: context.activity.channelId }
);
if (!result || !result.token || result._response.status == 404) {
return undefined;
} else {
return result as TokenResponse;
}
}
/**
* Asynchronously signs out the user from the token server.
*
* @param context The context object for the turn.
* @param connectionName The name of the auth connection to use.
* @param userId The ID of user to sign out.
* @param oAuthAppCredentials AppCredentials for OAuth.
*/
async signOutUser(context: TurnContext, connectionName?: string, userId?: string): Promise<void>;
async signOutUser(
context: TurnContext,
connectionName?: string,
userId?: string,
oAuthAppCredentials?: CoreAppCredentials
): Promise<void>;
/**
* 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.
*/
async signOutUser(
context: TurnContext,
connectionName?: string,
userId?: string,
oAuthAppCredentials?: AppCredentials
): Promise<void> {
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: string = this.oauthApiUrl(context);
const client: TokenApiClient = this.createTokenApiClient(url, oAuthAppCredentials);
context.turnState.set(this.TokenApiClientCredentialsKey, client);
await 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 context object for the turn.
* @param connectionName The name of the auth connection to use.
* @param oAuthAppCredentials AppCredentials for OAuth.
* @param userId The user id that will be associated with the token.
* @param finalRedirect The final URL that the OAuth flow will redirect to.
*/
async getSignInLink(
context: TurnContext,
connectionName: string,
oAuthAppCredentials?: AppCredentials,
userId?: string,
finalRedirect?: string
): Promise<string>;
async getSignInLink(
context: TurnContext,
connectionName: string,
oAuthAppCredentials?: CoreAppCredentials,
userId?: string,
finalRedirect?: string
): Promise<string>;
/**
* 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.
*/
async getSignInLink(
context: TurnContext,
connectionName: string,
oAuthAppCredentials?: AppCredentials,
userId?: string,
finalRedirect?: string
): Promise<string> {
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: Partial<ConversationReference> = TurnContext.getConversationReference(context.activity);
const url: string = this.oauthApiUrl(context);
const client: TokenApiClient = this.createTokenApiClient(url, oAuthAppCredentials);
context.turnState.set(this.TokenApiClientCredentialsKey, client);
const state: any = {
ConnectionName: connectionName,
Conversation: conversation,
MsAppId: (client.credentials as AppCredentials).appId,
RelatesTo: context.activity.relatesTo,
};
const finalState: string = Buffer.from(JSON.stringify(state)).toString('base64');
return (
await 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 context object 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 AppCredentials for OAuth.
*
* @returns The [TokenStatus](xref:botframework-connector.TokenStatus) objects retrieved.
*/
async getTokenStatus(context: TurnContext, userId?: string, includeFilter?: string): Promise<TokenStatus[]>;
async getTokenStatus(
context: TurnContext,
userId?: string,
includeFilter?: string,
oAuthAppCredentials?: CoreAppCredentials
): Promise<TokenStatus[]>;
/**
* 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.
*/
async getTokenStatus(
context: TurnContext,
userId?: string,
includeFilter?: string,
oAuthAppCredentials?: AppCredentials
): Promise<TokenStatus[]> {
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: string = this.oauthApiUrl(context);
const client: TokenApiClient = this.createTokenApiClient(url, oAuthAppCredentials);
context.turnState.set(this.TokenApiClientCredentialsKey, client);
return (
await client.userToken.getTokenStatus(userId, {
channelId: context.activity.channelId,
include: includeFilter,
})
)._response.parsedBody;
}
/**
* Asynchronously signs out the user from the token server.
*
* @param context The context object 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 AppCredentials for OAuth.
*
* @returns A map of the [TokenResponse](xref:botframework-schema.TokenResponse) objects by resource URL.
*/
async getAadTokens(
context: TurnContext,
connectionName: string,
resourceUrls: string[]
): Promise<{ [propertyName: string]: TokenResponse }>;
async getAadTokens(
context: TurnContext,
connectionName: string,
resourceUrls: string[],
oAuthAppCredentials?: CoreAppCredentials
): Promise<{ [propertyName: string]: TokenResponse }>;
/**
* 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.
*/
async getAadTokens(
context: TurnContext,
connectionName: string,
resourceUrls: string[],
oAuthAppCredentials?: AppCredentials
): Promise<{ [propertyName: string]: TokenResponse }> {
if (!context.activity.from || !context.activity.from.id) {
throw new Error('BotFrameworkAdapter.getAadTokens(): missing from or from.id');
}
this.checkEmulatingOAuthCards(context);
const userId: string = context.activity.from.id;
const url: string = this.oauthApiUrl(context);
const client: TokenApiClient = this.createTokenApiClient(url, oAuthAppCredentials);
context.turnState.set(this.TokenApiClientCredentialsKey, client);
return (
await client.userToken.getAadTokens(
userId,
connectionName,
{ resourceUrls: resourceUrls },
{ channelId: context.activity.channelId }
)
)._response.parsedBody as { [propertyName: string]: TokenResponse };
}
/**
* 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.
*/
async getSignInResource(
context: TurnContext,
connectionName: string,
userId?: string,
finalRedirect?: string,
appCredentials?: CoreAppCredentials
): Promise<SignInUrlResponse> {
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: string = this.oauthApiUrl(context);
const credentials = appCredentials as AppCredentials;
const client: TokenApiClient = this.createTokenApiClient(url, credentials);
const conversation: Partial<ConversationReference> = TurnContext.getConversationReference(context.activity);
const state: any = {
ConnectionName: connectionName,
Conversation: conversation,
relatesTo: context.activity.relatesTo,
MSAppId: (client.credentials as AppCredentials).appId,
};
const finalState: string = Buffer.from(JSON.stringify(state)).toString('base64');
const options: TokenApiModels.BotSignInGetSignInResourceOptionalParams = { finalRedirect: finalRedirect };
return await client.botSignIn.getSignInResource(finalState, options);
}
/**
* Asynchronously Performs a token exchange operation such as for single sign-on.
*
* @param context Context for the current turn of conversation with the user.
* @param connectionName Name of the auth connection to use.
* @param userId The user id that will be associated with the token.
* @param tokenExchangeRequest The exchange request details, either a token to exchange or a uri to exchange.
* @param appCredentials Optional. The CoreAppCredentials for OAuth.
*/
async exchangeToken(
context: TurnContext,
connectionName: string,
userId: string,
tokenExchangeRequest: TokenExchangeRequest,
appCredentials?: CoreAppCredentials
): Promise<TokenResponse>;
/**
* 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).
*/
async exchangeToken(
context: TurnContext,
connectionName: string,
userId: string,
tokenExchangeRequest: TokenExchangeRequest,
appCredentials?: AppCredentials
): Promise<TokenResponse> {
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: string = this.oauthApiUrl(context);
const client: TokenApiClient = this.createTokenApiClient(url, appCredentials);
return (
await client.userToken.exchangeAsync(
userId,
connectionName,
context.activity.channelId,
tokenExchangeRequest
)
)._response.parsedBody as TokenResponse;
}
/**
* Asynchronously sends an emulated OAuth card for a channel.
*
* This method supports the framework a