ember-websockets

EmberJS WebSockets addon for Ember-CLI apps.

Usage no npm install needed!

<script type="module">
  import emberWebsockets from 'https://cdn.skypack.dev/ember-websockets';
</script>

README

Ember Websockets

GitHub Actions CI

Installation

ember install ember-websockets

Simple example of using it in your app

import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';

export default class MyController extends Controller {

  /*
   * 1. Inject the websockets service
   */
  @service('websockets') websockets;
  socketRef = null,

  constructor() {
    super(...arguments);

    /*
      2. The next step you need to do is to create your actual websocket. Calling socketFor
      will retrieve a cached websocket if one exists or in this case it
      will create a new one for us.
    */
    const socket = this.websockets.socketFor('ws://localhost:7000/');

    /*
      3. The next step is to define your event handlers. All event handlers
      are added via the `on` method and take 3 arguments: event name, callback
      function, and the context in which to invoke the callback. All 3 arguments
      are required.
    */
    socket.on('open', this.myOpenHandler, this);
    socket.on('message', this.myMessageHandler, this);
    socket.on('close', this.myCloseHandler, this);

    this.set('socketRef', socket);
  }

  myOpenHandler(event) {
    console.log(`On open event has been called: ${event}`);
  }

  myMessageHandler(event) {
    console.log(`Message: ${event.data}`);
  }

  myCloseHandler(event) {
    console.log(`On close event has been called: ${event}`);
  }

  @action
  sendButtonPressed() {
    this.socketRef.send('Hello Websocket World');
  }
}

Sending messages to the server

const socket = this.socketService.socketFor('ws://localhost:7000/');
socket.send({username: 'foo', didSomeAction: 'pressedAButton'}, true);

// the above line is the same as this:
socket.send(JSON.stringify({username: 'foo', didSomeAction: 'pressedAButton'}));

The send method takes 2 arguments. A message which is passed into the native websockets send method and an optional stringify boolean. This boolean, if set to true, will do a JSON.stringify to the message before passing it to the websocket send method. If you are sending strings it is recommended to pass true.

Reconnecting

import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { later } from '@ember/runloop';

export default class MyController extends Controller {
  @service('websockets') socketService;

  constructor() {
    super(...arguments);

    const socket = this.socketService.socketFor('ws://localhost:7000/');
    socket.on('close', this.myOnClose, this);
  }

  myOnClose() {
    const socket = this.socketService.socketFor('ws://localhost:7000/');
    later(this, () => {
      /*
        This will remove the old socket and try and connect to a new one on the same url.
        NOTE: that this does not need to be in a Ember.run.later this is just an example on
        how to reconnect every second.
      */
      socket.reconnect();
    }, 1000);
  }
}

Closing the connection

import Component from '@ember/component';
import { inject as service } from '@ember/service';

export default class MyComponent extends Component {
  @service('websockets') socketService;

  /*
    To close a websocket connection simply call the closeSocketFor method. NOTE: it is good
    practice to close any connections after you are no longer in need of it. A good
    place for this clean up is in the willDestroyElement method of the object.
  */
  willDestroyElement() {
    this.socketService.closeSocketFor('ws://localhost:7000/');
  }
}

Multiple Websockets

import Component from '@ember/component';
import { inject as service } from '@ember/service';

export default class MyComponent extends Component {
  @service('websockets') socketService;

  didInsertElement() {
    const socketOne = this.socketService.socketFor('ws://localhost:7000/');
    const socketTwo = this.socketService.socketFor('ws://localhost:7001/');

    socketOne.on('open', this.myOpenFirst, this);
    socketTwo.on('open', this.myOpenSeconds, this);
  }

  myOpenFirst(event) {
    console.log('Hello from socket one');
  }

  myOpenSecond(event) {
    console.log('Hello from socket two');
  }

  willDestroyElement() {
    const socketOne = this.socketService.socketFor('ws://localhost:7000/');
    const socketTwo = this.socketService.socketFor('ws://localhost:7001/');
    socketOne.off('open', this.myOpenFirst);
    socketTwo.off('open', this.myOpenSecond);
  }
}

Multiple Event Handlers

import Component from '@ember/component';
import { inject as service } from '@ember/service';

export default class MyComponent extends Component {
  socketService: service('websockets'),

  didInsertElement() {
    const socket = this.socketService.socketFor('ws://localhost:7000/');

    socket.on('open', this.myOpenFirst, this);
    socket.on('open', this.myOpenSecond, this);
  }

  myOpenFirst() {
    console.log('This will be called');
  }

  myOpenSecond() {
    console.log('This will also be called');
  }

  willDestroyElement() {
    const socket = this.socketService.socketFor('ws://localhost:7000/');
    socket.off('open', this.myOpenFirst);
    socket.off('open', this.myOpenSecond);
  }
}

Socket.IO Support

First set socketIO to be true in your config/environment.js file:

var ENV = {
  'ember-websockets': {
    socketIO: true
  }
};
import Component from '@ember/component';
import { inject as service } from '@ember/service';

export default class MyComponent extends Component {

  /*
    1. Inject the socketio service
  */
  @service('socket-io') socketIOService;

  /*
    Important note: The namespace is an implementation detail of the Socket.IO protocol...
    http://socket.io/docs/rooms-and-namespaces/#custom-namespaces
  */
  namespace = 'myCustomNamespace',

  didInsertElement() {
    /*
      2. The next step you need to do is to create your actual socketIO.
    */
    const socket = this.socketIOService.socketFor(`http://localhost:7000/${this.namespace}`);

    /*
    * 3. Define any event handlers
    */
    socket.on('connect', this.onConnect, this);
    socket.on('message', this.onMessage, this);

    /*
      4. It is also possible to set event handlers on specific events
    */
    socket.on('myCustomEvent', () => { socket.emit('anotherCustomEvent', 'some data'); });
  }

  onConnect() {
    const socket = this.socketIOService.socketFor(`http://localhost:7000/${this.namespace}`);

    /*
      There are 2 ways to send messages to the server: send and emit
    */
    socket.send('Hello World');
    socket.emit('Hello server');
  }

  onMessage(data) {
    // This is executed within the ember run loop
  }

  myCustomEvent(data) {
    const socket = this.socketIOService.socketFor(`http://localhost:7000/${this.namespace}`);
    socket.emit('anotherCustomEvent', 'some data');
  }

  willDestroyElement() {
    const socket = this.socketIOService.socketFor(`http://localhost:7000/${this.namespace}`);
    socket.off('connect', this.onConnect);
    socket.off('message', this.onMessage);
    socket.off('myCustomEvent', this.myCustomEvent);
  }
}

Please visit: socket.io docs for more details on ember-websocket + socket.io

Detailed explanations of the APIs

SocketFor

Example:

const socket = this.socketService.socketFor('ws://localhost:7000/', ['myOptionalProtocol']);

socketFor takes two arguments: a url, a protocol array (optional), and returns a socket instance from its cache or a new websocket connection if one was not found.

To use a custom namespace, append the namespace to the end of the url.

const socket = this.socketService.socketFor(`ws://localhost:7000/${namespace}`);

On

Example:

const socket = this.socketService.socketFor('ws://localhost:7000/');

socket.on('open', this.myOtherOpenFunction);

on takes 3 arguments: event type, callback function, and context. Event type can be one of the following: 'open', 'message', 'close', and 'error'. Callback function will be invoked when one of the event types occurs.

Off

Example:

const socket = this.socketService.socketFor('ws://localhost:7000/');

let openFunctionReference = this.myOpenFunction.bind(this);

socket.on('open', openFunctionReference);
socket.off('open', openFunctionReference);

off takes 2 arguments: event type, callback function. Event type can be one of the following: 'open', 'message', 'close', and 'error'. The callback will be removed from the event pool and will no longer be invoked.

CloseSocketFor

Example:

this.socketService.closeSocketFor('ws://localhost:7000/');

closeSocketFor takes a single argument, a url, and closes the websocket connection. It will also remove it from the cache. In normal cases you would not have to call this method.

Reconnect

Example:

socket.on('close', event => {
  socket.reconnect();
});

reconnect takes no arguments. It will attempt to create a new websocket connect using the previous url. If the connect is not successful the close event will be triggered.

Live Example

  • git clone git@github.com:thoov/ember-websockets.git
  • cd ember-websockets
  • yarn
  • ember s
  • Then visit http://localhost:4200/sockets/example to view a very simple example.

The source code for the live example lives in ember-websockets/tests/dummy

Running tests

  • git clone git@github.com:thoov/ember-websockets.git
  • cd ember-websockets
  • yarn
  • ember t
  • or ember s then visit http://localhost:4200/tests to view the tests.

NOTE: To get the test to run in PhantomJS I created a mocking library found here: mocking library Note that it is still a work in progress.

Feedback or issues

If you have any feedback, encounter any bugs, or just have a question, please feel free to create a github issue or send me a tweet at @thoov.

FAQ

Recommended backend library/framework

License

This addon falls under the MIT license