Source

lib/use.injection.js

const tokenValidator = require('twilio-flex-token-validator').validator;
const _ = require('lodash');

const { InternalServerError } = require('./errors/internal-server.error');
const { UnauthorizedError } = require('./errors/unauthorized.error');

/**
 * @async
 * @typedef { function } InjectedFn
 *
 * @param { object } context Handler context
 * @param { object } event Handler event
 * @param { function } callback Handler callback function
 *
 * @return { void }
 */

/**
 * An object with the declared functions to be injected into your custom regular function.
 *
 * @typedef { object } Providers
 */

/**
 * @typedef { object } CustomFnThis
 *
 * @property { object } request The request values as headers
 * @property { object } cookies The request cookies
 * @property { object } env All of the ENV vars available through context can be founded here
 * @property { Providers } providers The providers attached on useInjection options will be available here
 */

/**
 * Your regular function that will receive all of the providers and process the Twilio handler function.
 *
 * @async
 * @typedef { function } CustomFn
 *
 * @this CustomFnThis
 *
 * @param { * } event Any aditional argument sent to the Twilio handler will be available here.
 *
 * @return { (Response|TwiMLResponse) }
 */

/**
 * The useInjection method takes two parameters. The first to apply as a handler and the last is an object of configuration options.
 * It reduces the need to apply frequent try-catches and improving context management, making it no longer necessary to return the callback() method in all functions.
 *
 * @function
 *
 * @param { CustomFn } fn Your custom function must be defined here as the first `useInjection` parameter
 * @param { object } [params] The `useInjection` additional options as object
 * @param { Providers } [params.providers] The providers that should be injected into your CustomFn
 * @param { boolean } [params.validateToken] A boolean to request for Token validation from Twilio Flex
 *
 * @example
 *
 * const { useInjection, Response } = require('twilio-functions-utils');
 * const { create } = require(Runtime.getFunctions()['create'].path)
 *
 * async function createAction(event) {
 *  const { cookies, request, env } = this
 *  const providerResult = await this.providers.create(event)
 *
 *  if (providerResult.isError) {
 *    return new BadRequestError(providerResult.error);
 *  }
 *
 *  return new Response(providerResult.data, 201);
 * }
 *
 * exports.handler = useInjection(createAction, {
 *  providers: {
 *    create,
 *  },
 *  validateToken: true
 * });
 *
 * @return { InjectedFn }
 */
const useInjection = (fn, params) => async function (...args) {
  const [context, event, callback] = args;
  const { getTwilioClient, ...env } = context;

  const providers = _.isUndefined(params?.providers)
    || !_.isPlainObject(params?.providers)
    ? {} : params.providers;

  const validateToken = _.isUndefined(params?.validateToken)
    || _.isNull(params?.validateToken)
    ? false : params.validateToken;

  const client = getTwilioClient();

  const providerThat = {
    client,
    env,
  };

  const {
    request, cookies, Token, ...values
  } = event;

  const providerNames = Object.keys(providers);

  const that = {
    request,
    cookies,
    env,
    providers: providerNames.reduce((p, c) => {
      Reflect.defineProperty(
        p, c, {
          value: providers[c].bind(providerThat),
          enumerable: true,
        },
      );
      return p;
    }, {}),
  };

  try {
    if (validateToken) {
      const validation = await tokenValidator(
        Token, env.ACCOUNT_SID, env.AUTH_TOKEN,
      );

      if (!validation.valid) {
        return callback(undefined, new UnauthorizedError(validation.message));
      }
    }

    return callback(undefined, await fn.apply(that, [values]));
  } catch (err) {
    if (typeof err === 'string') {
      return callback(undefined, new UnauthorizedError(err));
    }

    return callback(undefined, new InternalServerError(err.message));
  }
};

module.exports = { useInjection };