Installation:

npm: npm install @hapi/nes

yarn: yarn add @hapi/nes

Introduction

nes adds native WebSocket support to hapi-based application servers. Instead of treating the WebSocket connections as a separate platform with its own security and application context, nes builds on top of the existing hapi architecture to provide a flexible and organic extension.

Protocol version: 2.4.x (different from module version)

Protocol

The nes protocol is described in the Protocol documentation.

Examples

Route invocation

Server

const Hapi = require('@hapi/hapi');
const Nes = require('@hapi/nes');

const server = new Hapi.Server();

const start = async () => {

    await server.register(Nes);
    server.route({
        method: 'GET',
        path: '/h',
        config: {
            id: 'hello',
            handler: (request, h) => {

                return 'world!';
            }
        }
    });

    await server.start();
};

start();

Client

const Nes = require('@hapi/nes');

var client = new Nes.Client('ws://localhost');

const start = async () => {

    await client.connect();
    const payload = await client.request('hello');  // Can also request '/h'
    // payload -> 'world!'
};

start();

Subscriptions

Server

const Hapi = require('@hapi/hapi');
const Nes = require('@hapi/nes');

const server = new Hapi.Server();

const start = async () => {

    await server.register(Nes);
    server.subscription('/item/{id}');
    await server.start();
    server.publish('/item/5', { id: 5, status: 'complete' });
    server.publish('/item/6', { id: 6, status: 'initial' });
};

start();

Client

const Nes = require('@hapi/nes');

const client = new Nes.Client('ws://localhost');
const start = async () => {

    await client.connect();
    const handler = (update, flags) => {

        // update -> { id: 5, status: 'complete' }
        // Second publish is not received (doesn't match)
    };

    client.subscribe('/item/5', handler);
};

start();

Broadcast

Server

const Hapi = require('@hapi/hapi');
const Nes = require('@hapi/nes');

const server = new Hapi.Server();

const start = async () => {

    await server.register(Nes);
    await server.start();
    server.broadcast('welcome!');
};

start();

Client

const Nes = require('@hapi/nes');

const client = new Nes.Client('ws://localhost');
const start = async () => {

    await client.connect();
    client.onUpdate = (update) => {

        // update -> 'welcome!'
    };
};

start();

Route authentication

Server

const Hapi = require('@hapi/hapi');
const Basic = require('@hapi/basic');
const Bcrypt = require('bcrypt');
const Nes = require('@hapi/nes');

const server = new Hapi.Server();

const start = async () => {

    await server.register([Basic, Nes]);

    // Set up HTTP Basic authentication

    const users = {
        john: {
            username: 'john',
            password: '$2a$10$iqJSHD.BGr0E2IxQwYgJmeP3NvhPrXAeLSaGCj6IR/XU5QtjVu5Tm',   // 'secret'
            name: 'John Doe',
            id: '2133d32a'
        }
    };

    const validate = async (request, username, password) => {

        const user = users[username];
        if (!user) {
            return { isValid: false };
        }

        const isValid = await Bcrypt.compare(password, user.password);
        const  credentials = { id: user.id, name: user.name };
        return { isValid, credentials };
    };

    server.auth.strategy('simple', 'basic', { validate });

    // Configure route with authentication

    server.route({
        method: 'GET',
        path: '/h',
        config: {
            id: 'hello',
            handler: (request, h) => {

                return `Hello ${request.auth.credentials.name}`;
            }
        }
    });

    await server.start();
};

start();

Client

const Nes = require('@hapi/nes');

const client = new Nes.Client('ws://localhost');
const start = async () => {

    await client.connect({ auth: { headers: { authorization: 'Basic am9objpzZWNyZXQ=' } } });
    const payload = await client.request('hello')  // Can also request '/h'
    // payload -> 'Hello John Doe'
};

start();

Subscription filter

Server

const Hapi = require('@hapi/hapi');
const Basic = require('@hapi/basic');
const Bcrypt = require('bcrypt');
const Nes = require('@hapi/nes');

const server = new Hapi.Server();

const start = async () => {

    await server.register([Basic, Nes]);

    // Set up HTTP Basic authentication

    const users = {
        john: {
            username: 'john',
            password: '$2a$10$iqJSHD.BGr0E2IxQwYgJmeP3NvhPrXAeLSaGCj6IR/XU5QtjVu5Tm',   // 'secret'
            name: 'John Doe',
            id: '2133d32a'
        }
    };

    const validate = async (request, username, password) => {

        const user = users[username];
        if (!user) {
            return { isValid: false };
        }

        const isValid = await Bcrypt.compare(password, user.password);
        const  credentials = { id: user.id, name: user.name };
        return { isValid, credentials };
    };

    server.auth.strategy('simple', 'basic', 'required', { validate });

    // Set up subscription

    server.subscription('/items', {
        filter: (path, message, options) => {

            return (message.updater !== options.credentials.username);
        }
    });

    await server.start();
    server.publish('/items', { id: 5, status: 'complete', updater: 'john' });
    server.publish('/items', { id: 6, status: 'initial', updater: 'steve' });
};

start();

Client

const Nes = require('@hapi/nes');

const client = new Nes.Client('ws://localhost');

// Authenticate as 'john'

const start = async () => {

    await client.connect({ auth: { headers: { authorization: 'Basic am9objpzZWNyZXQ=' } } });
    const handler = (err, update) => {

        // First publish is not received (filtered due to updater key)
        // update -> { id: 6, status: 'initial', updater: 'steve' }
    };

    client.subscribe('/items', handler);
};

start();

Browser Client

When you require('@hapi/nes') it loads the full module and adds a lot of extra code that is not needed for the browser. The browser will only need the nes client. If you are using CommonJS you can load the client with require('@hapi/nes/lib/client').

Registration

The nes plugin uses the standard hapi registration process using the server.register() method. The plugin accepts the following optional registration options:

  • onConnection - a function with the signature function(socket) invoked for each incoming client connection where:
    • socket - the Socket object of the incoming connection.
  • onDisconnection - a function with the signature function(socket) invoked for each incoming client connection on disconnect where:
    • socket - the Socket object of the connection.
  • onMessage - a function with the signature async function(socket, message) used to receive custom client messages (when the client calls client.message()) where:
    • socket - the Socket object of the message source.
    • message - the message sent by the client.
    • the function may return a response to the client.
  • auth - optional plugin authentication options with the following supported values:
    • false - no client authentication supported.
    • an object with the following optional keys:
      • type - the type of authentication flow supported by the server. Each type has a very different security profile. The following types are supported:
        • 'direct' - the plugin configures an internal authentication endpoint which is only called internally by the plugin when the client provides its authentication credentials (or by passing an auth option to client.connect()). The endpoint returns a copy of the credentials object (along with any artifacts) to the plugin which is then used for all subsequent client requests and subscriptions. This type requires exposing the underlying credentials to the application. Note that if the authentication scheme uses the HTTP request method (e.g. hawk or oz) you need to use 'auth' as the value (and not 'GET'). This is the default value.
        • 'cookie' - the plugin configures a public authentication endpoint which must be called by the client application manually before it calls client.connect(). When the endpoint is called with valid credentials, it sets a cookie with the provided name which the browser then transmits back to the server when the WebSocket connection is made. This type removes the need to expose the authentication credentials to the JavaScript layer but requires an additional round trip before establishing a client connection.
        • 'token' - the plugin configures a public authentication endpoint which must be called by the client application manually before it calls client.connect(). When the endpoint is called with valid credentials, it returns an encrypted authentication token which the client can use to authenticate the connection by passing an auth option to client.connect() with the token. This type is useful when the client-side application needs to manage its credentials differently than relying on cookies (e.g. non-browser clients).
      • endpoint - the HTTP path of the authentication endpoint. Note that even though the 'direct' type does not exposes the endpoint, it is still created internally and registered using the provided path. Change it only if the default path creates a conflict. Defaults to '/nes/auth'.
      • id - the authentication endpoint identifier. Change it only if the default id creates a conflict. Defaults to nes.auth.
      • route - the hapi route config.auth settings. The authentication endpoint must be configured with at least one authentication strategy which the client is going to use to authenticate. The route value must be set to a valid value supported by the hapi route auth configuration. Defaults to the default authentication strategy if one is present, otherwise no authentication will be possible (clients will fail to authenticate).
      • password - the password used by the iron module to encrypt the cookie or token values. If no password is provided, one is automatically generated. However, the password will change every time the process is restarted (as well as generate different results on a distributed system). It is recommended that a password is manually set and managed by the application.
      • iron - the settings used by the iron module. Defaults to the iron defaults.
      • cookie - the cookie name when using type 'cookie'. Defaults to 'nes'.
      • isSecure - the cookie secure flag when using type 'cookie'. Defaults to true.
      • isHttpOnly - the cookie HTTP only flag when using type 'cookie'. Defaults to true.
      • path - the cookie path when using type 'cookie'. Defaults to '/'.
      • domain - the cookie domain when using type 'cookie'. Defaults to no domain.
      • ttl - the cookie expiration milliseconds when using type 'cookie'. Defaults to current session only.
      • index - if true, authenticated socket with user property in credentials are mapped for usage in server.broadcast() calls. Defaults to false.
      • timeout - number of milliseconds after which a new connection is disconnected if authentication is required but the connection has not yet sent a hello message. No timeout if set to false. Defaults to 5000 (5 seconds).
      • maxConnectionsPerUser - if specified, limits authenticated users to a maximum number of client connections. Requires the index option enabled. Defaults to false.
      • minAuthVerifyInterval - if specified, waits at least the specificed number of milliseconds between calls to await server.auth.verify() to check if credentials are still valid. Cannot be shorter than heartbeat.interval. Defaults to heartbeat.interval or 15000 if heartbeat is disabled.
  • headers - an optional array of header field names to include in server responses to the client. If set to '*' (without an array), allows all headers. Defaults to null (no headers).
  • payload - optional message payload settings where:
    • maxChunkChars - the maximum number of characters (after the full protocol object is converted to a string using JSON.stringify()) allowed in a single WebSocket message. This is important when using the protocol over a slow network (e.g. mobile) with large updates as the transmission time can exceed the timeout or heartbeat limits which will cause the client to disconnect. Defaults to false (no limit).
  • heartbeat - configures connection keep-alive settings where value can be:
    • false - no heartbeats.
    • an object with:
      • interval - time interval between heartbeat messages in milliseconds. Defaults to 15000 (15 seconds).
      • timeout - timeout in milliseconds after a heartbeat is sent to the client and before the client is considered disconnected by the server. Defaults to 5000 (5 seconds).
  • maxConnections - if specified, limits the number of simultaneous client connections. Defaults to false.
  • origin - an origin string or an array of origin strings incoming client requests must match for the connection to be permitted. Defaults to no origin validation.

Server

The plugin decorates the server with a few new methods for interacting with the incoming WebSocket connections.

await server.broadcast(message, [options])

Sends a message to all connected clients where:

  • message - the message sent to the clients. Can be any type which can be safely converted to string using JSON.stringify().
  • options - optional object with the following:
    • user - optional user filter. When provided, the message will be sent only to authenticated sockets with credentials.user equal to user. Requires the auth.index options to be configured to true.

Note that in a multi server deployment, only the client connected to the current server will receive the message.

server.subscription(path, [options])

Declares a subscription path client can subscribe to where:

  • path - an HTTP-like path. The path must begin with the '/' character. The path may contain path parameters as supported by the hapi route path parser.
  • options - an optional object where:
    • filter - a publishing filter function for making per-client connection decisions about which matching publication update should be sent to which client. The function uses the signature async function(path, message, options) where:
      • path - the path of the published update. The path is provided in case the subscription contains path parameters.
      • message - the message being published.
      • options - additional information about the subscription and client:
        • socket - the current socket being published to.
        • credentials - the client credentials if authenticated.
        • params - the parameters parsed from the publish message path if the subscription path contains parameters.
        • internal - the internal options data passed to the publish call, if defined.
      • the function must return a value of (or a promise that resolves into):
        • true - to proceed sending the message.
        • false - to skip sending the message.
        • { override } - an override message to send to this socket instead of the published one. Note that if you want to modify message, you must clone it first or the changes will apply to all other sockets.
    • auth - the subscription authentication options with the following supported values:
      • false - no authentication required to subscribe.
      • a configuration object with the following optional keys:
        • mode - same as the hapi route auth modes:
          • 'required' - authentication is required. This is the default value.
          • 'optional' - authentication is optional.
        • scope - a string or array of string of authentication scope as supported by the hapi route authenticate configuration.
        • entity - the required credentials type as supported by the hapi route authentication configuration:
          • 'user'
          • 'app'
          • 'any'
        • index - if true, authenticated socket with user property in credentials are mapped for usage in server.publish() calls. Defaults to false.
    • onSubscribe - a method called when a client subscribes to this subscription endpoint using the signature async function(socket, path, params) where:
      • socket - the Socket object of the incoming connection.
      • path - the path the client subscribed to
      • params - the parameters parsed from the subscription request path if the subscription path definition contains parameters.
    • onUnsubscribe - a method called when a client unsubscribes from this subscription endpoint using the signature async function(socket, path, params) where:
      • socket - the Socket object of the incoming connection.
      • path - Path of the unsubscribed route.
      • params - the parameters parsed from the subscription request path if the subscription path definition contains parameters.

await server.publish(path, message, [options])

Sends a message to all the subscribed clients where:

  • path - the subscription path. The path is matched first against the available subscriptions added via server.subscription() and then against the specific path provided by each client at the time of registration (only matter when the subscription path contains parameters). When a match is found, the subscription filter function is called (if present) to further filter which client should receive which update.
  • message - the message sent to the clients. Can be any type which can be safely converted to string using JSON.stringify().
  • options - optional object that may include
    • internal - Internal data that is passed to filter and may be used to filter messages on data that is not sent to the client.
    • user - optional user filter. When provided, the message will be sent only to authenticated sockets with credentials.user equal to user. Requires the subscription auth.index options to be configured to true.

await server.eachSocket(each, [options])

Iterates over all connected sockets, optionally filtering on those that have subscribed to a given subscription. This operation is synchronous.

  • each - Iteration method in the form async function(socket).
  • options - Optional options object
    • subscription - When set to a string path, limits the results to sockets that are subscribed to that path.
    • user - optional user filter. When provided, the each method will be invoked with authenticated sockets with credentials.user equal to user. Requires the subscription auth.index options to be configured to true.

Socket

An object representing a client connection.

socket.id

A unique socket identifier.

socket.app

An object used to store application state per socket. Provides a safe namespace to avoid conflicts with the socket methods.

socket.auth

The socket authentication state if any. Similar to the normal hapi request.auth object where:

  • isAuthenticated - a boolean set to true when authenticated.
  • credentials - the authentication credentials used.
  • artifacts - authentication artifacts specific to the authentication strategy used.

socket.server

The socket's server reference.

socket.connection

The socket's connection reference.

socket.disconnect()

Closes a client connection.

socket.isOpen()

Returns true is the socket connection is in ready state, otherwise false.

await socket.send(message)

Sends a custom message to the client where:

  • message - the message sent to the client. Can be any type which can be safely converted to string using JSON.stringify().

await socket.publish(path, message)

Sends a subscription update to a specific client where:

  • path - the subscription string. Note that if the client did not subscribe to the provided path, the client will ignore the update silently.
  • message - the message sent to the client. Can be any type which can be safely converted to string using JSON.stringify().

await socket.revoke(path, message, [options])

Revokes a subscription and optionally includes a last update where:

  • path - the subscription string. Note that if the client is not subscribe to the provided path, the client will ignore the it silently.
  • message - an optional last subscription update sent to the client. Can be any type which can be safely converted to string using JSON.stringify(). Pass null to revoke the subscription without sending a last update.
  • options - optional settings:
    • ignoreClosed - ignore errors if the underlying websocket has been closed. Defaults to false.

Request

The following decorations are available on each request received via the nes connection.

request.socket

Provides access to the Socket object of the incoming connection.

Client

The client implements the nes protocol and provides methods for interacting with the server. It supports auto-connect by default as well as authentication.

new Client(url, [options])

Creates a new client object where:

  • url - the WebSocket address to connect to (e.g. 'wss://localhost:8000').
  • option - optional configuration object where:
    • ws - available only when the client is used in node.js and passed as-is to the ws module.
    • timeout - server response timeout in milliseconds. Defaults to false (no timeout).

client.onError

A property used to set an error handler with the signature function(err). Invoked whenever an error happens that cannot be associated with a pending request.

client.onConnect

A property used to set a handler for connection events (initial connection and subsequent reconnections) with the signature function().

client.onDisconnect

A property used to set a handler for disconnection events with the signature function(willReconnect, log) where:

  • willReconnect - a boolean indicating if the client will automatically attempt to reconnect.
  • log - an object with the following optional keys:
    • code - the RFC6455 status code.
    • explanation - the RFC6455 explanation for the code.
    • reason - a human-readable text explaining the reason for closing.
    • wasClean - if false, the socket was closed abnormally.

client.onHeartbeatTimeout

A property used to set a handler for heartbeat timeout events with the signature function(willReconnect) where:

  • willReconnect - a boolean indicating if the client will automatically attempt to reconnect.

Upon heartbeat timeout, the client will disconnect the websocket. However, the client.onDisconnect() property will only be called (if set) once the server has completed the closing handshake. Users may use this property to be notified immediately and take action (e.g. display a message in the browser).

client.onUpdate

A property used to set a custom message handler with the signature function(message). Invoked whenever the server calls server.broadcast() or socket.send().

await client.connect([options])

Connects the client to the server where:

  • options - an optional configuration object with the following options:
    • auth - sets the credentials used to authenticate. when the server is configured for 'token' type authentication, the value is the token response received from the authentication endpoint (called manually by the application). When the server is configured for 'direct' type authentication, the value is the credentials expected by the server for the specified authentication strategy used which typically means an object with headers (e.g. { headers: { authorization: 'Basic am9objpzZWNyZXQ=' } }).
    • reconnect - a boolean that indicates whether the client should try to reconnect. Defaults to true.
    • delay - time in milliseconds to wait between each reconnection attempt. The delay time is cumulative, meaning that if the value is set to 1000 (1 second), the first wait will be 1 seconds, then 2 seconds, 3 seconds, until the maxDelay value is reached and then maxDelay is used.
    • maxDelay - the maximum delay time in milliseconds between reconnections.
    • retries - number of reconnection attempts. Defaults to Infinity (unlimited).
    • timeout - socket connection timeout in milliseconds. Defaults to the WebSocket implementation timeout default.

await client.disconnect()

Disconnects the client from the server and stops future reconnects.

client.id

The unique socket identifier assigned by the server. The value is set after the connection is established.

await client.request(options)

Sends an endpoint request to the server where:

  • options - value can be one of:
    • a string with the requested endpoint path or route id (defaults to a GET method).
    • an object with the following keys:
      • path - the requested endpoint path or route id.
      • method - the requested HTTP method (can also be any method string supported by the server). Defaults to 'GET'.
      • headers - an object where each key is a request header and the value the header content. Cannot include an Authorization header. Defaults to no headers.
      • payload - the request payload sent to the server.

Rejects with Error if the request failed.

Resolves with object containing:

  • payload - the server response object.
  • statusCode - the HTTP response status code.
  • headers - an object containing the HTTP response headers returned by the server (based on the server configuration).

await client.message(message)

Sends a custom message to the server which is received by the server onMessage handler where:

  • message - the message sent to the server. Can be any type which can be safely converted to string using JSON.stringify().

await client.subscribe(path, handler)

Subscribes to a server subscription where:

  • path - the requested subscription path. Paths are just like HTTP request paths (e.g. '/item/5' or '/updates' based on the paths supported by the server).
  • handler - the function used to receive subscription updates using the signature function(message, flags) where:
    • message - the subscription update sent by the server.
    • flags - an object with the following optional flags:
      • revoked - set to true when the message is the last update from the server due to a subscription revocation.

Note that when subscribe() is called before the client connects, any server errors will be throw by connect().

await client.unsubscribe(path, handler)

Cancels a subscription where:

  • path - the subscription path used to subscribe.
  • handler - remove a specific handler from a subscription or null to remove all handlers for the given path.

client.subscriptions()

Returns an array of the current subscription paths.

client.overrideReconnectionAuth(auth)

Sets or overrides the authentication credentials used to reconnect the client on disconnect when the client is configured to automatically reconnect, where:

Returns true if reconnection is enabled, otherwise false (in which case the method was ignored).

Note: this will not update the credentials on the server - use client.reauthenticate().

await client.reauthenticate(auth)

Will issue the reauth message to the server with updated auth details and also override the reconnection information, if reconnection is enabled. The server will respond with an error and drop the connection in case the new auth credentials are invalid.

Rejects with Error if the request failed.

Resolves with true if the request succeeds.

Note: when authentication has a limited lifetime, reauthenticate() should be called early enough to avoid the server dropping the connection.

Errors

When a client method returns or throws an error, the error is decorated with:

  • type - a string indicating the source of the error where:
    • 'disconnect' - the socket disconnected before the request completed.
    • 'protocol' - the client received an invalid message from the server violating the protocol.
    • 'server' - an error response sent from the server.
    • 'timeout' - a timeout event.
    • 'user' - user error (e.g. incorrect use of the API).
    • 'ws' - a socket error.

Changelog

#285
Unknown authentication strategy
#289
Update deps
#284
Add ability to revoke a socket ignoring if it is already closed
#283
cleanup called twice if there is an onUnsubscribe handler
#278
Passing remoteAddress and x-forwarded-for is in whitelist when connect
#279
Fix name
11.0.0
breaking changes
#276
Remove client dist
#248
eachSocket behaves as synchronous without filter, but async with filter
#275
Change module namespace
#270
Error in the browser with 10.0.1
#269
Update dependencies
10.0.0
breaking changes
#265
hapi v18
#263
Document minAuthVerifyInterval and make it less restrictive
#262
Close the socket when authentication verification fails
#260
Close the socket when authentication expires
#256
Cleanup
#247
Reconnect on error
#243
Add client.onHeartbeatTimeout hook
8.0.0
breaking changes
#229
Update ws to 5.x.x
#228
Mark NesErrors as coming from nes
#224
Add socket.info object
#223
Delay heartbeats until server is started
#222
Allow isSameSite cookie option to be passed
#221
Update hapijs/call to 5.x.x
#220
Ignore node network errors after opening client socket
#214
Do not replace message for all sockets with override from filter
#206
Handle ws socket error events
#198
Better error handling
7.0.0
breaking changes
#196
hapi v17
#195
Update ws
#194
Update babel script
#202
Fix test
#201
Update ws
#179
`maximum call stack exceeded` if web socket gets disconnected while nes is sending packets
#156
Handle undefined payloads from server in client
#171
server falls with bad cookie header
#169
Expose server and connection on socket
#168
Update deps
#145
Disable inject() validate
#144
Verify request Origin
#142
Reconnection timeout conflicts
#141
Add client.js and dist/ to npm tarball -Closes #140
#140
Missing root client.js and dist
#139
Update deps
#138
Always call onConnection before onSubscribe
#136
Reuse pre-stringified hapi response
#135
Cookie auth fails to register socket
#134
Per user connection limit
#133
Timeout sockets if hello takes too long
#132
Client indicates wasRequested when calling disconnect() on dead socket
#131
Incorrectly reports wasRequested when internally disconnects
#129
Include reason disconnected in log object
#127
Test on node v6
#126
Handle cases when client._ws is null
6.0.0
breaking changes
#125
Connect fails to check for previous internal socket when connecting.
#124
Adjust unsub processing timing on server
#123
Check is socket is still around while disconnecting
#122
Prevent server from sending data when stopping
5.0.0
breaking changes
#121
Properly clean up sockets and subscriptions on server stop
#120
Support user filter in eachSocket()
#119
Subscription revoke()
#118
Keep separate subscription per connection
#117
Race condition between unsubscribe and disconnect when using auth index on sub
#116
User-specific broadcast
#115
User-specific publish
#112
Expose socket and support publish override
#109
Typo: listners -> listeners
#108
Fix UMD global handling
#105
Client.js getting root as undefined
#107
Expose params on unsubscribe callback
#104
Provide a way to replace auth credentials before an automatic reconnect
4.0.0
breaking changes
#100
Add required callback to subscription onSubscribe option
#99
Pause heartbeat disconnect while sending
#98
Server disconnects client after a long message transmission
3.1.0
breaking changes
#97
Break large messages into smaller chunks
#94
Support sending initial subscription update
3.0.0
breaking changes
#96
Update Iron and increase auto-generated password to 32 characters
#95
Emit error when disconnecting due to missing server heartbeat
#93
Client requests fail if the request response takes longer than heartbeat timeout
#92
Return close event reason in onDisconnect
#90
NesError breaks if passed an error event from onerror.
#76
Pass the onclose information to onDisconnect
#89
Decorate errors with source type
#88
onDisconnect() reports reconnect state incorrectly
#87
The update to ws 1.0.x introduces breaking changes
#86
update ws to 1.0.x
2.0.0
breaking changes
#84
Change client.subscribe() to require a callback
#83
Add version check in hello
#81
Fix illegal invocation error when using default onError handler.
#79
Possible to subscribe, unsubscribe, and resubscribe? v1.2.0
1.2.0
breaking changes
#72
Update to create a es5 client artifact
#71
Add socket.app
#70
Decorate request with socket
#69
ES6 style changes
#67
Use internal auth endpoint id for lookup
#66
Plugin route prefix cause mismatch with default internal auth endpoint
#65
Return auth error message in response
#64
Implement internal data for filtering messages
#63
Add message metadata for publish
#62
Implement eachSocket iterator
#61
Implement per-subscription lifecycle
#60
Add onDisconnection callback
#56
Add disconnection callback
1.0.0
breaking changes
#46
socket.send()
#45
Rename onBroadcast to onUpdate
#44
Max reconnect retries
#43
Connection timeout
#22
Add heartbeat
#19
Allow for custom callback data to Client.connect()
#8
Request timeout
0.4.0
breaking changes
#42
Remove duplicated statusCode in errors
#41
Filter headers in responses
#40
Consistent error transmission
#38
Prevent client from including a request authorization header
#34
Prevent client from subscribing multiple times to same path
#32
Change server onConnect to onConnection and delay until after auth when required
#30
Block all client requests when channel requires auth
#29
Fix typo
#28
Implement unsubscribe on the server
#27
Expose socket authentication state
#26
Ability to force disconnect for specific clients
#25
'data' vs 'message' for incoming data
#24
Subscriptions are not cleaned on client disconnect
#21
Drop support for non-protocol messages
#20
Change default auth method to direct
#18
Race condition exists in client reconnect logic
#17
Allow for custom messages
#16
Turn auth on by default
#15
Add onConnect callback option
#14
Add event callback for client connection
#12
Add browser packaging entry points
#11
Initial publish/subscribe interface
#9
Use console.error() when no onError handler
#6
Re-auth on reconnect
#5
Allow any authentication method when using token auth
#4
Allow handler to handle unknown message types
#3
How will adding additional functionality work?
clipboard