thoov/ember-websockets

View on GitHub
addon/helpers/websocket-proxy.js

Summary

Maintainability
A
0 mins
Test Coverage
import { run } from '@ember/runloop';
import { assert } from '@ember/debug';
import ObjectProxy from '@ember/object/proxy';

const events = ['close', 'error', 'message', 'open'];
const { filter, indexOf, forEach } = Array.prototype;

export default ObjectProxy.extend({
  /*
   * {
   *    url: 'String'
   *    type: 'String' (such as 'open', 'message', 'close', and 'error')
   *    callback: The function to envoke
   *    context: The context of the function
   * }
   */
  listeners: null,

  init() {
    this._super(...arguments);
    this.listeners = [];

    this.setupInternalListeners();
  },

  /*
   * Adds a callback function into the listeners array which will
   * be invoked later whenever a given `type` event happens.
   *
   * type: must be either 'open', 'message', 'close', 'error'
   */
  on(type, callback, context) {
    assert(
      `${type} is not a recognized event name. Please use on of the following: ${events.join(
        ', '
      )}`,
      indexOf.call(events, type) !== -1
    );
    assert(
      'The second argument must be a function.',
      typeof callback === 'function'
    );

    this.listeners.push({ url: this.socket.url, type, callback, context });
  },

  /*
   * Removes a callback function from the listeners array. This callback
   * will not longer be invoked when the given `type` event happens.
   */
  off(type, callback) {
    this.listeners = filter.call(
      this.listeners,
      (listeners) =>
        !(listeners.callback === callback && listeners.type === type)
    );
  },

  /*
   * Message is the message which will be passed into the native websockets send method
   * and shouldStringify is a boolean which determines if we should call JSON.stringify on
   * the message.
   */
  send(message, shouldStringify = false) {
    if (shouldStringify && JSON && JSON.stringify) {
      message = JSON.stringify(message);
    }

    assert(
      'Cannot send message to the websocket while it is not open.',
      this.readyState() === WebSocket.OPEN
    );

    this.socket.send(message);
  },

  close() {
    this.socket.close();
  },

  reconnect() {
    this.set('socket', new WebSocket(this.socket.url, this.protocols));
    this.setupInternalListeners();
  },

  setupInternalListeners() {
    forEach.call(events, (eventName) => {
      this.socket[`on${eventName}`] = (event) => {
        run(() => {
          var activeListeners = filter.call(this.listeners, (listener) => {
            return (
              listener.url === event.currentTarget.url &&
              listener.type === eventName
            );
          });

          // TODO: filter active listeners for contexts that are not destroyed
          forEach.call(activeListeners, (item) => {
            if (item.context) {
              item.callback.call(item.context, event);
            } else {
              item.callback(event);
            }
          });
        });
      };
    });
  },

  /*
   * A helper method to get access to the readyState of the websocket.
   */
  readyState() {
    return this.socket.readyState;
  },
});