UNPKG

wa-chat-server

Version:

Watson Assistant powered chat server

289 lines (288 loc) 14.3 kB
"use strict"; 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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __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()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConversationService = exports.Types = exports.WAChatServer = void 0; const express_1 = __importDefault(require("express")); const cors_1 = __importDefault(require("cors")); const conversation_1 = require("./api/conversation"); const healthCheck_1 = require("./api/healthCheck"); const auth_1 = require("./api/auth"); const Logger_1 = require("./class/Logger/Logger"); const ServicesRegistrator_1 = require("./class/ServicesRegistrator"); const googleSheets_1 = require("./api/feedback/googleSheets"); const Session_1 = require("./class/Session/Session"); const ConversationService_1 = require("./class/ConversationService/ConversationService"); Object.defineProperty(exports, "ConversationService", { enumerable: true, get: function () { return ConversationService_1.ConversationService; } }); const ConversationAssistants_1 = require("./class/ConversationAssistants"); const Types = __importStar(require("./types")); exports.Types = Types; const processMessage_1 = require("./methods/processMessage"); const swagger_ui_express_1 = __importDefault(require("swagger-ui-express")); const yamljs_1 = __importDefault(require("yamljs")); const verifyJsonRequest_1 = require("./methods/verifyJsonRequest"); const handleParsingError_1 = require("./methods/handleParsingError"); class WAChatServer { constructor(config) { const splitConfig = this.separateAppAndAdapterConfig(config); this.config = Object.assign({ port: 3023, allow_origin: '*' }, splitConfig.appConfig); const watsonAssistants = this.compileConfig(config); this.adapterConfig = splitConfig.adapterConfig; this.logger = config.logger || new Logger_1.Logger(config); this.conversationAdapters = new ConversationAssistants_1.ConversationAssistants(watsonAssistants, this.config, this.logger); this.conversationAdapter = this.conversationAdapters.getMaster(); this.servicesRegistrator = new ServicesRegistrator_1.ServicesRegistrator(this.logger, this.config); this.adapters = new Map(); } separateAppAndAdapterConfig(config) { const appConfig = {}; const adapterConfig = {}; new Map(Object.entries(config)).forEach((val, key) => { const [namespace, name, property] = key.split('__'); if (namespace === 'adapter') { adapterConfig[name] = adapterConfig[name] || {}; adapterConfig[name][property] = val; } else { appConfig[key] = val; } }); return { appConfig, adapterConfig }; } compileConfig(config) { const watsonAssistants = [ { alias: 'MASTER', id: config['master_id'], environmentId: config['master_environment_id'], apikey: config['master_apikey'], url: config['master_url'], timeout: Number.parseInt(config['master_timeout_ms']) || undefined, }, ]; const prefix = 'child'; const suffix = '_alias'; for (const c in config) { if (c.substring(0, prefix.length) == prefix && c.substring(c.length - suffix.length, c.length) == suffix) { const n = c.substring(prefix.length, c.length - suffix.length); watsonAssistants.push({ alias: config[c], id: config[prefix + n + '_id'], environmentId: config[prefix + n + '_environment_id'], apikey: config[prefix + n + '_apikey'] || config['master_apikey'], url: config[prefix + n + '_url'] || config['master_url'], timeout: Number.parseInt(config[prefix + n + '_timeout_ms'] || config['master_timeout_ms']) || undefined, }); } } return watsonAssistants; } initRouter() { var _a; this.router = (0, express_1.default)(); const optionsJson = { limit: '1mb', verify: String((_a = this.config) === null || _a === void 0 ? void 0 : _a.json_raw_body) === 'true' && verifyJsonRequest_1.verifyJsonRequest, }; this.router.use(express_1.default.json(optionsJson)); this.router.use(handleParsingError_1.handleParsingErrors); if (this.config.allow_origin) { this.router.use((0, cors_1.default)({ origin: this.config.allow_origin, })); } this.setRoutes(); } setRoutes() { var _a; this.router.use('/conversation', (0, conversation_1.conversationAPI)(this.logger, this.config, this.servicesRegistrator)(this.conversationAdapters)); this.router.use('/auth', (0, auth_1.authentication)(this.logger, this.config)); // Authentication middleware this.router.use('/health-check', (0, healthCheck_1.healthCheckAPI)(this.logger, this.config)(this.conversationAdapter)); // Feedback middleware this.router.use('/feedback/googleSheets', (0, googleSheets_1.googleSheetsAPI)(this.logger, this.config)); this.adapters.forEach((adapter, name) => { this.logger.info(`Starting adapter '${name}' on endpoint 'HOST:${this.getPort()}/adapters/${name}'`); this.router.use(`/adapters/${name}`, adapter.getRouter()); }); if (String((_a = this.config) === null || _a === void 0 ? void 0 : _a.apidoc_enabled) === 'true') { const pathToSwagger = `${__dirname}/doc/api.swagger.yaml`; let swaggerScheme = []; this.router.use('/apidoc', (req, res, next) => { try { swaggerScheme = yamljs_1.default.load(pathToSwagger); req.swaggerDoc = swaggerScheme; next(); } catch (err) { next(` Error in file located at ${pathToSwagger} \n` + err); } }, swagger_ui_express_1.default.serve, swagger_ui_express_1.default.setup()); } } getPort() { return this.config.VCAP_APP_PORT || this.config.port; } /* * Parameter name is used as: * 1. an adapter name in the configuration (adapter related configuration will have keys * adapter.ADAPTER_NAME.PROPERTY_NAME) * 2. adapter identifier in the routes - the route will be /adapters/ADAPTER_NAME */ addAdapter(name, AdapterClass) { if (this.adapters.has(name)) { throw new Error(`Attempt to overwrite adapter '${name}'`); } this.adapters.set(name, new AdapterClass(this.adapterConfig[name] || {}, this)); } serve() { const port = this.getPort(); this.initRouter(); this.router.listen(port); this.logger.info(`Listening on port: ${port}`); } getRouter() { return this.router; } getServicesRegistrator() { return this.servicesRegistrator; } getLogger() { return this.logger; } getConversationAdapter() { return this.conversationAdapter; } /** * Add specific session storage factory implementation. Can be chosen from the **wa-chat-server** library or custom * implementation can be added which implements interface **Types.ISessionStorageFactory**. * @param storageName can be used to specify storage when starting session using **storageName** value in configOverride * @param storageFactory session storage factory implementation * @param setDefault specify if the storage should be used by default (without explicitly specifying **storageName**) * *DISCLAIMER: first custom factory added will be set as default regardless of this parameter* * @returns */ addSessionStorageFactory(storageName, storageFactory, setDefault) { if (!this.sessionStorageFactories) { this.sessionStorageFactories = new Map(); } if (this.sessionStorageFactories.has(storageName)) { throw new Error(`Factory with storageName "${storageName}" already exists`); } this.sessionStorageFactories.set(storageName, storageFactory); if (setDefault || !this.sessionStorageDefaultFactory) { this.sessionStorageDefaultFactory = storageName; } } // It is not necessary to start session manualy - it will be started automatically in getResponse() // However in some cases we need to have session before getResponse() is called // When session is modified, endSession needs to be performed startSession(sessionId_1) { return __awaiter(this, arguments, void 0, function* (sessionId, configOverride = {}) { let config = { storageName: configOverride.storageName || this.sessionStorageDefaultFactory, singleUseStorage: configOverride.singleUseStorage || false, lifeTimeMS: configOverride.lifeTimeMS, prioritizeRequestContext: typeof configOverride.prioritizeRequestContext === 'boolean' ? configOverride.prioritizeRequestContext : false, allowForeignSessionIds: typeof configOverride.allowForeignSessionIds === 'boolean' ? configOverride.allowForeignSessionIds : true, }; const session = yield new Session_1.Session(sessionId, config, this.sessionStorageFactories).start(); return session; }); } endSession(session) { return __awaiter(this, void 0, void 0, function* () { yield session.save(); }); } getResponse(sessionId_1, watsonRequest_1) { return __awaiter(this, arguments, void 0, function* (sessionId, watsonRequest, // AssistantV1.MessageRequest configOverride = {}) { var _a, _b, _c; const startTimeMS = new Date().getTime(); try { const session = yield this.startSession(sessionId, configOverride); const sessionContext = ((_c = (_b = (_a = session.data) === null || _a === void 0 ? void 0 : _a.assistants) === null || _b === void 0 ? void 0 : _b[session.runningAssistant]) === null || _c === void 0 ? void 0 : _c.context) || {}; const context = session.config.prioritizeRequestContext ? Object.assign(Object.assign({}, sessionContext), (watsonRequest.context || {})) : Object.assign(Object.assign({}, (watsonRequest.context || {})), sessionContext); const watsonReq = Object.assign(Object.assign({}, watsonRequest), { context }); const watsonResponse = yield (0, processMessage_1.processMessage)({ session, watsonAssistants: this.conversationAdapters, preProcessingServices: this.servicesRegistrator.getPreProcessingServices(), postProcessingServices: this.servicesRegistrator.getPostProcessingServices(), conversationServices: this.servicesRegistrator.getConversationServices(), payload: watsonReq, config: this.config, logger: this.logger, }); this.logger.info('WAChatServer - getResponse', { watsonRequest, watsonResponse, durationMS: `${new Date().getTime() - startTimeMS}`, }); return { session: session, watsonResponse: watsonResponse.newPayload, routeNotification: watsonResponse.addons.route, }; } catch (error) { this.logger.error('WAChatServer - getResponse ERROR', { watsonRequest, error, durationMS: `${new Date().getTime() - startTimeMS}`, }); } }); } } exports.WAChatServer = WAChatServer;