UNPKG

wingbot

Version:

Enterprise Messaging Bot Conversation Engine

190 lines (159 loc) 5.34 kB
/* * @author David Menger */ 'use strict'; const assert = require('assert'); const WingbotApiConnector = require('./WingbotApiConnector'); // @ts-ignore const packageJson = require('../../package.json'); const headersToAuditMeta = require('../utils/headersToAuditMeta'); const DEFAULT_GROUPS = ['botEditor', 'botAdmin', 'appToken']; const KEYS_URL = 'https://api.wingbot.ai/keys'; const DEFAULT_CACHE = 86400000; // 24 hours /** * @typedef {object} GraphQlResponse * @param {*} [data] * @param {object[]} [errors] */ /** * @typedef {object} RequestParams * @param {string} [snapshot] */ /** @typedef {import('../CallbackAuditLog')} AuditLog */ /** @typedef {import('graphql')} GqlLib */ /** * Experimental chatbot API */ class GraphApi { /** * * @param {object[]} apis - list of connected APIs * @param {object} options - API options * @param {string|Promise<string>} options.token - wingbot token * @param {string} [options.appToken] - public token * @param {string[]} [options.groups] - list of allowed bot groups * @param {boolean} [options.useBundledGql] - uses library bundled graphql definition * @param {AuditLog} [options.auditLog] */ constructor (apis, options) { this._root = { version () { return packageJson.version; } }; const opts = { token: null, groups: DEFAULT_GROUPS, keysUrl: KEYS_URL, cacheKeys: DEFAULT_CACHE, auditLog: { async callback () { // noop }, defaultWid: '0', async log () { // noop } } }; Object.assign(opts, options); apis.forEach((api) => Object.assign(this._root, api)); this._cachedSchema = null; this._originalSchema = null; this._defaultGroups = opts.groups; /** @type {AuditLog} */ // @ts-ignore this.auditLog = opts.auditLog; this._apiConnector = new WingbotApiConnector({ token: opts.token, appToken: opts.appToken, keysUrl: opts.keysUrl, cacheKeys: opts.cacheKeys, useBundledGql: opts.useBundledGql }); this._lib = null; } /** * @returns {GqlLib} */ get _gql () { if (this._lib === null) { // eslint-disable-next-line global-require this._lib = require('graphql'); } return this._lib; } /** * * @param {object} body * @param {object} body.query * @param {object} [body.variables] * @param {string} [body.operationName] * @param {object} headers * @param {string} [headers.Authorization] * @param {string} [headers.authorization] * @param {string} [headers.Origin] * @param {string} [headers.origin] * @param {string} [headers.Referer] * @param {string} [headers.referer] * @param {string} [wingbotToken] * @param {RequestParams} [params] * @returns {Promise<GraphQlResponse>} */ async request (body, headers, wingbotToken = undefined, params = {}) { assert.ok(body && typeof body === 'object', 'GraphQL request should be an object with a request property'); assert.equal(typeof body.query, 'string', 'GraphQL request should contain a query property'); const authHeader = headers.Authorization || headers.authorization; let token = {}; const audit = async (action, payload = {}, important = false, warn = false) => { await this.auditLog.log( { category: 'API', action, payload }, { id: token.id, jwt: token.id && authHeader.replace(/^bearer\s/i, '') }, headersToAuditMeta(headers), this.auditLog.defaultWid, warn ? 'Warn' : 'Info', important ? 'Important' : 'Debug' ); }; try { token = await this._apiConnector.verifyToken(authHeader, wingbotToken); } catch (e) { await audit('authorization failed', { message: e.message, authHeader }, true, true); throw e; } const schema = await this._schema(); const ctx = { token, groups: this._defaultGroups, audit, params }; const response = await this._gql.graphql({ schema, source: body.query, rootValue: this._root, contextValue: ctx, variableValues: body.variables, operationName: body.operationName }); return response; } async _schema () { const loadedSchema = await this._apiConnector.getSchema(); const schemaIsSame = this._originalSchema && this._originalSchema.length === loadedSchema.length; if (!schemaIsSame) { this._originalSchema = loadedSchema; this._cachedSchema = this._gql.buildSchema(loadedSchema); } return this._cachedSchema; } } module.exports = GraphApi;