UNPKG

15.6 kBPlain TextView Raw
1/**
2 * @module botbuilder
3 */
4/**
5 * Copyright (c) Microsoft Corporation. All rights reserved.
6 * Licensed under the MIT License.
7 */
8
9import { Activity, AttachmentData, ConversationParameters, StatusCodes, Transcript } from 'botbuilder-core';
10
11import type { ChannelServiceHandlerBase } from './channelServiceHandlerBase';
12import { StatusCodeError } from './statusCodeError';
13import { WebRequest, WebResponse } from './interfaces';
14
15export type RouteHandler = (request: WebRequest, response: WebResponse) => void;
16
17import { validateAndFixActivity } from './activityValidator';
18
19import { RouteConstants } from './routeConstants';
20
21/**
22 * Interface representing an Express Application or a Restify Server.
23 */
24export interface WebServer {
25 get: (path: string, handler: RouteHandler) => void;
26 post: (path: string, handler: RouteHandler) => void;
27 put: (path: string, handler: RouteHandler) => void;
28 // For the DELETE HTTP Method, we need two optional methods:
29 // - Express 4.x has delete() - https://expressjs.com/en/4x/api.html#app.delete.method
30 // - restify has del() - http://restify.com/docs/server-api/#del
31 del?: (path: string, handler: RouteHandler) => void;
32 delete?: (path: string, handler: RouteHandler) => void;
33}
34
35/**
36 * Routes the API calls with the ChannelServiceHandler methods.
37 */
38export class ChannelServiceRoutes {
39 /**
40 * @param channelServiceHandler
41 */
42 constructor(private readonly channelServiceHandler: ChannelServiceHandlerBase) {}
43
44 /**
45 * Registers all Channel Service paths on the provided WebServer.
46 * @param server WebServer
47 * @param basePath Optional basePath which is appended before the service's REST API is configured on the WebServer.
48 */
49 public register(server: WebServer, basePath = ''): void {
50 server.post(basePath + RouteConstants.Activities, this.processSendToConversation.bind(this));
51 server.post(basePath + RouteConstants.Activity, this.processReplyToActivity.bind(this));
52 server.put(basePath + RouteConstants.Activity, this.processUpdateActivity.bind(this));
53 server.get(basePath + RouteConstants.ActivityMembers, this.processGetActivityMembers.bind(this));
54 server.post(basePath + RouteConstants.Conversations, this.processCreateConversation.bind(this));
55 server.get(basePath + RouteConstants.Conversations, this.processGetConversations.bind(this));
56 server.get(basePath + RouteConstants.ConversationMembers, this.processGetConversationMembers.bind(this));
57 server.get(
58 basePath + RouteConstants.ConversationPagedMembers,
59 this.processGetConversationPagedMembers.bind(this)
60 );
61 server.post(basePath + RouteConstants.ConversationHistory, this.processSendConversationHistory.bind(this));
62 server.post(basePath + RouteConstants.Attachments, this.processUploadAttachment.bind(this));
63
64 // Express 4.x uses the delete() method to register handlers for the DELETE method.
65 // Restify 8.x uses the del() method.
66 if (typeof server.delete === 'function') {
67 server.delete(
68 basePath + RouteConstants.ConversationMember,
69 this.processDeleteConversationMember.bind(this)
70 );
71 server.delete(basePath + RouteConstants.Activity, this.processDeleteActivity.bind(this));
72 } else if (typeof server.del === 'function') {
73 server.del(basePath + RouteConstants.ConversationMember, this.processDeleteConversationMember.bind(this));
74 server.del(basePath + RouteConstants.Activity, this.processDeleteActivity.bind(this));
75 }
76 }
77
78 /**
79 * @private
80 */
81 private processSendToConversation(req: WebRequest, res: WebResponse): void {
82 const authHeader = req.headers.authorization || req.headers.Authorization || '';
83 ChannelServiceRoutes.readActivity(req)
84 .then((activity) => {
85 this.channelServiceHandler
86 .handleSendToConversation(authHeader, req.params.conversationId, activity)
87 .then((resourceResponse) => {
88 res.status(200);
89 if (resourceResponse) {
90 res.send(resourceResponse);
91 }
92 res.end();
93 })
94 .catch((err) => {
95 ChannelServiceRoutes.handleError(err, res);
96 });
97 })
98 .catch((err) => {
99 ChannelServiceRoutes.handleError(err, res);
100 });
101 }
102
103 /**
104 * @private
105 */
106 private processReplyToActivity(req: WebRequest, res: WebResponse): void {
107 const authHeader = req.headers.authorization || req.headers.Authorization || '';
108 ChannelServiceRoutes.readActivity(req)
109 .then((activity) => {
110 this.channelServiceHandler
111 .handleReplyToActivity(authHeader, req.params.conversationId, req.params.activityId, activity)
112 .then((resourceResponse) => {
113 res.status(200);
114 if (resourceResponse) {
115 res.send(resourceResponse);
116 }
117 res.end();
118 })
119 .catch((err) => {
120 ChannelServiceRoutes.handleError(err, res);
121 });
122 })
123 .catch((err) => {
124 ChannelServiceRoutes.handleError(err, res);
125 });
126 }
127
128 /**
129 * @private
130 */
131 private processUpdateActivity(req: WebRequest, res: WebResponse): void {
132 const authHeader = req.headers.authorization || req.headers.Authorization || '';
133 ChannelServiceRoutes.readActivity(req)
134 .then((activity) => {
135 this.channelServiceHandler
136 .handleUpdateActivity(authHeader, req.params.conversationId, req.params.activityId, activity)
137 .then((resourceResponse) => {
138 res.status(200);
139 if (resourceResponse) {
140 res.send(resourceResponse);
141 }
142 res.end();
143 })
144 .catch((err) => {
145 ChannelServiceRoutes.handleError(err, res);
146 });
147 })
148 .catch((err) => {
149 ChannelServiceRoutes.handleError(err, res);
150 });
151 }
152
153 /**
154 * @private
155 */
156 private processDeleteActivity(req: WebRequest, res: WebResponse): void {
157 const authHeader = req.headers.authorization || req.headers.Authorization || '';
158 this.channelServiceHandler
159 .handleDeleteActivity(authHeader, req.params.conversationId, req.params.activityId)
160 .then(() => {
161 res.status(200);
162 res.end();
163 })
164 .catch((err) => {
165 ChannelServiceRoutes.handleError(err, res);
166 });
167 }
168
169 /**
170 * @private
171 */
172 private processGetActivityMembers(req: WebRequest, res: WebResponse): void {
173 const authHeader = req.headers.authorization || req.headers.Authorization || '';
174 this.channelServiceHandler
175 .handleGetActivityMembers(authHeader, req.params.conversationId, req.params.activityId)
176 .then((channelAccounts) => {
177 if (channelAccounts) {
178 res.send(channelAccounts);
179 }
180 res.status(200);
181 res.end();
182 })
183 .catch((err) => {
184 ChannelServiceRoutes.handleError(err, res);
185 });
186 }
187
188 /**
189 * @private
190 */
191 private processCreateConversation(req: WebRequest, res: WebResponse): void {
192 const authHeader = req.headers.authorization || req.headers.Authorization || '';
193 ChannelServiceRoutes.readBody<ConversationParameters>(req).then((conversationParameters) => {
194 this.channelServiceHandler
195 .handleCreateConversation(authHeader, conversationParameters)
196 .then((conversationResourceResponse) => {
197 if (conversationResourceResponse) {
198 res.send(conversationResourceResponse);
199 }
200 res.status(201);
201 res.end();
202 })
203 .catch((err) => {
204 ChannelServiceRoutes.handleError(err, res);
205 });
206 });
207 }
208
209 /**
210 * @private
211 */
212 private processGetConversations(req: WebRequest, res: WebResponse): void {
213 const authHeader = req.headers.authorization || req.headers.Authorization || '';
214 this.channelServiceHandler
215 .handleGetConversations(authHeader, req.params.conversationId, req.query.continuationToken)
216 .then((conversationsResult) => {
217 if (conversationsResult) {
218 res.send(conversationsResult);
219 }
220 res.status(200);
221 res.end();
222 })
223 .catch((err) => {
224 ChannelServiceRoutes.handleError(err, res);
225 });
226 }
227
228 /**
229 * @private
230 */
231 private processGetConversationMembers(req: WebRequest, res: WebResponse): void {
232 const authHeader = req.headers.authorization || req.headers.Authorization || '';
233 this.channelServiceHandler
234 .handleGetConversationMembers(authHeader, req.params.conversationId)
235 .then((channelAccounts) => {
236 res.status(200);
237 if (channelAccounts) {
238 res.send(channelAccounts);
239 }
240 res.end();
241 })
242 .catch((err) => {
243 ChannelServiceRoutes.handleError(err, res);
244 });
245 }
246
247 /**
248 * @private
249 */
250 private processGetConversationPagedMembers(req: WebRequest, res: WebResponse): void {
251 const authHeader = req.headers.authorization || req.headers.Authorization || '';
252 let pageSize = parseInt(req.query.pageSize);
253 if (isNaN(pageSize)) {
254 pageSize = undefined;
255 }
256 this.channelServiceHandler
257 .handleGetConversationPagedMembers(
258 authHeader,
259 req.params.conversationId,
260 pageSize,
261 req.query.continuationToken
262 )
263 .then((pagedMembersResult) => {
264 res.status(200);
265 if (pagedMembersResult) {
266 res.send(pagedMembersResult);
267 }
268 res.end();
269 })
270 .catch((err) => {
271 ChannelServiceRoutes.handleError(err, res);
272 });
273 }
274
275 /**
276 * @private
277 */
278 private processDeleteConversationMember(req: WebRequest, res: WebResponse): void {
279 const authHeader = req.headers.authorization || req.headers.Authorization || '';
280 this.channelServiceHandler
281 .handleDeleteConversationMember(authHeader, req.params.conversationId, req.params.memberId)
282 .then((resourceResponse) => {
283 res.status(200);
284 res.end();
285 })
286 .catch((err) => {
287 ChannelServiceRoutes.handleError(err, res);
288 });
289 }
290
291 /**
292 * @private
293 */
294 private processSendConversationHistory(req: WebRequest, res: WebResponse): void {
295 const authHeader = req.headers.authorization || req.headers.Authorization || '';
296 ChannelServiceRoutes.readBody<Transcript>(req)
297 .then((transcript) => {
298 this.channelServiceHandler
299 .handleSendConversationHistory(authHeader, req.params.conversationId, transcript)
300 .then((resourceResponse) => {
301 if (resourceResponse) {
302 res.send(resourceResponse);
303 }
304 res.status(200);
305 res.end();
306 })
307 .catch((err) => {
308 ChannelServiceRoutes.handleError(err, res);
309 });
310 })
311 .catch((err) => {
312 ChannelServiceRoutes.handleError(err, res);
313 });
314 }
315
316 /**
317 * @private
318 */
319 private processUploadAttachment(req: WebRequest, res: WebResponse): void {
320 const authHeader = req.headers.authorization || req.headers.Authorization || '';
321 ChannelServiceRoutes.readBody<AttachmentData>(req)
322 .then((attachmentData) => {
323 this.channelServiceHandler
324 .handleUploadAttachment(authHeader, req.params.conversationId, attachmentData)
325 .then((resourceResponse) => {
326 if (resourceResponse) {
327 res.send(resourceResponse);
328 }
329 res.status(200);
330 res.end();
331 })
332 .catch((err) => {
333 ChannelServiceRoutes.handleError(err, res);
334 });
335 })
336 .catch((err) => {
337 ChannelServiceRoutes.handleError(err, res);
338 });
339 }
340
341 /**
342 * @private
343 */
344 private static readActivity(req: WebRequest): Promise<Activity> {
345 return new Promise((resolve, reject) => {
346 if (req.body) {
347 try {
348 const activity = validateAndFixActivity(req.body);
349 resolve(activity);
350 } catch (err) {
351 reject(new StatusCodeError(StatusCodes.BAD_REQUEST, err.message));
352 }
353 } else {
354 let requestData = '';
355 req.on('data', (chunk) => {
356 requestData += chunk;
357 });
358 req.on('end', () => {
359 try {
360 const body = JSON.parse(requestData);
361 const activity = validateAndFixActivity(body);
362 resolve(activity);
363 } catch (err) {
364 reject(new StatusCodeError(StatusCodes.BAD_REQUEST, err.message));
365 }
366 });
367 }
368 });
369 }
370
371 /**
372 * @private
373 */
374 private static readBody<T>(req: WebRequest): Promise<T> {
375 return new Promise((resolve, reject) => {
376 if (req.body) {
377 try {
378 resolve(req.body);
379 } catch (err) {
380 reject(new StatusCodeError(StatusCodes.BAD_REQUEST, err.message));
381 }
382 } else {
383 let requestData = '';
384 req.on('data', (chunk) => {
385 requestData += chunk;
386 });
387 req.on('end', () => {
388 try {
389 const body = JSON.parse(requestData);
390 resolve(body);
391 } catch (err) {
392 reject(new StatusCodeError(StatusCodes.BAD_REQUEST, err.message));
393 }
394 });
395 }
396 });
397 }
398
399 /**
400 * @private
401 */
402 private static handleError(err: any, res: WebResponse): void {
403 res.status(typeof err?.statusCode === 'number' ? err.statusCode : 500);
404 res.send(err instanceof Error ? err.message : err);
405 res.end();
406 }
407}
408
\No newline at end of file