All files / src/socket utils.ts

97.92% Statements 47/48
100% Branches 21/21
100% Functions 12/12
97.87% Lines 46/47

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 1202x 2x 2x         2x   2x                 2x 9x 9x     3x 5x         1x     9x   9x     2x 13x 7x   7x     7x     6x   6x         3x     6x   6x           2x 14x 14x 14x     14x   14x 5x 5x     14x 14x         2x     12x         2x     10x 10x     10x   10x     10x         14x 9x   9x 9x 5x          
import Debug from 'debug';
import { isEqual } from 'lodash';
import errors from '@feathersjs/errors';
import { HookContext, Application } from '@feathersjs/feathers';
import { CombinedChannel } from '../channels/channel/combined';
import { RealTimeConnection } from '../channels/channel/base';
 
const debug = Debug('@feathersjs/transport-commons');
 
export const paramsPositions: { [key: string]: number } = {
  find: 0,
  get: 1,
  remove: 1,
  create: 1,
  update: 2,
  patch: 2
};
 
export function normalizeError(e: any) {
  const hasToJSON = typeof e.toJSON === 'function';
  const result = hasToJSON ? e.toJSON() : {};
 
  if (!hasToJSON) {
    Object.getOwnPropertyNames(e).forEach(key => {
      result[key] = e[key];
    });
  }
 
  if (process.env.NODE_ENV === 'production') {
    delete result.stack;
  }
 
  delete result.hook;
 
  return result;
}
 
export function getDispatcher(emit: string, socketKey: any) {
  return function(event: string, channel: CombinedChannel, context: HookContext, data: any) {
    debug(`Dispatching '${event}' to ${channel.length} connections`);
 
    channel.connections.forEach(connection => {
      // The reference between connection and socket
      // is set in `app.setup`
      const socket = connection[socketKey];
 
      if (socket) {
        const eventName = `${context.path || ''} ${event}`.trim();
 
        let result = channel.dataFor(connection) || context.dispatch || context.result;
 
        // If we are getting events from an array but try to dispatch individual data
        // try to get the individual item to dispatch from the correct index.
        if (!Array.isArray(data) && Array.isArray(context.result) && Array.isArray(result)) {
          result = context.result.find(resultData => isEqual(resultData, data));
        }
 
        debug(`Dispatching '${eventName}' to Socket ${socket.id} with`, result);
 
        socket[emit](eventName, result);
      }
    });
  };
}
 
export function runMethod(app: Application, connection: RealTimeConnection, path: string, method: string, args: any[]) {
  const trace = `method '${method}' on service '${path}'`;
  const methodArgs = args.slice(0);
  const callback = typeof methodArgs[methodArgs.length - 1] === 'function'
    ? methodArgs.pop() : function () {};
 
  debug(`Running ${trace}`, connection, args);
 
  const handleError = (error: any) => {
    debug(`Error in ${trace}`, error);
    callback(normalizeError(error));
  };
  // A wrapper function that runs the method and returns a promise
  const _run = () => {
    const lookup = app.lookup(path);
 
    // No valid service was found, return a 404
    // just like a REST route would
    if (lookup === null) {
      return Promise.reject(new errors.NotFound(`Service '${path}' not found`));
    }
 
    const { service, params: route = {} } = lookup;
 
    // Only service methods are allowed
    // @ts-ignore
    if (paramsPositions[method] === undefined || typeof service[method] !== 'function') {
      return Promise.reject(new errors.MethodNotAllowed(`Method '${method}' not allowed on service '${path}'`));
    }
 
    const position = paramsPositions[method];
    const query = methodArgs[position] || {};
    // `params` have to be re-mapped to the query
    // and added with the route
    const params = Object.assign({ query, route, connection }, connection);
 
    methodArgs[position] = params;
 
    // @ts-ignore
    return service[method](...methodArgs, true);
  };
 
  try {
    // Run and map to the callback that is being called for Socket calls
    _run().then((hook: HookContext) => {
      const result = hook.dispatch || hook.result;
 
      debug(`Returned successfully ${trace}`, result);
      callback(null, result);
    }).catch((hook: HookContext) => handleError(hook.type === 'error' ? hook.error : hook));
  } catch (error) {
    handleError(error);
  }
}