@rinq/websocket

0.2.1 • Public • Published

Rinq WebSocket

Rinq in the browser.

Latest Version Build Status Code Coverage

Usage

Core module usage

The core module centers around single-use connections. It does not implement any reconnection logic, or application-level state. First a connection must be made, from which a session is created in order to communicate:

var rinq = require('@rinq/websocket')

var connection = rinq.connection('ws://example.org/')
var session = rinq.session()

connection.on('open', function () {
  session.execute('namespace', 'command', 'payload')
})

With this approach, once the connection is closed, or the session is destroyed, these objects must be discarded, and new ones created. It is up to the user to manage any application state that depends upon access to a Rinq connection or session.

Managed module usage

The managed module implements some higher-level constructs that simplify the management of transient communication issues, such as network dropouts. First a connection manager is created, then a session manager, and finally contexts, which provide similar functionality to a session:

var rinq = require('@rinq/websocket/managed')

var connectionManager = rinq.connectionManager({url: 'ws://example.org/'})
var sessionManager = connectionManager.sessionManager()
var context = sessionManager.context()

context.on('ready', function () {
  context.execute('namespace', 'command', 'payload')
})

context.start()

With this approach, transient communication issues are managed by Rinq. This means it is safe to store references to the connection manager, session manager, and context, across the lifetime of the application.

Additionally, contexts provide some basic application-level state management, as they can specify an initialization function that must execute before the context is "ready":

var context = sessionManager.context({
  initialize: function (done, session) {
    // listen for notifications
    session.on('notification', onNotification)
    session.once('destroy', function () {
      session.removeListener('notification', onNotification)
    })

    // perform an authentication request
    session.call('auth.1', 'token', 'U53R-70K3N', 10000, done)
  }
})

context.on('ready', function () {
  context.execute('namespace', 'command', 'payload')
})

context.start()

API

Core module

require('@rinq/websocket')

The core module contains only the essential functionality for communicating via the Rinq protocol:


Connection connection (url[, options])

Creates a new Rinq connection to url.

The options are represented as a generic object, and may specify:

Option Description Type Example Default
CBOR A reference to the @rinq/cbor module. object require('@rinq/cbor') (none)
log A set of logging options. object {debug: true} (none)

Specifying CBOR is recommended, as it enables messages to be serialized with CBOR rather than JSON:

var c = connection('ws://example.org/', {CBOR: CBOR})

boolean isFailure (error)

Returns true if error is a Rinq failure. This function can be used to assist in handling errors returned by Rinq calls:

session.call('namespace', 'command', 'payload', 3000, function (error, response) {
  if (error) {
    if (isFailure(error)) {
      // handle failures
    } else {
      // handle other errors
    }
  }

  // proceed as normal
})

boolean isFailureType (type, error)

Returns true if error is a Rinq failure of type type. This function can be used to assist in handling errors returned by Rinq calls:

session.call('namespace', 'command', 'payload', 3000, function (error, response) {
  if (error) {
    if (isFailureType('type-a', error)) {
      // handle type a failures
    } else if (isFailureType('type-b', error)) {
      // handle type b failures
    } else {
      // handle other errors
    }
  }

  // proceed as normal
})

Connection

Represents a Rinq connection, and allows the creation of sessions for communication:


Session connection.session ([options])

Creates a new session.

The options are represented as a generic object, and may specify:

Option Description Type Example Default
log A set of logging options. object {debug: true} (none)
connection.session({log: {prefix: '[session-a] '}})

void connection.close ()

Closes the connection.

Once a connection is closed, it cannot be re-opened.


connection.on( 'open' , function () {})

This event is emitted once the connection is open and ready to communicate.

The handler for this event accepts no arguments.


connection.on( 'close' , function ([error]) {})

This event is emitted once the connection is closed.

The handler for this event accepts a single, optional error argument. If the connection was closed normally, via close(), error will be undefined.

Session

Represents a session, and allows for multiple channels of communication over a single Rinq connection:


void session.execute (namespace, command, payload)

Sends a Rinq command, for which no response is expected.

Both namespace and command are strings used to dispatch the command to the appropriate server. The payload can be any JSON serializable value.


void session.call (namespace, command, payload[, timeout][, function (error, response) {}])

Sends a Rinq command, and handles the response.

Both namespace and command are strings used to dispatch the command to the appropriate server. The payload can be any JSON serializable value.

The timeout value is used in the Rinq protocol to determine when an unprocessed command can be discarded due to its age. In addition, if a handler function is supplied, a client-side timeout will cause the handler function to be called with a timeout error as its error argument.

The timeout value is specified as an integer. A positive timeout value indicates the number of milliseconds before timeout occurs. A timeout of 0 indicates that the server-side default timeout should be used. A negative timeout value indicates that no timeout should be used, but this is only allowed when no handler function is specified.

The last argument is an optional handler that accepts an error as the first argument, and the response as the second. If error is non-empty, the response value should be ignored.

If no handler function is specified, the response to the call will instead be emitted from the session as a response event.

Errors supplied to the handler, or emitted via a response event will typically be Rinq failures, which are sent by the server handling the command, but they can also be regular JavaScript errors for unexpected circumstances.

Generally speaking, specific handling should exist for any relevant failures, and a single catch-all for unexpected errors should also exist. To differentiate the errors, use the isFailure() and isFailureType() functions.

If no error is supplied, the response value can be any plain JavaScript value sent by the server, including any values that can be unserialized from JSON.


void session.destroy ()

Destroys the session.

Once a session is destroyed, it cannot be re-used.


session.on( 'execute' , function (...args) {})

This event is emitted when execute() is called.

The handler for this event accepts the arguments passed to execute().


session.on( 'call' , function (...args) {})

This event is emitted when call() is called.

The handler for this event accepts the arguments passed to call().


session.on( 'notification' , function (type, payload) {})

This event is emitted when a notification is received.

The handler for this event accepts the notification's type string as the first argument, and its payload value as the second argument. The payload value can be any plain JavaScript value sent by the server, including any values that can be unserialized from JSON.

Errors thrown while handling this event will cause disconnection. To avoid this, implement error handling inside the event handler.


session.on( 'response' , function (error, response, namespace, command) {})

This event is emitted when a response is received, and no handler function was specified in the originating call.

The handler for this event accepts the same error and response values as would normally be passed to a handler function supplied to call(). In addition to these arguments, namespace and command are provided, which supply the namespace and command values specified in the originating call.

Errors thrown while handling this event will cause disconnection. To avoid this, implement error handling inside the event handler.


session.on( 'destroy' , function ([error]) {})

This event is emitted once the session is destroyed.

The handler for this event accepts a single, optional error argument. If the session was destroyed normally, via destroy(), error will be undefined.

Failure

Represents a failure response sent by a server. Failures typically represent "expected" error cases that may need to be handled by the client. Some examples of failures might be:

  • Resource not found
  • Input validation failures
  • Unauthorized

Failures are normal JavaScript errors, with the following properties:

Property Description Type Example
type A type used to categorize the failure. string 'not-found'
message A message describing the failure. string 'The specified user does not exist.'
data An optional value populated with additional data by the server. (any) {username: 'jsmith'}

Managed module

require('@rinq/websocket/managed')

The managed module contains higher-lever tools for managing Rinq connections and sessions in an environment where connection to the server is transient, and dependent on network connectivity and availability of servers:


ConnectionManager connectionManager ([options])

Creates a new Rinq connection manager.

The options are represented as a generic object, and may specify:

Option Description Type Example Default
url The URL to connect to. string 'ws://example.org/' (none)
delay A function for calculating the delay before reconnecting. function (see below) (see below)
CBOR A reference to the @rinq/cbor module. object require('@rinq/cbor') (none)
log A set of logging options. object {debug: true} (none)

The url is optional, because it is sometimes necessary to determine this information based upon the outcome of some asynchronous action, such as fetching some external configuration. The URL can also be set later via the connectionManager.url property.

The delay option allows customization of the amount of time between a disconnection, and the subsequent reconnection attempt, based upon the number of consecutive disconnections. The supplied function should take a single argument representing the number of disconnects, and return a delay time in milliseconds. For example, the default delay function is:

function delay (disconnects) {
  return Math.min(Math.pow(2, disconnects - 1) * 1000, 32000)
}

Which produces the following delay times:

Disconnects Delay (seconds)
1 1
2 2
3 4
4 8
5 16
6+ 32

Specifying CBOR is recommended, as it enables messages to be serialized with CBOR rather than JSON.

ConnectionManager

Represents a transient Rinq connection, and allows the creation of session managers:


SessionManager connectionManager.sessionManager ([options])

Creates a new session manager.

The options are represented as a generic object, and may specify:

Option Description Type Example Default
log A set of logging options. object {debug: true} (none)
connectionManager.sessionManager({log: {prefix: '[session-a] '}})

void connectionManager.start ()

Starts the connection manager.

While the connection manager is started, it will attempt to maintain a connection. It will also monitor network availability, and avoid attempting to reconnect when the network is down.


void connectionManager.stop ()

Stops the connection manager.

When the connection manager is stopped, it will close the current connection if it is open, and will not attempt to reconnect until started again.


connectionManager.on( 'connection' , function (connection) {})

This event is emitted when a new open connection is available.

The handler for this event accepts a single connection argument, which is a Rinq connection. The handler is only called when the connection is open, and ready for communication.

This event will fire multiple times (interspersed with error events) as transient communication problems arise, and are resolved. The latest connection should always replace any previous connections.


connectionManager.on( 'error' , function (error) {})

This event is emitted when communication issues arise.

The handler for this event accepts a single error argument. Upon handling this event, no further communication should be attempted until a new connection is received via the next connection event.

SessionManager

Represents a transient Rinq session, and allows the creation of contexts:


Context sessionManager.context ([options])

Creates a new context.

The options are represented as a generic object, and may specify:

Option Description Type Example Default
initialize A function that must complete before the context is ready. function (see below) (none)
log A set of logging options. object {debug: true} (none)

The initialize option allows for the situation where a context is not ready for use until some initialization logic has been performed. This initialization may involve asynchronous operations, and can include communication over a Rinq session.

The function supplied for the initialize option should accept a done callback as the first argument, that must be executed in order for the context to be considered "ready", and a Rinq session as the second argument:

var context = sessionManager.context({
  initialize: function (done) {
    done()
  }
})

The done callback accepts an optional error which, if supplied, will cause the context to emit an error event. An error event will also be emitted if the initialize function throws, using the thrown value as the error. The context will not proceed to the "ready" state, unless the done callback is called without an error argument.

Context initialization can be used to hook up notification event listeners. Remember to clean up listeners as appropriate:

var context = sessionManager.context({
  initialize: function (done, session) {
    session.on('notification', onNotification)
    session.once('destroy', function () {
      session.removeListener('notification', onNotification)
    })
  }
})

Another common use case for context initialization is authentication. For example, this initialization function demonstrates authenticating via a Rinq service:

var context = sessionManager.context({
  initialize: function (done, session) {
    session.call('auth.1', 'token', 'U53R-70K3N', 10000, done)
  }
})

void sessionManager.execute (namespace, command, payload)

Sends a Rinq command, for which no response is expected.

Functionally equivalent to session.execute.


void sessionManager.call (namespace, command, payload, timeout, function (error, response) {})

Sends a Rinq command, and handles the response.

Functionally equivalent to session.call..


void sessionManager.start ()

Starts the session manager, and the connection manager from which it was created.

While the session manager is started, it will attempt to maintain a session.


void sessionManager.stop ()

Stops the session manager.

When the session manager is stopped, it will destroy the current session if it is open, and will not attempt to create a new session until started again.


sessionManager.on( 'session' , function (session) {})

This event is emitted when a new session is available.

The handler for this event accepts a single session argument, which is a Rinq session.

This event will fire multiple times (interspersed with error events) as transient communication problems arise, and are resolved. The latest session should always replace any previous sessions.


sessionManager.on( 'execute' , function (...args) {})

This event is emitted when an underlying session emits an execute event.


sessionManager.on( 'call' , function (...args) {})

This event is emitted when an underlying session emits an call event.


sessionManager.on( 'notification' , function (type, payload) {})

This event is emitted when an underlying session emits a notification event.


sessionManager.on( 'response' , function (error, response, namespace, command) {})

This event is emitted when an underlying session emits a response event.


sessionManager.on( 'error' , function (error) {})

This event is emitted when communication issues arise.

The handler for this event accepts a single error argument. Upon handling this event, no further communication should be attempted until a new connection is received via the next session event.

Context

Allows communication over a transient Rinq session, with the option of asynchronous initialization logic before communication can commence:


void context.start ()

Starts the context, and the session manager and connection manager from which it was created.

While the context is started, it will attempt to maintain a "ready" state.


void context.stop ()

Stops the context.

When the context is stopped, it will not attempt to maintain a "ready" state.


void context.execute (namespace, command, payload)

Sends a Rinq command, for which no response is expected.

Functionally equivalent to session.execute.


void context.call (namespace, command, payload, timeout, function (error, response) {})

Sends a Rinq command, and handles the response.

Functionally equivalent to session.call, except that both timeout, and the handler function are mandatory.


void context.whenReady (function (error) {}[, timeout])

Calls the supplied callback when the context is ready, or immediately if the context is already ready.

If a timeout value is specificed, the callback will be called with an error as the first argument after timeout milliseconds.


context.on( 'ready' , function () {})

This event is emitted when the context has completed any initialization steps, and is ready for communication.

The handler for this event accepts no arguments.

This event will fire multiple times (interspersed with error events) as transient communication problems arise, and are resolved.


context.on( 'error' , function (error) {})

This event is emitted when communication issues arise.

The handler for this event accepts a single error argument. Upon handling this event, no further communication should be attempted until the next ready event.

Logging options

Logging options are represented as a generic object, and may specify:

Option Description Type Example Default
prefix A prefix to use when logging. string '[context-a] ' ''
debug Specifies whether to log debug information. boolean true false

If logging options are omitted entirely, no logging will take place.

Readme

Keywords

Package Sidebar

Install

npm i @rinq/websocket

Weekly Downloads

1

Version

0.2.1

License

MIT

Unpacked Size

430 kB

Total Files

105

Last publish

Collaborators

  • rinq