botbuilder
Version:
Bot Builder is a framework for building rich bots on virtually any platform.
448 lines (422 loc) • 17.4 kB
text/typescript
/**
* @module botbuilder
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { Activity, AttachmentData, ConversationParameters, StatusCodes, Transcript } from 'botbuilder-core';
import type { ChannelServiceHandlerBase } from './channelServiceHandlerBase';
import { StatusCodeError } from './statusCodeError';
import { WebRequest, WebResponse } from './interfaces';
export type RouteHandler = (request: WebRequest, response: WebResponse) => void;
import { validateAndFixActivity } from './activityValidator';
import { RouteConstants } from './routeConstants';
/**
* Interface representing an Express Application or a Restify Server.
*/
export interface WebServer {
get: (path: string, handler: RouteHandler) => void;
post: (path: string, handler: RouteHandler) => void;
put: (path: string, handler: RouteHandler) => void;
// For the DELETE HTTP Method, we need two optional methods:
// - Express 4.x has delete() - https://expressjs.com/en/4x/api.html#app.delete.method
// - restify has del() - http://restify.com/docs/server-api/#del
del?: (path: string, handler: RouteHandler) => void;
delete?: (path: string, handler: RouteHandler) => void;
}
/**
* Routes the API calls with the ChannelServiceHandler methods.
*/
export class ChannelServiceRoutes {
/**
* @param channelServiceHandler The channel service handler.
*/
constructor(private readonly channelServiceHandler: ChannelServiceHandlerBase) {}
/**
* Registers all Channel Service paths on the provided WebServer.
*
* @param server WebServer
* @param basePath Optional basePath which is appended before the service's REST API is configured on the WebServer.
*/
register(server: WebServer, basePath = ''): void {
server.post(basePath + RouteConstants.Activities, this.processSendToConversation.bind(this));
server.post(basePath + RouteConstants.Activity, this.processReplyToActivity.bind(this));
server.put(basePath + RouteConstants.Activity, this.processUpdateActivity.bind(this));
server.get(basePath + RouteConstants.ActivityMembers, this.processGetActivityMembers.bind(this));
server.post(basePath + RouteConstants.Conversations, this.processCreateConversation.bind(this));
server.get(basePath + RouteConstants.Conversations, this.processGetConversations.bind(this));
server.get(basePath + RouteConstants.ConversationMembers, this.processGetConversationMembers.bind(this));
server.get(basePath + RouteConstants.ConversationMember, this.processGetConversationMember.bind(this));
server.get(
basePath + RouteConstants.ConversationPagedMembers,
this.processGetConversationPagedMembers.bind(this),
);
server.post(basePath + RouteConstants.ConversationHistory, this.processSendConversationHistory.bind(this));
server.post(basePath + RouteConstants.Attachments, this.processUploadAttachment.bind(this));
// Express 4.x uses the delete() method to register handlers for the DELETE method.
// Restify 8.x uses the del() method.
if (typeof server.delete === 'function') {
server.delete(
basePath + RouteConstants.ConversationMember,
this.processDeleteConversationMember.bind(this),
);
server.delete(basePath + RouteConstants.Activity, this.processDeleteActivity.bind(this));
} else if (typeof server.del === 'function') {
server.del(basePath + RouteConstants.ConversationMember, this.processDeleteConversationMember.bind(this));
server.del(basePath + RouteConstants.Activity, this.processDeleteActivity.bind(this));
}
}
/**
* @private
*/
private processSendToConversation(req: WebRequest, res: WebResponse, next: () => void): void {
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();
return next();
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
}
/**
* @private
*/
private processReplyToActivity(req: WebRequest, res: WebResponse, next: () => void): void {
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();
return next();
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
}
/**
* @private
*/
private processUpdateActivity(req: WebRequest, res: WebResponse, next: () => void): void {
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();
return next();
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
}
/**
* @private
*/
private processDeleteActivity(req: WebRequest, res: WebResponse, next: () => void): void {
const authHeader = req.headers.authorization || req.headers.Authorization || '';
this.channelServiceHandler
.handleDeleteActivity(authHeader, req.params.conversationId, req.params.activityId)
.then(() => {
res.status(200);
res.end();
return next();
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
}
/**
* @private
*/
private processGetActivityMembers(req: WebRequest, res: WebResponse, next: () => void): void {
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();
return next();
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
}
/**
* @private
*/
private processCreateConversation(req: WebRequest, res: WebResponse, next: () => void): void {
const authHeader = req.headers.authorization || req.headers.Authorization || '';
ChannelServiceRoutes.readBody<ConversationParameters>(req).then((conversationParameters) => {
this.channelServiceHandler
.handleCreateConversation(authHeader, conversationParameters)
.then((conversationResourceResponse) => {
if (conversationResourceResponse) {
res.send(conversationResourceResponse);
}
res.status(201);
res.end();
return next();
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
});
}
/**
* @private
*/
private processGetConversations(req: WebRequest, res: WebResponse, next: () => void): void {
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();
return next();
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
}
/**
* @private
*/
private processGetConversationMembers(req: WebRequest, res: WebResponse, next: () => void): void {
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();
return next();
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
}
/**
* @private
*/
private processGetConversationMember(req: WebRequest, res: WebResponse, next: () => void): void {
const authHeader = req.headers.authorization || req.headers.Authorization || '';
this.channelServiceHandler
.handleGetConversationMember(authHeader, req.params.memberId, req.params.conversationId)
.then((channelAccount) => {
res.status(200);
if (channelAccount) {
res.send(channelAccount);
}
res.end();
return next();
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
}
/**
* @private
*/
private processGetConversationPagedMembers(req: WebRequest, res: WebResponse, next: () => void): void {
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();
return next();
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
}
/**
* @private
*/
private processDeleteConversationMember(req: WebRequest, res: WebResponse, next: () => void): void {
const authHeader = req.headers.authorization || req.headers.Authorization || '';
this.channelServiceHandler
.handleDeleteConversationMember(authHeader, req.params.conversationId, req.params.memberId)
.then(() => {
res.status(200);
res.end();
return next();
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
}
/**
* @private
*/
private processSendConversationHistory(req: WebRequest, res: WebResponse, next: () => void): void {
const authHeader = req.headers.authorization || req.headers.Authorization || '';
ChannelServiceRoutes.readBody<Transcript>(req)
.then((transcript) => {
this.channelServiceHandler
.handleSendConversationHistory(authHeader, req.params.conversationId, transcript)
.then((resourceResponse) => {
if (resourceResponse) {
res.send(resourceResponse);
}
res.status(200);
res.end();
return next();
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
}
/**
* @private
*/
private processUploadAttachment(req: WebRequest, res: WebResponse, next: () => void): void {
const authHeader = req.headers.authorization || req.headers.Authorization || '';
ChannelServiceRoutes.readBody<AttachmentData>(req)
.then((attachmentData) => {
this.channelServiceHandler
.handleUploadAttachment(authHeader, req.params.conversationId, attachmentData)
.then((resourceResponse) => {
if (resourceResponse) {
res.send(resourceResponse);
}
res.status(200);
res.end();
return next();
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
})
.catch((err) => {
ChannelServiceRoutes.handleError(err, res);
});
}
/**
* @private
*/
private static readActivity(req: WebRequest): Promise<Activity> {
return new Promise((resolve, reject) => {
if (req.body) {
try {
const activity = validateAndFixActivity(req.body);
resolve(activity);
} catch (err) {
reject(new StatusCodeError(StatusCodes.BAD_REQUEST, err.message));
}
} else {
let requestData = '';
req.on('data', (chunk) => {
requestData += chunk;
});
req.on('end', () => {
try {
const body = JSON.parse(requestData);
const activity = validateAndFixActivity(body);
resolve(activity);
} catch (err) {
reject(new StatusCodeError(StatusCodes.BAD_REQUEST, err.message));
}
});
}
});
}
/**
* @private
*/
private static readBody<T>(req: WebRequest): Promise<T> {
return new Promise((resolve, reject) => {
if (req.body) {
try {
resolve(req.body);
} catch (err) {
reject(new StatusCodeError(StatusCodes.BAD_REQUEST, err.message));
}
} else {
let requestData = '';
// eslint-disable-next-line prettier/prettier
req.on('data', (chunk) => { // CodeQL [SM1524] validation of the data is being made in the 'end' event
requestData += chunk;
});
req.on('end', () => {
try {
const body = JSON.parse(requestData);
resolve(body);
} catch (err) {
reject(new StatusCodeError(StatusCodes.BAD_REQUEST, err.message));
}
});
}
});
}
/**
* @private
*/
private static handleError(err: any, res: WebResponse): void {
res.status(typeof err?.statusCode === 'number' ? err.statusCode : 500);
if (err instanceof Error) {
res.send(err.message);
} else {
res.send('An error occurred while processing the request.');
console.error(err);
}
res.end();
}
}