UNPKG

botbuilder

Version:

Bot Builder is a framework for building rich bots on virtually any platform.

305 lines • 16.5 kB
"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CloudAdapter = void 0; const z = __importStar(require("zod")); const botbuilder_core_1 = require("botbuilder-core"); const streaming_1 = require("./streaming"); const azureCoreHttpCompat_1 = require("botbuilder-stdlib/lib/azureCoreHttpCompat"); const zod_1 = require("./zod"); const interfaces_1 = require("./interfaces"); const botFrameworkAdapter_1 = require("./botFrameworkAdapter"); const botbuilder_stdlib_1 = require("botbuilder-stdlib"); const activityValidator_1 = require("./activityValidator"); const botframework_connector_1 = require("botframework-connector"); const botframework_streaming_1 = require("botframework-streaming"); // Note: this is _okay_ because we pass the result through `validateAndFixActivity`. Should not be used otherwise. const ActivityT = z.custom((val) => z.record(z.unknown()).safeParse(val).success, { message: 'Activity' }); /** * An adapter that implements the Bot Framework Protocol and can be hosted in different cloud environmens both public and private. */ class CloudAdapter extends botbuilder_core_1.CloudAdapterBase { /** * Initializes a new instance of the [CloudAdapter](xref:botbuilder:CloudAdapter) class. * * @param botFrameworkAuthentication Optional [BotFrameworkAuthentication](xref:botframework-connector.BotFrameworkAuthentication) instance */ constructor(botFrameworkAuthentication = botframework_connector_1.BotFrameworkAuthenticationFactory.create()) { super(botFrameworkAuthentication); } /** * @internal */ process(req, resOrSocket, logicOrHead, maybeLogic) { var _a, _b, _c, _d; return __awaiter(this, void 0, void 0, function* () { // Early return with web socket handler if function invocation matches that signature if (maybeLogic) { const socket = zod_1.INodeSocketT.parse(resOrSocket); const head = zod_1.INodeBufferT.parse(logicOrHead); const logic = zod_1.LogicT.parse(maybeLogic); return this.connect(req, socket, head, logic); } const res = interfaces_1.ResponseT.parse(resOrSocket); const logic = zod_1.LogicT.parse(logicOrHead); const end = (status, body) => { res.status(status); if (body) { res.send(body); } res.end(); }; // Only POST requests from here on out if (req.method !== 'POST') { return end(botbuilder_core_1.StatusCodes.METHOD_NOT_ALLOWED); } // Ensure we have a parsed request body already. We rely on express/restify middleware to parse // request body and azure functions, which does it for us before invoking our code. Warn the user // to update their code and return an error. if (!z.record(z.unknown()).safeParse(req.body).success) { return end(botbuilder_core_1.StatusCodes.BAD_REQUEST, '`req.body` not an object, make sure you are using middleware to parse incoming requests.'); } const activity = (0, activityValidator_1.validateAndFixActivity)(ActivityT.parse(req.body)); if (!activity.type) { console.warn('BadRequest: Missing activity or activity type.'); return end(botbuilder_core_1.StatusCodes.BAD_REQUEST); } const authHeader = z.string().parse((_b = (_a = req.headers.Authorization) !== null && _a !== void 0 ? _a : req.headers.authorization) !== null && _b !== void 0 ? _b : ''); try { const invokeResponse = yield this.processActivity(authHeader, activity, logic); 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); } catch (err) { console.error(err); 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); } }); } /** * Asynchronously process an activity running the provided logic function. * * @param authorization The authorization header in the format: "Bearer [longString]" or the AuthenticateRequestResult for this turn. * @param activity The activity to process. * @param logic The logic function to apply. * @returns a promise representing the asynchronous operation. */ processActivityDirect(authorization, activity, logic) { return __awaiter(this, void 0, void 0, function* () { try { yield this.processActivity(authorization, activity, logic); } catch (err) { throw new Error(`CloudAdapter.processActivityDirect(): ERROR\n ${err.stack}`); } }); } /** * Used to connect the adapter to a named pipe. * * @param pipeName Pipe name to connect to (note: yields two named pipe servers by appending ".incoming" and ".outgoing" to this name) * @param logic The logic function to call for resulting bot turns. * @param appId The Bot application ID * @param audience The audience to use for outbound communication. The will vary by cloud environment. * @param callerId Optional, the caller ID * @param retryCount Optional, the number of times to retry a failed connection (defaults to 7) */ connectNamedPipe(pipeName, logic, appId, audience, callerId, retryCount = 7) { return __awaiter(this, void 0, void 0, function* () { z.object({ pipeName: z.string(), logic: zod_1.LogicT, appId: z.string(), audience: z.string(), callerId: z.string().optional(), }).parse({ pipeName, logic, appId, audience, callerId }); // The named pipe is local and so there is no network authentication to perform: so we can create the result here. const authenticateRequestResult = { audience, callerId, claimsIdentity: appId ? this.createClaimsIdentity(appId) : new botframework_connector_1.ClaimsIdentity([]), }; // Creat request handler const requestHandler = new StreamingRequestHandler(authenticateRequestResult, (authenticateRequestResult, activity) => this.processActivity(authenticateRequestResult, activity, logic)); // Create server const server = new botframework_streaming_1.NamedPipeServer(pipeName, requestHandler); // Attach server to request handler for outbound requests requestHandler.server = server; // Spin it up yield (0, botbuilder_stdlib_1.retry)(() => server.start(), retryCount); }); } connect(req, socket, head, logic) { var _a, _b; return __awaiter(this, void 0, void 0, function* () { // Grab the auth header from the inbound http request const authHeader = z.string().parse((_b = (_a = req.headers.Authorization) !== null && _a !== void 0 ? _a : req.headers.authorization) !== null && _b !== void 0 ? _b : ''); // Grab the channelId which should be in the http headers const channelIdHeader = z.string().optional().parse(req.headers.channelid); // Authenticate inbound request const authenticateRequestResult = yield this.botFrameworkAuthentication.authenticateStreamingRequest(authHeader, channelIdHeader); // Creat request handler const requestHandler = new StreamingRequestHandler(authenticateRequestResult, (authenticateRequestResult, activity) => this.processActivity(authenticateRequestResult, activity, logic)); // Create server const server = new botframework_streaming_1.WebSocketServer(yield new botframework_streaming_1.NodeWebSocketFactory().createWebSocket(req, socket, head), requestHandler); // Attach server to request handler requestHandler.server = server; // Spin it up yield server.start(); }); } } exports.CloudAdapter = CloudAdapter; /** * @internal */ class StreamingRequestHandler extends botframework_streaming_1.RequestHandler { // Note: `processActivity` lambda is to work around the fact that CloudAdapterBase#processActivity // is protected, and we can't get around that by defining classes inside of other classes constructor(authenticateRequestResult, processActivity) { super(); this.authenticateRequestResult = authenticateRequestResult; this.processActivity = processActivity; // Attach streaming connector factory to authenticateRequestResult so it's used for outbound calls this.authenticateRequestResult.connectorFactory = new StreamingConnectorFactory(this); } processRequest(request) { var _a, _b; return __awaiter(this, void 0, void 0, function* () { const response = new botframework_streaming_1.StreamingResponse(); const end = (statusCode, body) => { response.statusCode = statusCode; if (body) { response.setBody(body); } return response; }; if (!request) { return end(botbuilder_core_1.StatusCodes.BAD_REQUEST, 'No request provided.'); } if (!request.verb || !request.path) { return end(botbuilder_core_1.StatusCodes.BAD_REQUEST, `Request missing verb and/or path. Verb: ${request.verb}, Path: ${request.path}`); } if (request.verb.toUpperCase() !== streaming_1.POST && request.verb.toUpperCase() !== streaming_1.GET) { return end(botbuilder_core_1.StatusCodes.METHOD_NOT_ALLOWED, `Invalid verb received. Only GET and POST are accepted. Verb: ${request.verb}`); } if (request.path.toLowerCase() === streaming_1.VERSION_PATH) { if (request.verb.toUpperCase() === streaming_1.GET) { return end(botbuilder_core_1.StatusCodes.OK, { UserAgent: botFrameworkAdapter_1.USER_AGENT }); } else { return end(botbuilder_core_1.StatusCodes.METHOD_NOT_ALLOWED, `Invalid verb received for path: ${request.path}. Only GET is accepted. Verb: ${request.verb}`); } } const [activityStream, ...attachmentStreams] = request.streams; let activity; try { activity = (0, activityValidator_1.validateAndFixActivity)(ActivityT.parse(yield activityStream.readAsJson())); activity.attachments = yield Promise.all(attachmentStreams.map((attachmentStream) => __awaiter(this, void 0, void 0, function* () { const contentType = attachmentStream.contentType; const content = contentType === 'application/json' ? yield attachmentStream.readAsJson() : yield attachmentStream.readAsString(); return { contentType, content }; }))); } catch (err) { return end(botbuilder_core_1.StatusCodes.BAD_REQUEST, `Request body missing or malformed: ${err}`); } try { const invokeResponse = yield this.processActivity(this.authenticateRequestResult, activity); 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); } catch (err) { return end(botbuilder_core_1.StatusCodes.INTERNAL_SERVER_ERROR, (_b = err.message) !== null && _b !== void 0 ? _b : err); } }); } } /** * @internal */ class StreamingConnectorFactory { constructor(requestHandler) { this.requestHandler = requestHandler; } create(serviceUrl, _audience) { var _a; return __awaiter(this, void 0, void 0, function* () { (_a = this.serviceUrl) !== null && _a !== void 0 ? _a : (this.serviceUrl = serviceUrl); if (serviceUrl !== this.serviceUrl) { throw new Error('This is a streaming scenario, all connectors from this factory must all be for the same url.'); } const httpClient = new StreamingHttpClient(this.requestHandler); return new botframework_connector_1.ConnectorClient(botframework_connector_1.MicrosoftAppCredentials.Empty, { httpClient }); }); } } /** * @internal */ class StreamingHttpClient { constructor(requestHandler) { this.requestHandler = requestHandler; } sendRequest(httpRequest) { var _a; return __awaiter(this, void 0, void 0, function* () { const streamingRequest = this.createStreamingRequest(httpRequest); const receiveResponse = yield ((_a = this.requestHandler.server) === null || _a === void 0 ? void 0 : _a.send(streamingRequest)); return this.createHttpResponse(receiveResponse, httpRequest); }); } createStreamingRequest(httpRequest) { const verb = httpRequest.method.toString(); const path = httpRequest.url.slice(httpRequest.url.indexOf('/v3')); const request = botframework_streaming_1.StreamingRequest.create(verb, path); request.setBody(httpRequest.body); return request; } createHttpResponse(receiveResponse, httpRequest) { var _a, _b, _c; return __awaiter(this, void 0, void 0, function* () { 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 : []; return { bodyAsText, headers: new azureCoreHttpCompat_1.HttpHeaders(), request: httpRequest, status: receiveResponse.statusCode, }; }); } } //# sourceMappingURL=cloudAdapter.js.map