botbuilder
Version:
Bot Builder is a framework for building rich bots on virtually any platform.
305 lines • 16.5 kB
JavaScript
;
// 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