The nes protocol consists of JSON messages sent between the client and server.
Each incoming request from the client to the server contains:
type
- the message type:'ping'
- heartbeat response.'hello'
- connection initialization and authentication.'reauth'
- authentication refresh.'request'
- endpoint request.'sub'
- subscribe to a path.'unsub'
- unsubscribe from a path.'message'
- send custom message.
id
- a unique per-client request id (number or string).- additional type-specific fields.
Each outgoing request from the server to the client contains:
type
- the message type:'ping'
- heartbeat request.'hello'
- connection initialization and authentication.'reauth'
- authentication refresh.'request'
- endpoint request.'sub'
- subscribe to a path.'unsub'
- unsubscribe from a path.'message'
- send custom message.'update'
- a custom message push from the server.'pub'
- a subscription update.'revoke'
- server forcedly removed the client from a subscription.
- additional type-specific fields.
If a message is too large to send as a single WebSocket update, it can be chunked into multiple
messages. After constructing the JSON message string, the string is sliced into chunked and each is
sent with the '+'
prefix except for the last chunk sent with the '!'
prefix.
When a message indicates an error, the message will include in addition to the message-specific fields:
statusCode
- an HTTP equivalent status code (4xx, 5xx).headers
- optional headers related to the request.payload
- the error details which include:error
- the HTTP equivalent error message.message
- a description of the error.- additional error-specific fields.
For example:
{
type: 'hello',
id: 1,
statusCode: 401,
payload: {
error: 'Unauthorized',
message: 'Unknown username or incorrect password'
}
}
Flow: server
-> client
-> server
For cases where it is not possible for the TCP connection to determine if the connection is still active, the server sends a heartbeat message to the client every configured interval, and then expects the client to respond within a configured timeout. The server sends:
type
- set to'ping'
.
For example:
{
type: 'ping'
}
When the client receives the message, it sends back:
type
- set to'ping'
.id
- a unique per-client request id (number or string).
For example:
{
type: 'ping',
id: 6
}
Flow: client
-> server
-> client
Every client connection must first be initialized with a hello
message. The client sends a message to the server
with the following:
type
- set to'hello'
.id
- a unique per-client request id (number or string).version
- set to'2'
.auth
- optional authentication credentials. Can be any value understood by the server.subs
- an optional array of strings indicating the path subscriptions the client is interested in.
For example:
{
type: 'hello',
id: 1,
version: '2',
auth: {
headers: {
authorization: 'Basic am9objpzZWNyZXQ='
}
},
subs: ['/a', '/b']
}
The server responds by sending a message back with the following:
type
- set to'hello'
.id
- the sameid
received from the client.heartbeat
- the server heartbeat configuration which can be:false
- no heartbeats will be sent.- an object with:
interval
- the heartbeat interval in milliseconds.timeout
- the time from sending a heartbeat to the client until a response is expected before a connection is considered closed by the server.
socket
- the server generated socket identifier for the connection.
Note: the client should assume the connection is closed if it has not heard from the server in heartbeat.interval + heartbeat.timeout.
For example:
{
type: 'hello',
id: 1,
heartbeat: {
interval: 15000,
timeout: 5000
},
socket: 'abc-123'
}
If the request failed (including subscription errors), the server includes the standard error fields.
For example:
{
type: 'hello',
id: 1,
statusCode: 401,
payload: {
error: 'Unauthorized',
message: 'Unknown username or incorrect password'
}
}
If the request fails due to a subscription error, the server will include the failed subscription path in the response:
path
- the requested path which failed to subscribe.
For example:
{
type: 'hello',
id: 1,
path: '/a',
statusCode: 403,
payload: {
error: 'Subscription not found'
}
}
Flow: client
-> server
-> client
When the authentication credentials have an expiry, the client may want to update the authentication information for the connection:
type
- set to'reauth'
.id
- a unique per-client request id (number or string).auth
- authentication credentials. Can be any value understood by the server.
For example:
{
type: 'reauth',
id: 1,
auth: {
headers: {
authorization: 'Basic am9objpzZWNyZXQ='
}
}
}
The server responds by sending a message back with the following:
type
- set to'reauth'
.id
- the sameid
received from the client.
For example:
{
type: 'reauth',
id: 1
}
If the request failed, the server includes the standard error fields.
For example:
{
type: 'reauth',
id: 1,
statusCode: 401,
payload: {
error: 'Unauthorized',
message: 'Unknown username or incorrect password'
}
}
Flow: client
-> server
-> client
Request a resource from the server where:
type
- set to'request'
.id
- a unique per-client request id (number or string).method
- the corresponding HTTP method (e.g.'GET'
).path
- the requested resource (can be an HTTP path or resource name).headers
- an optional object with the request headers (each header name is a key with a corresponding value).payload
- an optional value to send with the request.
For example:
{
type: 'request',
id: 2,
method: 'POST',
path: '/item/5',
payload: {
id: 5,
status: 'done'
}
}
The server response includes:
type
- set to'request'
.id
- the sameid
received from the client.statusCode
- an HTTP equivalent status code.payload
- the requested resource.headers
- optional headers related to the request (e.g. `{ 'content-type': 'text/html; charset=utf-8' }').
For example:
{
type: 'request',
id: 2,
statusCode: 200,
payload: {
status: 'ok'
}
}
If the request fails, the statusCode
, headers
, and payload
fields will comply with the
standard error values.
Flow: client
-> server
[-> client
]
Sends a custom message to the server where:
type
- set to'message'
.id
- a unique per-client request id (number or string).message
- any value (string, object, etc.).
For example:
{
type: 'message',
id: 3,
message: 'hi'
}
The server response includes:
type
- set to'message'
.id
- the sameid
received from the client.message
- any value (string, object, etc.).
For example:
{
type: 'message',
id: 3,
message: 'hello back'
}
If the request fails, the response will include the standard error fields.
Flow: client
-> server
[-> client
]
Sends a subscription request to the server:
type
- set to'sub'
.id
- a unique per-client request id (number or string).path
- the requested subscription path.
For example:
{
type: 'sub',
id: 4,
path: '/box/blue'
}
The server response includes:
type
- set to'sub'
.id
- the sameid
received from the client.path
- the requested path which failed to subscribe.- the standard error fields if failed.
For example:
{
type: 'sub',
id: 4,
path: '/box/blue',
statusCode: 403,
payload: {
error: 'Forbidden'
}
}
Flow: client
-> server
-> client
Unsubscribe from a server subscription:
type
- set to'unsub'
.id
- a unique per-client request id (number or string).path
- the subscription path.
For example:
{
type: 'unsub',
id: 5,
path: '/box/blue'
}
The server response includes:
type
- set to'sub'
.id
- the sameid
received from the client.- the standard error fields if failed.
For example:
{
type: 'unsub',
id: 5
}
Flow: server
-> client
A custom message sent from the server to a specific client or to all connected clients:
type
- set to'update'
.message
- any value (string, object, etc.).
{
type: 'update',
message: {
some: 'message'
}
}
Flow: server
-> client
A message sent from the server to all subscribed clients:
type
- set to'pub'
.path
- the subscription path.message
- any value (string, object, etc.).
{
type: 'pub',
path: '/box/blue',
message: {
status: 'closed'
}
}
Flow: server
-> client
The server forcefully removed the client from a subscription:
type
- set to'revoke'
.path
- the subscription path.message
- any value (string, object, etc.). An optional last message sent to the client for the specified subscription.
For example:
{
type: 'revoke',
path: '/box/blue',
message: {
reason: 'channel permissions changed'
}
}