0.0.3 • Published 5 years ago

@gavant/ember-websockets v0.0.3

Weekly downloads
21
License
-
Repository
github
Last release
5 years ago

gavant-ember-websockets

Basic websockets support for ember applications, using sock.js and the STOMP messaging protocol via the stomp.js client.

Installation

ember install @gavant/ember-websockets

Add the settings below (shown with default values) to your app's config/environment.js to configure the addon. At the very least, you will need to set the websockets.baseURL value to point to an API endpoint URL.

let ENV = {

    //other environment configs...

    websockets: {
        //REQUIRED - the endpoint url used to make the socket connection,
        //typically this is an endpoint on the app's main API host
        baseURL: '',
        //the name of the request header sent in all AJAX requests
        //that uniquely identify the originating browser/client
        clientUUIDHeader: 'x-client-uuid',
        //the global socket channel path for app-wide socket events
        //if there is no global channel, set this to false
        globalChannel: '/topic/global',
        //the name of the property in ember-data models that hold
        //the date the record was last modified. used by ModelSocketEventMixin
        //to determine if received model data should be pushed into the store
        //set to false to disable this behavior
        modelDateField: 'dateModified',
        //display verbose sock.js logs in the dev tools console
        debug: true,
        //user must have an authenticated session to connect
        requiresAuth: true,
        //the intervals at which successive reconnect attempts
        //are made when the socket is disconnected
        reconnectDelaySteps: [1000, 2000, 5000, 10000, 30000, 60000]
    }
}

Usage

Socket Service

The primary service provided by the addon, it is used to make a websocket connection, and subscribe to socket channels.

//Basic usage example
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { get } from '@ember/object';
import { bind } from '@ember/runloop';

export default Route.extend({
    socket: service(),

    setupController() {
        this._super(...arguments);
        //setup a socket channel subscription when entering a route
        const listener = bind(this, 'onSocketEvent');
        get(this, 'socket').subscribe('/topic/some-socket-channel', listener);
    },

    resetController(controller, isExiting) {
        this._super(...arguments);

        if(isExiting) {
            //unsubscribe from the socket channel when leaving the route
            get(this, 'socket').unsubscribe('/topic/some-socket-channel');
        }
    },

    onSocketEvent(message) {
        try {
            //parse the socket event message body
            const body = JSON.parse(message.body);
            console.log(body);
        } catch(err) {

        }
    }
});

The Socket service also mixes in @ember/object/evented, and triggers the following events which can be listened to:

  • connected - a websocket connection is successfully established
  • disconnected - the websocket connection was lost (passes an error object with the event)

ClientIdentity Service

Implements a mechanism for uniquely identifying every app instance/browser client, which can be sent in AJAX request headers to the API. The API can then return this UUID value in socket events, which the addon's ModelSocketEventMixin uses to ignore events that originated from the user that caused them.

The most common places in the app that need to be modified to add the UUID header are:

  • ember-data adapter
    In your application/adapter, define a headers (docs) property that includes the client UUID header:
    export default RESTAdapter.extend({
        clientIdentity: service(),
        headers: readOnly('clientIdentity.uuidHeader'),
    });
  • ember-ajax service
    If your application uses an ajax service extended from the ember-ajax addon to do custom non ember-data AJAX requests (most apps do), you will need to modify the service to include the client UUID header:

    export default AjaxService.extend({
        clientIdentity: service(),
    
        //NOTE: example includes some other common headers,
        //e.g. Content-Type and authorization token from ember-simple-auth
        headers: computed('authorizationHeaders', 'clientIdentity.uuidHeader', function () {
            const headers = assign(
                {'Content-Type': 'application/vnd.api+json'},
                get(this, 'clientIdentity.uuidHeader'),
                get(this, 'authorizationHeaders')
            );
    
            return headers;
        })
    });
  • ember-simple-auth authenticator
    In a custom ember-simple-auth authenticator (e.g. for OAuth 2 authentication), you will need to override the makeRequest() method to be able to add custom headers:

    export default Authenticator.extend({
        clientIdentity: service(),
    
        makeRequest(url, data, headers = {}) {
            assign(
                headers,
                get(this, 'clientIdentity.uuidHeader'),
                {'Content-Type': 'application/x-www-form-urlencoded'}
            );
    
            //rest of method implementation...
        }
    });

ApplicationRouteMixin

This mixin should be applied to the application/route. It sets up the standard boilerplate logic for making a socket connection on app boot/login, and subscribing the the global events channel.

import SocketsApplicationRouteMixin from '@gavant/ember-websockets/mixins/application-route-mixin';

ApplicationControllerMixin

This mixin should be applied to the application/controller. It simply adds a websockets query param, which can be used to disable socket connections when visiting the app with ?websockets=false in the URL.

import SocketsApplicationControllerMixin from '@gavant/ember-websockets/mixins/application-controller-mixin';

GlobalSocketEventsMixin

Subscribes to the global events channel. This is used by the ApplicationRouteMixin and generally should not need to be used directly.

import GlobalSocketEventsMixin from '@gavant/ember-websockets/mixins/socket-events/global';

ModelSocketEventsMixin

Implements common logic used for handling socket events which send changes to ember-data models. It expects socket events to follow a standard REST-like pattern to identify record create/update/delete events. Generally, you will use this in a Route in the following manner:

import ModelSocketEventsMixin from '@gavant/ember-websockets/mixins/socket-events/model';

export default Route.extend(ModelSocketEventsMixin, {
    myChannelName: '/topic/my-channel-name',

    listenForEvents() {
        const channel = get(this, 'myChannelName');
        const listener = bind(this, 'onSocketEvent');
        get(this, 'socket').subscribe(channel, listener);
    },

    stopListeningForEvents() {
        get(this, 'socket').unsubscribe(get(this, 'myChannelName'));
    },

    onSocketEvent(message) {
        this.handleModelEvent(message, 'modelName');
    },

    afterModel() {
        this._super(...arguments);
        this.listenForEvents();
    },

    resetController(controller, isExiting) {
        this._super(...arguments);
        if(isExiting) {
            this.stopListeningForEvents();
        }
    }
});

The ModelSocketEventsMixin also triggers an event, modelReceived on the Socket service when an event is received, so that other parts of your application can execute logic when this happens:

get(this, 'socket').on('modelReceived', this, (method, modelName, modelJson, existingModel) => {
    //do something with the received model event
    console.log(method, modelName, modelJson, existingModel);
    //=>
    //"POST|PUT|DELETE"
    //"someModel"
    //{id: 999, ...}
    //DS.Model instance
});

Contributing

Installation

  • git clone <repository-url>
  • cd gavant-ember-websockets
  • npm install

Linting

  • npm run lint:js
  • npm run lint:js -- --fix

Running tests

  • ember test – Runs the test suite on the current Ember version
  • ember test --server – Runs the test suite in "watch mode"
  • ember try:each – Runs the test suite against multiple Ember versions

Running the dummy application

For more information on using ember-cli, visit https://ember-cli.com/.

License

This project is licensed under the MIT License.