diff --git a/libraries/botbuilder-core/src/activityHandler.ts b/libraries/botbuilder-core/src/activityHandler.ts index a51ca90675..7dc0445558 100644 --- a/libraries/botbuilder-core/src/activityHandler.ts +++ b/libraries/botbuilder-core/src/activityHandler.ts @@ -2,10 +2,13 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ -import { ChannelAccount, MessageReaction, TurnContext } from '.'; + +import { MessageReaction } from 'botframework-schema'; import { ActivityHandlerBase } from './activityHandlerBase'; -import { verifyStateOperationName, tokenExchangeOperationName, tokenResponseEventName } from './signInConstants'; import { InvokeResponse } from './invokeResponse'; +import { verifyStateOperationName, tokenExchangeOperationName, tokenResponseEventName } from './signInConstants'; +import { StatusCodes } from './statusCodes'; +import { TurnContext } from './turnContext'; /** * Describes a bot activity event handler, for use with an [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. @@ -252,22 +255,6 @@ export class ActivityHandler extends ActivityHandlerBase { return this.on('Event', handler); } - /** - * Registers an activity event handler for the _invoke_ event, emitted for every incoming event activity. - * - * @param handler The event handler. - * - * @remarks - * Returns a reference to the [ActivityHandler](xref:botbuilder-core.ActivityHandler) object. - * - * To handle a `signin/verifyState` invoke event or `signin/tokenExchange`, use the - * [onInvokeActivity](xref:botbuilder-core.ActivityHandler.onInvokeActivity) sub-type - * event handler. To handle other named events, add logic to this handler. - */ - public onInvoke(handler: BotHandler): this { - return this.on('Invoke', handler); - } - /** * Registers an activity event handler for the _end of conversation_ activity. * @@ -416,38 +403,44 @@ export class ActivityHandler extends ActivityHandlerBase { } /* - * Runs all registered _invoke_ handlers and then continues the event emission process. + * Provides default behavior for invoke activities. * * @param context The context object for the current turn. * * @remarks * Overwrite this method to support channel-specific behavior across multiple channels. - * The default logic is to call any handlers registered via - * [onInvoke](xref:botbuilder-core.ActivityHandler.onInvoke), + * The default logic is to check for a signIn invoke and handle that * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ - protected async onInvokeActivity(context: TurnContext): Promise { - if(context.activity.name && (context.activity.name === verifyStateOperationName || context.activity.name === tokenExchangeOperationName)) { - await this.onSignInInvoke(context); + protected async onInvokeActivity(context: TurnContext): Promise { + try { + if (context.activity.name && (context.activity.name === verifyStateOperationName || context.activity.name === tokenExchangeOperationName)) { + await this.onSignInInvoke(context); + return { status: StatusCodes.OK }; + } + throw new Error('NotImplemented'); } - else { - await this.handle(context, 'Invoke', this.defaultNextEvent(context)); + catch (err) { + if (err.message === 'NotImplemented') { + return { status: StatusCodes.NOT_IMPLEMENTED }; + } + throw err; + } + finally { + this.defaultNextEvent(context)(); } } /* - * Runs all registered _signin invoke activity type_ handlers and then continues the event emission process. + * Handle _signin invoke activity type_. * * @param context The context object for the current turn. * * @remarks * Overwrite this method to support channel-specific behavior across multiple channels. - * The default logic is to call any handlers registered via - * [onSignInInvoke](xref:botbuilder-core.ActivityHandler.onSignInInvoke), - * and then continue by calling [defaultNextEvent](xref:botbuilder-core.ActivityHandler.defaultNextEvent). */ protected async onSignInInvoke(context: TurnContext): Promise { - await this.handle(context, 'Invoke', this.defaultNextEvent(context)); + throw new Error('NotImplemented'); } /** diff --git a/libraries/botbuilder-core/src/activityHandlerBase.ts b/libraries/botbuilder-core/src/activityHandlerBase.ts index 0c831c8a3b..10f4a19c5d 100644 --- a/libraries/botbuilder-core/src/activityHandlerBase.ts +++ b/libraries/botbuilder-core/src/activityHandlerBase.ts @@ -7,6 +7,11 @@ import { ChannelAccount, MessageReaction, TurnContext } from '.'; +import { InvokeResponse } from './invokeResponse'; +import { StatusCodes } from './statusCodes'; + +// This key is exported internally so that subclassed ActivityHandlers and BotAdapters will not overwrite any already set InvokeResponses. +export const INVOKE_RESPONSE_KEY: symbol = Symbol('invokeResponse'); /** * Defines the core behavior for event-emitting activity handlers for bots. @@ -62,7 +67,11 @@ export class ActivityHandlerBase { await this.onEventActivity(context); break; case ActivityTypes.Invoke: - await this.onInvokeActivity(context); + const invokeResponse = await this.onInvokeActivity(context); + // If onInvokeActivity has already sent an InvokeResponse, do not send another one. + if (invokeResponse && !context.turnState.get(INVOKE_RESPONSE_KEY)) { + await context.sendActivity({ value: invokeResponse, type: 'invokeResponse' }); + } break; case ActivityTypes.EndOfConversation: await this.onEndOfConversationActivity(context); @@ -153,8 +162,16 @@ export class ActivityHandlerBase { return; } - protected async onInvokeActivity(context: TurnContext): Promise { - return; + /** + * Provides a hook for invoke calls. + * + * @param context The context object for the current turn. + * + * @remarks + * Overwrite this method to handle particular invoke calls. + */ + protected async onInvokeActivity(context: TurnContext): Promise { + return { status: StatusCodes.NOT_IMPLEMENTED }; } /** diff --git a/libraries/botbuilder-core/tests/ActivityHandler.test.js b/libraries/botbuilder-core/tests/ActivityHandler.test.js index ee35ca0749..53711ade23 100644 --- a/libraries/botbuilder-core/tests/ActivityHandler.test.js +++ b/libraries/botbuilder-core/tests/ActivityHandler.test.js @@ -197,18 +197,6 @@ describe('ActivityHandler', function() { processActivity({type: ActivityTypes.Event}, bot, done); }); - it(`should fire onInvoke`, async function (done) { - const bot = new ActivityHandler(); - - bot.onInvoke(async(context, next) => { - assert(true, 'onInvoke not called'); - done(); - await next(); - }); - - processActivity({type: ActivityTypes.Invoke}, bot, done); - }); - it(`should fire onEndOfConversation`, async function(done) { const bot = new ActivityHandler(); diff --git a/libraries/botbuilder/src/botFrameworkAdapter.ts b/libraries/botbuilder/src/botFrameworkAdapter.ts index a503b5d8f4..3b868a9d3f 100644 --- a/libraries/botbuilder/src/botFrameworkAdapter.ts +++ b/libraries/botbuilder/src/botFrameworkAdapter.ts @@ -9,7 +9,7 @@ import { STATUS_CODES } from 'http'; import * as os from 'os'; -import { Activity, ActivityTypes, BotAdapter, BotCallbackHandlerKey, ChannelAccount, ConversationAccount, ConversationParameters, ConversationReference, ConversationsResult, DeliveryModes, ExpectedReplies, InvokeResponse, ExtendedUserTokenProvider, ResourceResponse, StatusCodes, TokenResponse, TurnContext } from 'botbuilder-core'; +import { Activity, ActivityTypes, BotAdapter, BotCallbackHandlerKey, ChannelAccount, ConversationAccount, ConversationParameters, ConversationReference, ConversationsResult, DeliveryModes, ExpectedReplies, InvokeResponse, ExtendedUserTokenProvider, ResourceResponse, StatusCodes, TokenResponse, TurnContext, INVOKE_RESPONSE_KEY } from 'botbuilder-core'; import { AuthenticationConfiguration, AuthenticationConstants, ChannelValidation, Claim, ClaimsIdentity, ConnectorClient, EmulatorApiClient, GovernmentConstants, GovernmentChannelValidation, JwtTokenValidation, MicrosoftAppCredentials, AppCredentials, CertificateAppCredentials, SimpleCredentialProvider, TokenApiClient, TokenStatus, TokenApiModels, SignInUrlResponse, SkillValidation, TokenExchangeRequest } from 'botframework-connector'; import { INodeBuffer, INodeSocket, IReceiveRequest, ISocket, IStreamingTransportServer, NamedPipeServer, NodeWebSocketFactory, NodeWebSocketFactoryBase, RequestHandler, StreamingResponse, WebSocketServer } from 'botframework-streaming'; @@ -85,9 +85,6 @@ export const USER_AGENT: string = `Microsoft-BotFramework/3.1 BotBuilder/${ pjso const OAUTH_ENDPOINT = 'https://api.botframework.com'; const US_GOV_OAUTH_ENDPOINT = 'https://api.botframework.azure.us'; -// This key is exported internally so that the TeamsActivityHandler will not overwrite any already set InvokeResponses. -export const INVOKE_RESPONSE_KEY: symbol = Symbol('invokeResponse'); - /** * A [BotAdapter](xref:botbuilder-core.BotAdapter) that can connect a bot to a service endpoint. * Implements [IUserTokenProvider](xref:botbuilder-core.IUserTokenProvider). @@ -835,14 +832,14 @@ export class BotFrameworkAdapter extends BotAdapter implements ExtendedUserToken status = value.status; body = value.body; } else { - status = 501; + status = StatusCodes.NOT_IMPLEMENTED; } } else if (request.deliveryMode === DeliveryModes.ExpectReplies) { const expectedReplies: ExpectedReplies = { activities: context.bufferedReplyActivities as Activity[] }; body = expectedReplies; status = StatusCodes.OK; } else { - status = 200; + status = StatusCodes.OK; } } catch (err) { // Catch the error to try and throw the stacktrace out of processActivity() @@ -938,7 +935,7 @@ export class BotFrameworkAdapter extends BotAdapter implements ExtendedUserToken responses.push({} as ResourceResponse); break; case 'invokeResponse': - // Cache response to context object. This will be retrieved when turn completes. + // Cache response to context object. This will be retrieved when turn completes. context.turnState.set(INVOKE_RESPONSE_KEY, activity); responses.push({} as ResourceResponse); break; @@ -1264,11 +1261,12 @@ export class BotFrameworkAdapter extends BotAdapter implements ExtendedUserToken if (body.type === ActivityTypes.Invoke) { let invokeResponse: any = context.turnState.get(INVOKE_RESPONSE_KEY); - if (invokeResponse && invokeResponse.value) { const value: InvokeResponse = invokeResponse.value; response.statusCode = value.status; - response.setBody(value.body); + if (value.body) { + response.setBody(value.body); + } } else { response.statusCode = StatusCodes.NOT_IMPLEMENTED; } diff --git a/libraries/botbuilder/src/index.ts b/libraries/botbuilder/src/index.ts index a9752778e5..68d286895e 100644 --- a/libraries/botbuilder/src/index.ts +++ b/libraries/botbuilder/src/index.ts @@ -8,8 +8,7 @@ export { BotFrameworkAdapter, - BotFrameworkAdapterSettings, - INVOKE_RESPONSE_KEY + BotFrameworkAdapterSettings } from './botFrameworkAdapter'; export { BotFrameworkHttpClient } from './botFrameworkHttpClient'; export { ChannelServiceHandler } from './channelServiceHandler'; diff --git a/libraries/botbuilder/src/teamsActivityHandler.ts b/libraries/botbuilder/src/teamsActivityHandler.ts index 3e1b0a4e73..accc816f46 100644 --- a/libraries/botbuilder/src/teamsActivityHandler.ts +++ b/libraries/botbuilder/src/teamsActivityHandler.ts @@ -6,8 +6,6 @@ * Licensed under the MIT License. */ -import { INVOKE_RESPONSE_KEY } from './botFrameworkAdapter'; - import { ActivityHandler, ActivityTypes, @@ -34,44 +32,17 @@ import { TeamsInfo } from './teamsInfo'; export class TeamsActivityHandler extends ActivityHandler { - /** - * - * @param context - */ - protected async onTurnActivity(context: TurnContext): Promise { - switch (context.activity.type) { - case ActivityTypes.Invoke: - const invokeResponse = await this.onInvokeActivity(context); - // If onInvokeActivity has already sent an InvokeResponse, do not send another one. - if (invokeResponse && !context.turnState.get(INVOKE_RESPONSE_KEY)) { - await context.sendActivity({ value: invokeResponse, type: 'invokeResponse' }); - } - await this.defaultNextEvent(context)(); - break; - default: - await super.onTurnActivity(context); - break; - } - } - /** * * @param context */ protected async onInvokeActivity(context: TurnContext): Promise { + let runEvents = true; try { if (!context.activity.name && context.activity.channelId === 'msteams') { return await this.handleTeamsCardActionInvoke(context); } else { switch (context.activity.name) { - case verifyStateOperationName: - await this.handleTeamsSigninVerifyState(context, context.activity.value); - return TeamsActivityHandler.createInvokeResponse(); - - case tokenExchangeOperationName: - await this.handleTeamsSigninTokenExchange(context, context.activity.value); - return TeamsActivityHandler.createInvokeResponse(); - case 'fileConsent/invoke': return TeamsActivityHandler.createInvokeResponse(await this.handleTeamsFileConsent(context, context.activity.value)); @@ -112,7 +83,8 @@ export class TeamsActivityHandler extends ActivityHandler { return TeamsActivityHandler.createInvokeResponse(await this.handleTeamsTaskModuleSubmit(context, context.activity.value)); default: - throw new Error('NotImplemented'); + runEvents = false; + return super.onInvokeActivity(context); } } } catch (err) { @@ -122,6 +94,10 @@ export class TeamsActivityHandler extends ActivityHandler { return { status: 400 }; } throw err; + } finally { + if (runEvents) { + this.defaultNextEvent(context)(); + } } } @@ -181,6 +157,18 @@ export class TeamsActivityHandler extends ActivityHandler { throw new Error('NotImplemented'); } + /* + * override default because to call teams specific events + */ + protected async onSignInInvoke(context: TurnContext): Promise { + switch (context.activity.name) { + case verifyStateOperationName: + await this.handleTeamsSigninVerifyState(context, context.activity.value); + case tokenExchangeOperationName: + await this.handleTeamsSigninTokenExchange(context, context.activity.value); + } + } + /** * Receives invoke activities with Activity name of 'signin/verifyState' * @param context