UNPKG

14.3 kBJavaScriptView Raw
1"use strict";
2// Copyright (c) Microsoft Corporation.
3// Licensed under the MIT License.
4var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
5 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6 return new (P || (P = Promise))(function (resolve, reject) {
7 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
8 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
9 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10 step((generator = generator.apply(thisArg, _arguments || [])).next());
11 });
12};
13Object.defineProperty(exports, "__esModule", { value: true });
14exports.CloudAdapter = void 0;
15const z = require("zod");
16const botbuilder_core_1 = require("botbuilder-core");
17const streaming_1 = require("./streaming");
18const ms_rest_js_1 = require("@azure/ms-rest-js");
19const zod_1 = require("./zod");
20const interfaces_1 = require("./interfaces");
21const botFrameworkAdapter_1 = require("./botFrameworkAdapter");
22const botbuilder_stdlib_1 = require("botbuilder-stdlib");
23const activityValidator_1 = require("./activityValidator");
24const botframework_connector_1 = require("botframework-connector");
25const botframework_streaming_1 = require("botframework-streaming");
26// Note: this is _okay_ because we pass the result through `validateAndFixActivity`. Should not be used otherwise.
27const ActivityT = z.custom((val) => z.record(z.unknown()).check(val), { message: 'Activity' });
28class CloudAdapter extends botbuilder_core_1.CloudAdapterBase {
29 /**
30 * Initializes a new instance of the [CloudAdapter](xref:botbuilder:CloudAdapter) class.
31 *
32 * @param botFrameworkAuthentication Optional [BotFrameworkAuthentication](xref:botframework-connector.BotFrameworkAuthentication) instance
33 */
34 constructor(botFrameworkAuthentication = botframework_connector_1.BotFrameworkAuthenticationFactory.create()) {
35 super(botFrameworkAuthentication);
36 }
37 /**
38 * @internal
39 */
40 process(req, resOrSocket, logicOrHead, maybeLogic) {
41 var _a, _b, _c, _d;
42 return __awaiter(this, void 0, void 0, function* () {
43 // Early return with web socket handler if function invocation matches that signature
44 if (maybeLogic) {
45 const socket = zod_1.INodeSocketT.parse(resOrSocket);
46 const head = zod_1.INodeBufferT.parse(logicOrHead);
47 const logic = zod_1.LogicT.parse(maybeLogic);
48 return this.connect(req, socket, head, logic);
49 }
50 const res = interfaces_1.ResponseT.parse(resOrSocket);
51 const logic = zod_1.LogicT.parse(logicOrHead);
52 const end = (status, body) => {
53 res.status(status);
54 if (body) {
55 res.send(body);
56 }
57 res.end();
58 };
59 // Only POST requests from here on out
60 if (req.method !== 'POST') {
61 return end(botbuilder_core_1.StatusCodes.METHOD_NOT_ALLOWED);
62 }
63 // Ensure we have a parsed request body already. We rely on express/restify middleware to parse
64 // request body and azure functions, which does it for us before invoking our code. Warn the user
65 // to update their code and return an error.
66 if (!z.record(z.unknown()).check(req.body)) {
67 return end(botbuilder_core_1.StatusCodes.BAD_REQUEST, '`req.body` not an object, make sure you are using middleware to parse incoming requests.');
68 }
69 const activity = activityValidator_1.validateAndFixActivity(ActivityT.parse(req.body));
70 if (!activity.type) {
71 return end(botbuilder_core_1.StatusCodes.BAD_REQUEST);
72 }
73 const authHeader = z.string().parse((_b = (_a = req.headers.Authorization) !== null && _a !== void 0 ? _a : req.headers.authorization) !== null && _b !== void 0 ? _b : '');
74 try {
75 const invokeResponse = yield this.processActivity(authHeader, activity, logic);
76 return end((_c = invokeResponse === null || invokeResponse === void 0 ? void 0 : invokeResponse.status) !== null && _c !== void 0 ? _c : botbuilder_core_1.StatusCodes.OK, invokeResponse === null || invokeResponse === void 0 ? void 0 : invokeResponse.body);
77 }
78 catch (err) {
79 return end(err instanceof botframework_connector_1.AuthenticationError ? botbuilder_core_1.StatusCodes.UNAUTHORIZED : botbuilder_core_1.StatusCodes.INTERNAL_SERVER_ERROR, (_d = err.message) !== null && _d !== void 0 ? _d : err);
80 }
81 });
82 }
83 /**
84 * Used to connect the adapter to a named pipe.
85 *
86 * @param pipeName Pipe name to connect to (note: yields two named pipe servers by appending ".incoming" and ".outgoing" to this name)
87 * @param logic The logic function to call for resulting bot turns.
88 * @param appId The Bot application ID
89 * @param audience The audience to use for outbound communication. The will vary by cloud environment.
90 * @param callerId Optional, the caller ID
91 * @param retryCount Optional, the number of times to retry a failed connection (defaults to 7)
92 */
93 connectNamedPipe(pipeName, logic, appId, audience, callerId, retryCount = 7) {
94 return __awaiter(this, void 0, void 0, function* () {
95 z.object({
96 pipeName: z.string(),
97 logic: zod_1.LogicT,
98 appId: z.string(),
99 audience: z.string(),
100 callerId: z.string().optional(),
101 }).parse({ pipeName, logic, appId, audience, callerId });
102 // The named pipe is local and so there is no network authentication to perform: so we can create the result here.
103 const authenticateRequestResult = {
104 audience,
105 callerId,
106 claimsIdentity: appId ? this.createClaimsIdentity(appId) : new botframework_connector_1.ClaimsIdentity([]),
107 };
108 // Creat request handler
109 const requestHandler = new StreamingRequestHandler(authenticateRequestResult, (authenticateRequestResult, activity) => this.processActivity(authenticateRequestResult, activity, logic));
110 // Create server
111 const server = new botframework_streaming_1.NamedPipeServer(pipeName, requestHandler);
112 // Attach server to request handler for outbound requests
113 requestHandler.server = server;
114 // Spin it up
115 yield botbuilder_stdlib_1.retry(() => server.start(), retryCount);
116 });
117 }
118 connect(req, socket, head, logic) {
119 var _a, _b;
120 return __awaiter(this, void 0, void 0, function* () {
121 // Grab the auth header from the inbound http request
122 const authHeader = z.string().parse((_b = (_a = req.headers.Authorization) !== null && _a !== void 0 ? _a : req.headers.authorization) !== null && _b !== void 0 ? _b : '');
123 // Grab the channelId which should be in the http headers
124 const channelIdHeader = z.string().optional().parse(req.headers.channelid);
125 // Authenticate inbound request
126 const authenticateRequestResult = yield this.botFrameworkAuthentication.authenticateStreamingRequest(authHeader, channelIdHeader);
127 // Creat request handler
128 const requestHandler = new StreamingRequestHandler(authenticateRequestResult, (authenticateRequestResult, activity) => this.processActivity(authenticateRequestResult, activity, logic));
129 // Create server
130 const server = new botframework_streaming_1.WebSocketServer(yield new botframework_streaming_1.NodeWebSocketFactory().createWebSocket(req, socket, head), requestHandler);
131 // Attach server to request handler
132 requestHandler.server = server;
133 // Spin it up
134 yield server.start();
135 });
136 }
137}
138exports.CloudAdapter = CloudAdapter;
139/**
140 * @internal
141 */
142class StreamingRequestHandler extends botframework_streaming_1.RequestHandler {
143 // Note: `processActivity` lambda is to work around the fact that CloudAdapterBase#processActivity
144 // is protected, and we can't get around that by defining classes inside of other classes
145 constructor(authenticateRequestResult, processActivity) {
146 super();
147 this.authenticateRequestResult = authenticateRequestResult;
148 this.processActivity = processActivity;
149 // Attach streaming connector factory to authenticateRequestResult so it's used for outbound calls
150 this.authenticateRequestResult.connectorFactory = new StreamingConnectorFactory(this);
151 }
152 processRequest(request) {
153 var _a, _b;
154 return __awaiter(this, void 0, void 0, function* () {
155 const response = new botframework_streaming_1.StreamingResponse();
156 const end = (statusCode, body) => {
157 response.statusCode = statusCode;
158 if (body) {
159 response.setBody(body);
160 }
161 return response;
162 };
163 if (!request) {
164 return end(botbuilder_core_1.StatusCodes.BAD_REQUEST, 'No request provided.');
165 }
166 if (!request.verb || !request.path) {
167 return end(botbuilder_core_1.StatusCodes.BAD_REQUEST, `Request missing verb and/or path. Verb: ${request.verb}, Path: ${request.path}`);
168 }
169 if (request.verb.toUpperCase() !== streaming_1.POST && request.verb.toUpperCase() !== streaming_1.GET) {
170 return end(botbuilder_core_1.StatusCodes.METHOD_NOT_ALLOWED, `Invalid verb received. Only GET and POST are accepted. Verb: ${request.verb}`);
171 }
172 if (request.path.toLowerCase() === streaming_1.VERSION_PATH) {
173 if (request.verb.toUpperCase() === streaming_1.GET) {
174 return end(botbuilder_core_1.StatusCodes.OK, { UserAgent: botFrameworkAdapter_1.USER_AGENT });
175 }
176 else {
177 return end(botbuilder_core_1.StatusCodes.METHOD_NOT_ALLOWED, `Invalid verb received for path: ${request.path}. Only GET is accepted. Verb: ${request.verb}`);
178 }
179 }
180 const [activityStream, ...attachmentStreams] = request.streams;
181 let activity;
182 try {
183 activity = activityValidator_1.validateAndFixActivity(ActivityT.parse(yield activityStream.readAsJson()));
184 activity.attachments = yield Promise.all(attachmentStreams.map((attachmentStream) => __awaiter(this, void 0, void 0, function* () {
185 const contentType = attachmentStream.contentType;
186 const content = contentType === 'application/json'
187 ? yield attachmentStream.readAsJson()
188 : yield attachmentStream.readAsString();
189 return { contentType, content };
190 })));
191 }
192 catch (err) {
193 return end(botbuilder_core_1.StatusCodes.BAD_REQUEST, `Request body missing or malformed: ${err}`);
194 }
195 try {
196 const invokeResponse = yield this.processActivity(this.authenticateRequestResult, activity);
197 return end((_a = invokeResponse === null || invokeResponse === void 0 ? void 0 : invokeResponse.status) !== null && _a !== void 0 ? _a : botbuilder_core_1.StatusCodes.OK, invokeResponse === null || invokeResponse === void 0 ? void 0 : invokeResponse.body);
198 }
199 catch (err) {
200 return end(botbuilder_core_1.StatusCodes.INTERNAL_SERVER_ERROR, (_b = err.message) !== null && _b !== void 0 ? _b : err);
201 }
202 });
203 }
204}
205/**
206 * @internal
207 */
208class StreamingConnectorFactory {
209 constructor(requestHandler) {
210 this.requestHandler = requestHandler;
211 }
212 create(serviceUrl, _audience) {
213 var _a;
214 return __awaiter(this, void 0, void 0, function* () {
215 (_a = this.serviceUrl) !== null && _a !== void 0 ? _a : (this.serviceUrl = serviceUrl);
216 if (serviceUrl !== this.serviceUrl) {
217 throw new Error('This is a streaming scenario, all connectors from this factory must all be for the same url.');
218 }
219 const httpClient = new StreamingHttpClient(this.requestHandler);
220 return new botframework_connector_1.ConnectorClient(botframework_connector_1.MicrosoftAppCredentials.Empty, { httpClient });
221 });
222 }
223}
224/**
225 * @internal
226 */
227class StreamingHttpClient {
228 constructor(requestHandler) {
229 this.requestHandler = requestHandler;
230 }
231 sendRequest(httpRequest) {
232 var _a;
233 return __awaiter(this, void 0, void 0, function* () {
234 const streamingRequest = this.createStreamingRequest(httpRequest);
235 const receiveResponse = yield ((_a = this.requestHandler.server) === null || _a === void 0 ? void 0 : _a.send(streamingRequest));
236 return this.createHttpResponse(receiveResponse, httpRequest);
237 });
238 }
239 createStreamingRequest(httpRequest) {
240 const verb = httpRequest.method.toString();
241 const path = httpRequest.url.slice(httpRequest.url.indexOf('/v3'));
242 const request = botframework_streaming_1.StreamingRequest.create(verb, path);
243 request.setBody(httpRequest.body);
244 return request;
245 }
246 createHttpResponse(receiveResponse, httpRequest) {
247 var _a, _b, _c;
248 return __awaiter(this, void 0, void 0, function* () {
249 const [bodyAsText] = (_c = (yield Promise.all((_b = (_a = receiveResponse.streams) === null || _a === void 0 ? void 0 : _a.map((stream) => stream.readAsString())) !== null && _b !== void 0 ? _b : []))) !== null && _c !== void 0 ? _c : [];
250 return {
251 bodyAsText,
252 headers: new ms_rest_js_1.HttpHeaders(),
253 request: httpRequest,
254 status: receiveResponse.statusCode,
255 };
256 });
257 }
258}
259//# sourceMappingURL=cloudAdapter.js.map
\No newline at end of file