305 lines
No EOL
16 KiB
JavaScript
305 lines
No EOL
16 KiB
JavaScript
"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 core_http_1 = require("@azure/core-http");
|
|
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 core_http_1.HttpHeaders(),
|
|
request: httpRequest,
|
|
status: receiveResponse.statusCode,
|
|
};
|
|
});
|
|
}
|
|
}
|
|
//# sourceMappingURL=cloudAdapter.js.map
|