From 1f236f385faca6d3bee76f233ccefe43e5fc4856 Mon Sep 17 00:00:00 2001 From: johnataylor Date: Thu, 5 Dec 2019 14:55:35 -0800 Subject: [PATCH] Johtaylo/skills protocol (#1472) * init commit * controller equivalent in typescript --- libraries/botbuilder/package.json | 1 + .../botbuilder/src/botFrameworkAdapter.ts | 7 + .../botbuilder/src/botFrameworkHttpClient.ts | 52 ++-- .../botbuilder/src/channelServiceHandler.ts | 55 ++-- .../botbuilder/src/channelServiceRoutes.ts | 280 ++++++++++++++++++ libraries/botbuilder/src/index.ts | 2 + 6 files changed, 346 insertions(+), 51 deletions(-) create mode 100644 libraries/botbuilder/src/channelServiceRoutes.ts diff --git a/libraries/botbuilder/package.json b/libraries/botbuilder/package.json index 037f3b5981..0fa0c41e6b 100644 --- a/libraries/botbuilder/package.json +++ b/libraries/botbuilder/package.json @@ -22,6 +22,7 @@ "dependencies": { "@azure/ms-rest-js": "1.2.6", "@types/node": "^10.12.18", + "axios": "^0.19.0", "botbuilder-core": "4.1.6", "botframework-connector": "4.1.6", "botframework-streaming": "4.1.6", diff --git a/libraries/botbuilder/src/botFrameworkAdapter.ts b/libraries/botbuilder/src/botFrameworkAdapter.ts index 2fe5b14d2b..af495a1d47 100644 --- a/libraries/botbuilder/src/botFrameworkAdapter.ts +++ b/libraries/botbuilder/src/botFrameworkAdapter.ts @@ -26,6 +26,13 @@ export enum StatusCodes { NOT_IMPLEMENTED = 501, } +export class StatusCodeError extends Error { + constructor(public readonly statusCode: StatusCodes, message?: string) { + super(message); + this.statusCode = statusCode; + } +} + /** * Represents an Express or Restify request object. * diff --git a/libraries/botbuilder/src/botFrameworkHttpClient.ts b/libraries/botbuilder/src/botFrameworkHttpClient.ts index 8dc30e6920..fb4afa7631 100644 --- a/libraries/botbuilder/src/botFrameworkHttpClient.ts +++ b/libraries/botbuilder/src/botFrameworkHttpClient.ts @@ -6,7 +6,6 @@ * Licensed under the MIT License. */ -import { HttpClient, WebResource } from '@azure/ms-rest-js'; import { AuthenticationConstants, GovernmentConstants, @@ -14,6 +13,9 @@ import { JwtTokenValidation, MicrosoftAppCredentials } from 'botframework-connector'; + +import axios from 'axios'; + import { Activity } from 'botbuilder-core'; import { InvokeResponse, USER_AGENT } from './botFrameworkAdapter'; @@ -28,26 +30,18 @@ export class BotFrameworkHttpClient { */ private static readonly appCredentialMapCache: Map = new Map(); - constructor( - private readonly credentialProvider: ICredentialProvider, - private readonly channelService?: string, - private readonly httpClient?: HttpClient, - ) { - if (!this.channelService) { - this.channelService = process.env[AuthenticationConstants.ChannelService]; - } - if (!this.httpClient) { - throw new Error('BotFrameworkHttpClient(): missing httpClient'); - } + constructor(private readonly credentialProvider: ICredentialProvider, private readonly channelService?: string) { if (!this.credentialProvider) { throw new Error('BotFrameworkHttpClient(): missing credentialProvider'); } + if (!this.channelService) { + this.channelService = process.env[AuthenticationConstants.ChannelService]; + } } /** - * Forwards an activity to a skill (bot). + * Forwards an activity to a another bot. * @remarks - * NOTE: Forwarding an activity to a skill will flush UserState and ConversationState changes so that skill has accurate state. * * @param fromBotId The MicrosoftAppId of the bot sending the activity. * @param toBotId The MicrosoftAppId of the bot receiving the activity. @@ -59,11 +53,11 @@ export class BotFrameworkHttpClient { public async postActivity(fromBotId: string, toBotId: string, toUrl: string, serviceUrl: string, conversationId: string, activity: Activity): Promise { const appCredentials = await this.getAppCredentials(fromBotId, toBotId); if (!appCredentials) { - throw new Error('Unable to get appCredentials to connect to the skill'); + throw new Error('BotFrameworkHttpClient.postActivity(): Unable to get appCredentials to connect to the skill'); } // Get token for the skill call - const token = await appCredentials.getToken(); + const token = appCredentials.appId === '' && appCredentials.appPassword === '' ? null : await appCredentials.getToken(); // Capture current activity settings before changing them. // TODO: DO we need to set the activity ID? (events that are created manually don't have it). @@ -72,23 +66,21 @@ export class BotFrameworkHttpClient { try { activity.conversation.id = conversationId; activity.serviceUrl = serviceUrl; - const headers = { - Accept: 'application/json', - Authorization: `Bearer ${ token }`, - 'Content-Type': 'application/json', - 'User-Agent': USER_AGENT + const config = { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + 'User-Agent': USER_AGENT + } }; - const request = new WebResource( - toUrl, - 'POST', - activity, - undefined, - headers - ); + if (token !== null) { + config.headers['Authorization'] = `Bearer ${ token }`; + } + + const response = await axios.post(toUrl, activity, config); - const response = await this.httpClient.sendRequest(request); - const invokeResponse: InvokeResponse = { status: response.status, body: response.parsedBody }; + const invokeResponse: InvokeResponse = { status: response.status, body: response.data }; return invokeResponse; } finally { diff --git a/libraries/botbuilder/src/channelServiceHandler.ts b/libraries/botbuilder/src/channelServiceHandler.ts index 0651ae9968..6a4c3fc094 100644 --- a/libraries/botbuilder/src/channelServiceHandler.ts +++ b/libraries/botbuilder/src/channelServiceHandler.ts @@ -26,7 +26,7 @@ import { ICredentialProvider, JwtTokenValidation } from 'botframework-connector'; -import { StatusCodes } from './botFrameworkAdapter'; +import { StatusCodeError, StatusCodes } from './botFrameworkAdapter'; /** * The ChannelServiceHandler implements API to forward activity to a skill and @@ -79,9 +79,9 @@ export class ChannelServiceHandler { return await this.onGetActivityMembers(claimsIdentity, conversationId, activityId); } - public async handleCreateConversation(authHeader: string, conversationId: string, parameters: ConversationParameters): Promise { + public async handleCreateConversation(authHeader: string, parameters: ConversationParameters): Promise { const claimsIdentity = await this.authenticate(authHeader); - return await this.onCreateConversation(claimsIdentity, conversationId, parameters); + return await this.onCreateConversation(claimsIdentity, parameters); } public async handleGetConversations(authHeader: string, conversationId: string, continuationToken?: string /* some default */): Promise { @@ -89,7 +89,7 @@ export class ChannelServiceHandler { return await this.onGetConversations(claimsIdentity, conversationId, continuationToken); } - public async HandleGetConversationMembers(authHeader: string, conversationId: string): Promise { + public async handleGetConversationMembers(authHeader: string, conversationId: string): Promise { const claimsIdentity = await this.authenticate(authHeader); return await this.onGetConversationMembers(claimsIdentity, conversationId); } @@ -163,7 +163,7 @@ export class ChannelServiceHandler { * @param activity Activity to send. */ protected async onReplyToActivity(claimsIdentity: ClaimsIdentity, conversationId: string, activityId: string, activity: Activity): Promise { - throw new Error(`ChannelServiceHandler.onReplyToActivity(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); + throw new StatusCodeError(StatusCodes.NOT_IMPLEMENTED, `ChannelServiceHandler.onReplyToActivity(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); } /** @@ -181,7 +181,7 @@ export class ChannelServiceHandler { * @param activity replacement Activity. */ protected async onUpdateActivity(claimsIdentity: ClaimsIdentity, conversationId: string, activityId: string, activity: Activity): Promise { - throw new Error(`ChannelServiceHandler.onUpdateActivity(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); + throw new StatusCodeError(StatusCodes.NOT_IMPLEMENTED, `ChannelServiceHandler.onUpdateActivity(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); } /** @@ -198,7 +198,7 @@ export class ChannelServiceHandler { * @param activityId activityId to delete. */ protected async onDeleteActivity(claimsIdentity: ClaimsIdentity, conversationId: string, activityId: string): Promise { - throw new Error(`ChannelServiceHandler.onDeleteActivity(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); + throw new StatusCodeError(StatusCodes.NOT_IMPLEMENTED, `ChannelServiceHandler.onDeleteActivity(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); } /** @@ -213,9 +213,8 @@ export class ChannelServiceHandler { * @param conversationId Conversation ID. * @param activityId Activity ID. */ - protected async onGetActivityMembers(claimsIdentity: ClaimsIdentity, conversationId: string, activityId: string): Promise - { - throw new Error(`ChannelServiceHandler.onGetActivityMembers(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); + protected async onGetActivityMembers(claimsIdentity: ClaimsIdentity, conversationId: string, activityId: string): Promise { + throw new StatusCodeError(StatusCodes.NOT_IMPLEMENTED, `ChannelServiceHandler.onGetActivityMembers(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); } /** @@ -237,8 +236,8 @@ export class ChannelServiceHandler { * @param conversationId Conversation ID. * @param parameters Parameters to create the conversation from. */ - protected async onCreateConversation(claimsIdentity: ClaimsIdentity, conversationId: string, parameters: ConversationParameters): Promise { - throw new Error(`ChannelServiceHandler.onCreateConversation(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); + protected async onCreateConversation(claimsIdentity: ClaimsIdentity, parameters: ConversationParameters): Promise { + throw new StatusCodeError(StatusCodes.NOT_IMPLEMENTED, `ChannelServiceHandler.onCreateConversation(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); } /** @@ -260,7 +259,7 @@ export class ChannelServiceHandler { * @param continuationToken Skip or continuation token. */ protected async onGetConversations(claimsIdentity: ClaimsIdentity, conversationId: string, continuationToken?: string): Promise { - throw new Error(`ChannelServiceHandler.onGetConversations(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); + throw new StatusCodeError(StatusCodes.NOT_IMPLEMENTED, `ChannelServiceHandler.onGetConversations(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); } /** @@ -274,7 +273,7 @@ export class ChannelServiceHandler { * @param conversationId Conversation ID. */ protected async onGetConversationMembers(claimsIdentity: ClaimsIdentity, conversationId: string): Promise { - throw new Error(`ChannelServiceHandler.onGetConversationMembers(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); + throw new StatusCodeError(StatusCodes.NOT_IMPLEMENTED, `ChannelServiceHandler.onGetConversationMembers(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); } /** @@ -302,9 +301,8 @@ export class ChannelServiceHandler { * @param pageSize Suggested page size. * @param continuationToken Continuation Token. */ - protected async onGetConversationPagedMembers(claimsIdentity: ClaimsIdentity, conversationId: string, pageSize: number = -1, continuationToken?: string): Promise - { - throw new Error(`ChannelServiceHandler.onGetConversationPagedMembers(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); + protected async onGetConversationPagedMembers(claimsIdentity: ClaimsIdentity, conversationId: string, pageSize: number = -1, continuationToken?: string): Promise { + throw new StatusCodeError(StatusCodes.NOT_IMPLEMENTED, `ChannelServiceHandler.onGetConversationPagedMembers(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); } /** @@ -320,7 +318,7 @@ export class ChannelServiceHandler { * @param memberId ID of the member to delete from this conversation. */ protected async onDeleteConversationMember(claimsIdentity: ClaimsIdentity, conversationId: string, memberId: string): Promise { - throw new Error(`ChannelServiceHandler.onDeleteConversationMember(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); + throw new StatusCodeError(StatusCodes.NOT_IMPLEMENTED, `ChannelServiceHandler.onDeleteConversationMember(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); } /** @@ -338,7 +336,7 @@ export class ChannelServiceHandler { * @param transcript Transcript of activities. */ protected async onSendConversationHistory(claimsIdentity: ClaimsIdentity, conversationId: string, transcript: Transcript): Promise { - throw new Error(`ChannelServiceHandler.onSendConversationHistory(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); + throw new StatusCodeError(StatusCodes.NOT_IMPLEMENTED, `ChannelServiceHandler.onSendConversationHistory(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); } /** @@ -356,11 +354,26 @@ export class ChannelServiceHandler { * @param attachmentUpload Attachment data. */ protected async onUploadAttachment(claimsIdentity: ClaimsIdentity, conversationId: string, attachmentUpload: AttachmentData): Promise { - throw new Error(`ChannelServiceHandler.onUploadAttachment(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); + throw new StatusCodeError(StatusCodes.NOT_IMPLEMENTED, `ChannelServiceHandler.onUploadAttachment(): ${StatusCodes.NOT_IMPLEMENTED}: ${STATUS_CODES[StatusCodes.NOT_IMPLEMENTED]}`); } private async authenticate(authHeader: string): Promise { - return await JwtTokenValidation.validateAuthHeader(authHeader, this.credentialProvider, this.channelService, 'unknown', undefined, this.authConfig); + try { + if (!authHeader) { + const isAuthDisable = this.credentialProvider.isAuthenticationDisabled() + if (isAuthDisable) { + // In the scenario where Auth is disabled, we still want to have the + // IsAuthenticated flag set in the ClaimsIdentity. To do this requires + // adding in an empty claim. + return new ClaimsIdentity([], false); + } + } + + return await JwtTokenValidation.validateAuthHeader(authHeader, this.credentialProvider, this.channelService, 'unknown', undefined, this.authConfig); + } + catch (err) { + throw new StatusCodeError(StatusCodes.UNAUTHORIZED); + } } } diff --git a/libraries/botbuilder/src/channelServiceRoutes.ts b/libraries/botbuilder/src/channelServiceRoutes.ts new file mode 100644 index 0000000000..5aca0f8509 --- /dev/null +++ b/libraries/botbuilder/src/channelServiceRoutes.ts @@ -0,0 +1,280 @@ +/** + * @module botbuilder + */ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { ChannelServiceHandler } from './channelServiceHandler'; +import { Activity, ConversationParameters, Transcript, AttachmentData } from 'botbuilder-core'; +import { WebRequest, WebResponse, StatusCodeError } from './botFrameworkAdapter'; + +export class ChannelServiceRoutes { + + constructor(private readonly channelServiceHandler: ChannelServiceHandler) { + this.channelServiceHandler = channelServiceHandler; + } + + register(baseAddress, server) { + server.post(baseAddress + '/v3/conversations/:conversationId/activities', this.processSendToConversation.bind(this)); + server.post(baseAddress + '/v3/conversations/:conversationId/activities/:activityId', this.processReplyToActivity.bind(this)); + server.put(baseAddress + '/v3/conversations/:conversationId/activities/:activityId', this.processUpdateActivity.bind(this)); + server.del(baseAddress + '/v3/conversations/:conversationId/activities/:activityId', this.processDeleteActivity.bind(this)); + server.get(baseAddress + '/v3/conversations/:conversationId/activities/:activityId/members', this.processGetActivityMembers.bind(this)); + server.post(baseAddress + '/v3/conversations', this.processCreateConversation.bind(this)); + server.get(baseAddress + '/v3/conversations', this.processGetConversations.bind(this)); + server.get(baseAddress + '/v3/conversations/:conversationId/members', this.processGetConversationMembers.bind(this)); + server.get(baseAddress + '/v3/conversations/:conversationId/pagedmembers', this.processGetConversationPagedMembers.bind(this)); + server.del(baseAddress + '/v3/conversations/:conversationId/members/:memberId', this.processDeleteConversationMember.bind(this)); + server.post(baseAddress + '/v3/conversations/:conversationId/activities/history', this.processSendConversationHistory.bind(this)); + server.post(baseAddress + '/v3/conversations/:conversationId/attachments', this.processUploadAttachment.bind(this)); + } + + processSendToConversation(req, res) { + const authHeader = req.headers.authorization || req.headers.Authorization || ''; + ChannelServiceRoutes.readActivity(req) + .then((activity) => { + this.channelServiceHandler.handleSendToConversation(authHeader, req.params.conversationId, activity) + .then((resourceResponse) => { + res.status(200); + if (resourceResponse) { + res.send(resourceResponse); + } + res.end(); + }) + .catch(err => { ChannelServiceRoutes.handleError(err, res); }) + }); + } + + processReplyToActivity(req, res) { + const authHeader = req.headers.authorization || req.headers.Authorization || ''; + ChannelServiceRoutes.readActivity(req) + .then((activity) => { + this.channelServiceHandler.handleReplyToActivity(authHeader, req.params.conversationId, req.params.activityId, activity) + .then((resourceResponse) => { + res.status(200); + if (resourceResponse) { + res.send(resourceResponse); + } + res.end(); + }) + .catch(err => { ChannelServiceRoutes.handleError(err, res); }) + }); + } + + processUpdateActivity(req, res) { + const authHeader = req.headers.authorization || req.headers.Authorization || ''; + ChannelServiceRoutes.readActivity(req) + .then((activity) => { + this.channelServiceHandler.handleUpdateActivity(authHeader, req.params.conversationId, req.params.activityId, activity) + .then((resourceResponse) => { + res.status(200); + if (resourceResponse) { + res.send(resourceResponse); + } + res.end(); + }) + .catch(err => { ChannelServiceRoutes.handleError(err, res); }) + }); + } + + processDeleteActivity(req, res) { + const authHeader = req.headers.authorization || req.headers.Authorization || ''; + this.channelServiceHandler.handleDeleteActivity(authHeader, req.params.conversationId, req.params.activityId) + .then(() => { + res.status(200); + res.end(); + }) + .catch(err => { ChannelServiceRoutes.handleError(err, res); }); + } + + processGetActivityMembers(req, res) { + const authHeader = req.headers.authorization || req.headers.Authorization || ''; + this.channelServiceHandler.handleGetActivityMembers(authHeader, req.params.conversationId, req.params.activityId) + .then((channelAccounts) => { + if (channelAccounts) { + res.send(channelAccounts); + } + res.status(200); + res.end(); + }) + .catch(err => { ChannelServiceRoutes.handleError(err, res); }); + } + + processCreateConversation(req, res) { + const authHeader = req.headers.authorization || req.headers.Authorization || ''; + ChannelServiceRoutes.readBody(req) + .then((conversationParameters) => { + this.channelServiceHandler.handleCreateConversation(authHeader, conversationParameters) + .then((conversationResourceResponse) => { + if (conversationResourceResponse) { + res.send(conversationResourceResponse); + } + res.status(201); + res.end(); + }) + .catch(err => { ChannelServiceRoutes.handleError(err, res); }) + }); + } + + processGetConversations(req, res) { + const authHeader = req.headers.authorization || req.headers.Authorization || ''; + this.channelServiceHandler.handleGetConversations(authHeader, req.params.conversationId, req.query.continuationToken) + .then((conversationsResult) => { + if (conversationsResult) { + res.send(conversationsResult); + } + res.status(200); + res.end(); + }) + .catch(err => { ChannelServiceRoutes.handleError(err, res); }); + } + + processGetConversationMembers(req, res) { + const authHeader = req.headers.authorization || req.headers.Authorization || ''; + this.channelServiceHandler.handleGetConversationMembers(authHeader, req.params.conversationId) + .then((channelAccounts) => { + res.status(200); + if (channelAccounts) { + res.send(channelAccounts); + } + res.end(); + }) + .catch(err => { ChannelServiceRoutes.handleError(err, res); }); + } + + processGetConversationPagedMembers(req, res) { + const authHeader = req.headers.authorization || req.headers.Authorization || ''; + let pageSize = parseInt(req.query.pageSize); + if (isNaN(pageSize)) + { + pageSize = undefined; + } + this.channelServiceHandler.handleGetConversationPagedMembers( + authHeader, + req.params.conversationId, + pageSize, + req.query.continuationToken) + .then((pagedMembersResult) => { + res.status(200); + if (pagedMembersResult) { + res.send(pagedMembersResult); + } + res.end(); + }) + .catch(err => { ChannelServiceRoutes.handleError(err, res); }); + } + + processDeleteConversationMember(req, res) { + const authHeader = req.headers.authorization || req.headers.Authorization || ''; + this.channelServiceHandler.handleDeleteConversationMember(authHeader, req.params.conversationId, req.params.memberId) + .then((resourceResponse) => { + res.status(200); + res.end(); + }) + .catch(err => { ChannelServiceRoutes.handleError(err, res); }); + } + + processSendConversationHistory(req, res) { + const authHeader = req.headers.authorization || req.headers.Authorization || ''; + ChannelServiceRoutes.readBody(req) + .then((transcript) => { + this.channelServiceHandler.handleSendConversationHistory(authHeader, req.params.conversationId, transcript) + .then((resourceResponse) => { + if (resourceResponse) { + res.send(resourceResponse); + } + res.status(201); + res.end(); + }) + .catch(err => { ChannelServiceRoutes.handleError(err, res); }) + }); + } + + processUploadAttachment(req, res) { + const authHeader = req.headers.authorization || req.headers.Authorization || ''; + ChannelServiceRoutes.readBody(req) + .then((attachmentData) => { + this.channelServiceHandler.handleUploadAttachment(authHeader, req.params.conversationId, attachmentData) + .then((resourceResponse) => { + if (resourceResponse) { + res.send(resourceResponse); + } + res.status(201); + res.end(); + }) + .catch(err => { ChannelServiceRoutes.handleError(err, res); }) + }); + } + + static readActivity(req: WebRequest) : Promise { + return new Promise((resolve, reject) => { + function returnActivity(activity) { + if (typeof activity !== 'object') { throw new Error(`BotFrameworkAdapter.parseRequest(): invalid request body.`); } + if (typeof activity.type !== 'string') { throw new Error(`BotFrameworkAdapter.parseRequest(): missing activity type.`); } + if (typeof activity.timestamp === 'string') { activity.timestamp = new Date(activity.timestamp); } + if (typeof activity.localTimestamp === 'string') { activity.localTimestamp = new Date(activity.localTimestamp); } + if (typeof activity.expiration === 'string') { activity.expiration = new Date(activity.expiration); } + resolve(activity); + } + + if (req.body) { + try { + returnActivity(req.body); + } catch (err) { + reject(err); + } + } else { + let requestData = ''; + req.on('data', (chunk) => { + requestData += chunk; + }); + req.on('end', () => { + try { + const body = JSON.parse(requestData); + returnActivity(body); + } catch (err) { + reject(err); + } + }); + } + }); + } + + static readBody(req: WebRequest) : Promise { + return new Promise((resolve, reject) => { + if (req.body) { + try { + resolve(req.body); + } catch (err) { + reject(err); + } + } else { + let requestData = ''; + req.on('data', (chunk) => { + requestData += chunk; + }); + req.on('end', () => { + try { + const body = JSON.parse(requestData); + resolve(body); + } catch (err) { + reject(err); + } + }); + } + }); + } + + static handleError(err: any, res: WebResponse) { + if (err instanceof StatusCodeError) { + res.status(err.statusCode); + } else { + res.status(500); + } + res.end(); + } +} + +module.exports = { ChannelServiceRoutes: ChannelServiceRoutes }; diff --git a/libraries/botbuilder/src/index.ts b/libraries/botbuilder/src/index.ts index 1286d564f3..beea788959 100644 --- a/libraries/botbuilder/src/index.ts +++ b/libraries/botbuilder/src/index.ts @@ -12,11 +12,13 @@ export { InvokeResponse, INVOKE_RESPONSE_KEY, StatusCodes, + StatusCodeError, WebRequest, WebResponse } from './botFrameworkAdapter'; export { BotFrameworkHttpClient } from './botFrameworkHttpClient'; export { ChannelServiceHandler } from './channelServiceHandler'; +export { ChannelServiceRoutes } from './channelServiceRoutes'; export * from './fileTranscriptStore'; export * from './inspectionMiddleware'; export * from './streaming';