Skip to content

Commit

Permalink
feat: create conversation (#3805)
Browse files Browse the repository at this point in the history
* feat: create conversation

Note: *Async postfix is due to conflicts with BotFrameworkAdapter

Fixes #3752

* fix: validate parameters
  • Loading branch information
Josh Gummersall committed Jun 24, 2021
1 parent 0db8231 commit 18b3ebb
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 1 deletion.
4 changes: 4 additions & 0 deletions libraries/botbuilder-core/etc/botbuilder-core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ClaimsIdentity } from 'botframework-connector';
import { Configuration } from 'botbuilder-dialogs-adaptive-runtime-core';
import { ConnectorClientOptions } from 'botframework-connector';
import { ConnectorFactory } from 'botframework-connector';
import { ConversationParameters } from 'botframework-schema';
import { ConversationReference } from 'botframework-schema';
import { HeroCard } from 'botframework-schema';
import { InputHints } from 'botframework-schema';
Expand Down Expand Up @@ -151,6 +152,7 @@ export abstract class BotAdapter {
continueConversationAsync(botAppId: string, reference: Partial<ConversationReference>, logic: (context: TurnContext) => Promise<void>): Promise<void>;
continueConversationAsync(claimsIdentity: ClaimsIdentity, reference: Partial<ConversationReference>, logic: (context: TurnContext) => Promise<void>): Promise<void>;
continueConversationAsync(claimsIdentity: ClaimsIdentity, reference: Partial<ConversationReference>, audience: string, logic: (context: TurnContext) => Promise<void>): Promise<void>;
createConversationAsync(botAppId: string, channelId: string, serviceUrl: string, audience: string, conversationParameters: ConversationParameters, logic: (context: TurnContext) => Promise<void>): Promise<void>;
abstract deleteActivity(context: TurnContext, reference: Partial<ConversationReference>): Promise<void>;
// (undocumented)
protected middleware: MiddlewareSet;
Expand Down Expand Up @@ -301,6 +303,8 @@ export abstract class CloudAdapterBase extends BotAdapter {
continueConversationAsync(botAppIdOrClaimsIdentity: string | ClaimsIdentity, reference: Partial<ConversationReference>, logicOrAudience: ((context: TurnContext) => Promise<void>) | string, maybeLogic?: (context: TurnContext) => Promise<void>): Promise<void>;
protected createClaimsIdentity(botAppId?: string): ClaimsIdentity;
// (undocumented)
createConversationAsync(botAppId: string, channelId: string, serviceUrl: string, audience: string, conversationParameters: ConversationParameters, logic: (context: TurnContext) => Promise<void>): Promise<void>;
// (undocumented)
deleteActivity(context: TurnContext, reference: Partial<ConversationReference>): Promise<void>;
protected processActivity(authHeader: string, activity: Activity, logic: (context: TurnContext) => Promise<void>): Promise<InvokeResponse | undefined>;
protected processActivity(authenticateRequestResult: AuthenticateRequestResult, activity: Activity, logic: (context: TurnContext) => Promise<void>): Promise<InvokeResponse | undefined>;
Expand Down
35 changes: 34 additions & 1 deletion libraries/botbuilder-core/src/botAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License.
*/
import { ClaimsIdentity } from 'botframework-connector';
import { Activity, ConversationReference, ResourceResponse } from 'botframework-schema';
import { Activity, ConversationParameters, ConversationReference, ResourceResponse } from 'botframework-schema';
import { makeRevocable } from './internal';
import { Middleware, MiddlewareHandler, MiddlewareSet } from './middlewareSet';
import { TurnContext } from './turnContext';
Expand Down Expand Up @@ -157,6 +157,39 @@ export abstract class BotAdapter {
throw new Error('NotImplemented');
}

/**
* Creates a conversation on the specified channel.
*
* @param botAppId The application ID of the bot.
* @param channelId The ID for the channel.
* @param serviceUrl The ID for the channel.
* @param audience The audience for the connector.
* <param name="conversationParameters">
* @param conversationParameters The conversation information to use to create the conversation
* @param logic The method to call for the resulting bot turn.
* @returns A promise that represents the asynchronous operation
*
* @remarks
* To start a conversation, your bot must know its account information and the user's account information on that
* channel. Most _channels only support initiating a direct message (non-group) conversation.
*
* The adapter attempts to create a new conversation on the channel, and then sends a `conversationUpdate` activity
* through its middleware pipeline to the logic method.
*
* If the conversation is established with the specified users, the ID of the activity's converstion will contain
* the ID of the new conversation.
*/
async createConversationAsync(
botAppId: string,
channelId: string,
serviceUrl: string,
audience: string,
conversationParameters: ConversationParameters,
logic: (context: TurnContext) => Promise<void>
): Promise<void> {
throw new Error('NotImplemented');
}

/**
* Gets or sets an error handler that can catch exceptions in the middleware or application.
*
Expand Down
86 changes: 86 additions & 0 deletions libraries/botbuilder-core/src/cloudAdapterBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { BotAdapter } from './botAdapter';
import { BotCallbackHandlerKey, TurnContext } from './turnContext';
import { INVOKE_RESPONSE_KEY } from './activityHandlerBase';
import { delay } from 'botbuilder-stdlib';
import { v4 as uuid } from 'uuid';

import {
AuthenticateRequestResult,
Expand All @@ -18,9 +19,11 @@ import {

import {
Activity,
ActivityEventNames,
ActivityEx,
ActivityTypes,
Channels,
ConversationParameters,
ConversationReference,
DeliveryModes,
InvokeResponse,
Expand Down Expand Up @@ -176,6 +179,89 @@ export abstract class CloudAdapterBase extends BotAdapter {
return this.processProactive(claimsIdentity, ActivityEx.getContinuationActivity(reference), audience, logic);
}

/**
* @inheritdoc
*/
async createConversationAsync(
botAppId: string,
channelId: string,
serviceUrl: string,
audience: string,
conversationParameters: ConversationParameters,
logic: (context: TurnContext) => Promise<void>
): Promise<void> {
if (typeof serviceUrl !== 'string' || !serviceUrl) {
throw new TypeError('`serviceUrl` must be a non-empty string');
}

if (!conversationParameters) throw new TypeError('`conversationParameters` must be defined');
if (!logic) throw new TypeError('`logic` must be defined');

// Create a ClaimsIdentity, to create the connector and for adding to the turn context.
const claimsIdentity = this.createClaimsIdentity(botAppId);
claimsIdentity.claims.push({ type: AuthenticationConstants.ServiceUrlClaim, value: serviceUrl });

// Create the connector factory.
const connectorFactory = this.botFrameworkAuthentication.createConnectorFactory(claimsIdentity);

// Create the connector client to use for outbound requests.
const connectorClient = await connectorFactory.create(serviceUrl, audience);

// Make the actual create conversation call using the connector.
const createConversationResult = await connectorClient.conversations.createConversation(conversationParameters);

// Create the create activity to communicate the results to the application.
const createActivity = this.createCreateActivity(
createConversationResult.id,
channelId,
serviceUrl,
conversationParameters
);

// Create a UserTokenClient instance for the application to use. (For example, in the OAuthPrompt.)
const userTokenClient = await this.botFrameworkAuthentication.createUserTokenClient(claimsIdentity);

// Create a turn context and run the pipeline.
const context = this.createTurnContext(
createActivity,
claimsIdentity,
undefined,
connectorClient,
userTokenClient,
logic,
connectorFactory
);

// Run the pipeline.
await this.runMiddleware(context, logic);
}

private createCreateActivity(
createdConversationId: string | undefined,
channelId: string,
serviceUrl: string,
conversationParameters: ConversationParameters
): Partial<Activity> {
// Create a conversation update activity to represent the result.
const activity = ActivityEx.createEventActivity();

activity.name = ActivityEventNames.CreateConversation;
activity.channelId = channelId;
activity.serviceUrl = serviceUrl;
activity.id = createdConversationId ?? uuid();
activity.conversation = {
conversationType: undefined,
id: createdConversationId,
isGroup: conversationParameters.isGroup,
name: undefined,
tenantId: conversationParameters.tenantId,
};
activity.channelData = conversationParameters.channelData;
activity.recipient = conversationParameters.bot;

return activity;
}

/**
* The implementation for continue conversation.
*
Expand Down

0 comments on commit 18b3ebb

Please sign in to comment.