transport-ws

WebSocket Transport layer that (currently) works with the HTTP transport to handle websocket connections. Internally, it uses socket.io@1.4.x to handle web sockets. The transport is under active development and changes might occur over time.

The transport was specialized to work with rooms and namespaces in stead of individual sockets. However, this might change over time and additional functionality will be added to address individual socket event sending.

Installation and usage
npm i --save thorin-transport-ws@1.x
'use strict';
// app.js entry file
const thorin = require('thorin');

thorin.addTransport(require('thorin-transport-ws'));  // <-- add this line
thorin.run((err) => {});

# run once to allow the transport to set itself up
node app.js --setup=transport.ws
Default configuration
  • debugtrue Enables or disables event logging
  • adapter.storenullA redis store name to use as an adapter. The default adapter is the in-memory one.
  • adapter.channelthorin:wsThe redis channel to use for publishing and subscribing
  • authorizationnull If specified, override the default authorization source of the http transport
  • transporthttpSince this transport uses the http transport as the base one, this contains the name of the http transport inside your app
  • actionNamedispatchThe name of the event to listen for when clients send events.
  • options{}Additional options to send to socket.io

A web-based client example can be found here. A list of default events a client can send:

  • action sent when the socket wants to trigger an action
  • room.join sent when the socket wants to join a room
  • room.leave sent when the socket wants to leave a room he is in.
  • room.clear sent when the socket wants to leave all the rooms he is in.

A list of server actions that can (and should) be treated on the server side:

  • ws#socket.connect triggered when a new socket connects to the server. This is where authorization logic should be placed
  • ws#room.join triggered when a socket wants to join a room
  • ws#room.leave triggered when a socket wants to leave a room he is in
  • ws#room.clear triggered when a socket wants to leave all the rooms he is in
  • ws#socket.disconnect triggered when an authorized client disconnects.
Extended Thorin.Action
roomName(pattern)
Matches the ws#room event to a room name pattern
  • patternstring | regex the pattern to use when matching the room events
'use strict';
thorin.dispatcher
   .addAction('ws#room.join')
   .roomName('/myroom/:room_id')
   .input({
      room_id: dispatcher.validate('NUMBER')
   })
   .use((intentObj, next) => {
      // Check if user can join room. If not, return next(thorin.error());
      log.info(`User joined room ${intentObj.input('room_id')}`);
      next();
   })
Extended Thorin.Intent
intentObj.socket : WebSocket
The socket property is added to an intent, containing the raw client websocket object.
intentObj.socketConnectionTime()
Returns the number of milliseconds that have passed since the socket connected to the server (uptime tick)
intentObj.socketData(key, val)
Get or ser data to the intent's web socket object. The value is not persisted after disconnects, it is only available during the lifetime of a socket connection to the server. It is usually called the first time an authorization function authorizes a new connection.
  • keystring | object the key that we want to get from the socket data, or the {key or object} we want to use when setting socket data.
  • valanywhen key is a string, the function will act as a setter and use this argument as its value.
The function can only be called on intents that are handled by the ws transport, and not from any other transports.
Exported class ws.Event

When the server is sending events, it uses an instance of the Event class that it exposes. The instance is supposed to contain the payload, target action and rooms that will be sent to. Websocket events also work with target actions and events.

constructor(action, payload)
Construct an event object passing the action and payload directly
  • actionstring the action name that we want to send.
  • payloadobject the payload that we want to send.
eventObj.addRoom(name)
Adds a new target room by its name.
  • namestring the target room name
eventObj.payload(obj)
Manually set the event's payload and override any previous data.
  • objobject the payload that we wish to send.
'use strict';
const wsObj = thorin.transport('ws');
const myEvent = new wsObj.Event('target.action.name', {
   "with": "some",
   "payload": {
      "right": "here"
   }
});
myEvent
   .addRoom('room_1')   // send the action to room 1 and 2
   .addRoom('room_2');
wsObj.send(myEvent);
Transport functionality
sendIntent(intentObj, fn)
Converts the intentObj into a ws.Event class and use it to send out events to clients. This functionality is under development
  • intentObjinstance of thorin.Intent the intent used to extract the data and target client.
  • fnfunctionoptional callback function called once the ack is received from the client.
disableAction(name)
Stops routing intents that come for the specified action, temporary disabling the action processing
  • namestring the action name
enableAction(name)
Re-enables routing to the given action.
  • namestring the action name
app : ThorinSocketIoApp
Expose the internal thorin socket.io app wrapper. This property should only be accessible by components that somehow require access to the internal app. Backward-compatibility is not guaranteed in this case.
Short example
'use strict';
// File: config/app.js
module.exports = {
   "transport.http": {  // use http as base transport
      "port": 3500
   },
   "transport.ws": {    // use websockets attached to the http one
      "authorization": {
         "cookie": "logapi"   // use the Set-Cookie: logapi as authorization source
      },
      "adapter": {   // use the redis instance to work as an adapter.
         "store": "redis",
         "name": "store:ws"
      }
   },
   "plugin.session": {  // use the session plugin
      "cookieName": "logapi",
      "store": "redis"
   },
   "store.redis": {  // additional redis connection configuration
   }
};

'use strict';
// File: app.js
const thorin = require('thorin');

thorin
   .addTransport(require('thorin-transport-http'))
   .addTransport(require('thorin-transport-ws'))
   .addStore(require('thorin-store-redis'))
   .addPlugin(require('thorin-plugin-session'));

thorin.run((err) => {
   log.info('Running');
});

'use strict';
// File: app/actions/stream.js
const dispatcher = thorin.dispatcher;
// Listen to socket connect events
dispatcher
   .addAction('ws#socket.connect')
   .use((intentObj, next) => {
      if (intentObj.session.isNew() || !intentObj.session.account) {
         return next(thorin.error('AUTH.REQUIRED', 'Session expired', 403))
      }
      intentObj.socketData('account', intentObj.session.account);
      log.info('Socket connected');
      next();
   });
// Listen to socket disconnect events
dispatcher
   .addAction('ws#socket.disconnect')
   .use((intentObj, next) => {
      log.info(`User ${intentObj.socketData('account')} disconnected.`);
   });

// Listen if somebody wants to join a room
dispatcher
   .addAction('ws#room.join')
   .use((intentObj, next) => {
      const socketObj = intentObj.socket,
         roomName = intentObj.room;
      log.info(`Socket ${socketObj.id} wants to join room ${roomName}`);
      intentObj.result({
         welcome: 'to the new room!'
      });
      next();
   });

A more in-detail example can be found here

Do you have a question or is something missing?

You can always create a new issue on GitHub or contact one of the core founders by chat.