README
ws-promise
This project enables you to use async
and await
while communicating over WebSockets.
Behind the scenes, the WebSocket API is first wrapped in a Promise
layer, and then the different endpoints are wired together through a tiny RPC protocol.
In summary, it enables you to write code like this on the client…:
client.mjs
import Client from "ws-promise/Client";
const client = new Client("ws://localhost:8000");
(async () => {
await client.open();
/* The client can now call all server (!) methods that you expose */
const six = await client.add(1, 2, 3);
console.log(six);
})();
…and code like this on the server:
server.mjs
import Server from "ws-promise/Server";
class MathServer extends Server {
constructor() {
super({
engineOptions: {
port: 8000
}
});
}
async onAdd(message, ...args) {
/* Clients can sum up numbers on the server */
await message.reply(args.reduce((a, b) => a + b));
/* In this line, the client will have received the result! */
}
}
const server = new MathServer();
server.open();
Note that client.add
will actually contact the server
and call its onAdd
method with the arguments [1, 2, 3]
as args
. The result of this call is a Promise
that we can await
to retrieve the resulting number from the server. You don't need to differentiate multiple calls of the same type. The protocol does this for you, so an endpoint will only get a response when the remote endpoint has replied to it specifically. If a method doesn't exist on an endpoint, it will automatically be looked up on the remote endpoint.
Hence, the client can call server methods, and the server can also call client methods.
Getting started
Installation
Most likely, you won't be using ws-promise
at build time, so you should install it as a run-time dependency:
$ npm install --save ws-promise
Setup
Client (browser)
On a browser, there already is a native WebSocket
client that you can use. Therefore, you can simply write:
import Client from "ws-promise/Client";
const client = new Client(url);
Client (node)
As WebSocket
is a web standard, it is not part of the node.js
runtime. Therefore, if you would like to instantiate a client in a non-browser environment, you have to pass a standards-compliant WebSocket
client class implementation for it to use. A good example for such an implementation would be ws
.
import ws from "ws";
import Client from "ws-promise/Client";
const client = new Client(url, {
engine: ws
});
Server
Most non-browser environments have no built-in WebSocket
implementation either, so the same rules apply.
Note that this project has been tested against the client and server engines from ws
. In general, all engines with the corresponding events that ws
provides should work just as well.
import Server from "ws-promise/Server";
import WSClient from "ws";
const { Server: WSServer } = WSClient;
const server = new Server({
engine: WSServer,
engineOptions: {
port: 8000
}
});
If you don't provide any engine, the node.js
version comes pre-bundled with the engine ws
; so this will also work:
import Server from "ws-promise/Server";
const server = new Server({
engineOptions: {
port: 8000
}
});
Usage
Adding event listeners
There's two ways to make the server respond to your remote procedure calls. One way is to embrace ES2015 classes and add methods whose name is determined by the name of the remote procedure call. For example, if your client is trying to call client.saveImage()
, you should extend the Server
class and give your class a saveImage
method:
import Server from "ws-promise/Server";
class ImageServer extends Server {
onSaveImage(message, ...args) {
/* Your code here. */
}
}
If you're not too fond of classes, that's fine, too. You can achieve the same by using a single instance of Server
and then registering event listeners, similar to how you would do it in the DOM:
import Server from "ws-promise/Server";
const server = new Server();
server.addEventListener("saveImage", (message, ...args) => {
/* Your code here. */
});
Note that on
is a shorthand for addEventListener
.
Wildcard events
When debugging your endpoint, it can often be useful to log all incoming events, regardless of their type. The wildcard event is named message
, so the corresponding method to implement would be onMessage
. The arguments start with an additional parameter to help you figure out the event name, i. e.:
server.on("message", (event, message, ...args) => {
/* Your code here. */
});
API
Before reading this API documentation, note that classes are documented in PascalCase
and class instances in camelCase
.
Client
class: constructor(url[, protocols], [options])
Constructs a new instance of Client
, but in contrast to native WebSocket
clients, this deliberately avoids a connection to url
until you call client.open
— it merely serves the construction of the class.
url
: <string> The WebSocket URL to the instance of yourServer
class.protocols
: <?string | ?[string]> Analogous to the browser'sWebSocket
API.options
: <?Object>autoReconnect
: <boolean> Whether the client should attempt to reconnect to the server in case of a sudden connection loss. Defaults totrue
.binaryType
: <?string> Indicates how to represent binary data. For an overview of values that you can assign to this option, see WebSocket.binaryType or WS.binaryType. If set toundefined
, the expected values to be transferred and received will be of typestring
. Defaults to"arraybuffer"
.decode
: <function(string | Uint8Array): Object>: A function that will parse incoming values. IfbinaryType
isundefined
, this function will be invoked with astring
. IfbinaryType
is"arraybuffer"
, this function will be invoked with aUint8Array
. Defaults toMessagePackLite.decode
.encode
: <function(Object): string | Uint8Array>: A function that will be used to serialize outgoing values. The return type is dependent on thebinaryType
option. IfbinaryType
isundefined
, this function must return astring
. IfbinaryType
is"arraybuffer"
, this function must return aUint8Array
. Defaults toMessagePackLite.encode
.engine
: <function(string, ?string | ?[string], Object): WebSocket | WS.WebSocket> The client engine that should power the client. Defaults toglobalThis.WebSocket
.engineOptions
: <Object> The options passed to the underlying WebSocket client engine. Note that in browsers,globalThis.WebSocket
has nooptions
argument, so providing it only makes sense in non-browser environments.reconnectionFactor
: <number> A factor by which the reconnection delay is prolonged for every failed connection attempt. Defaults to1.15
.reconnectionMinimum
: <number> The minimum delay between two reconnection attempts in milliseconds. Defaults to200
.
- returns: <Client>
client.close()
Note: This method is idempotent.
Disconnects the client from the server.
client.open()
Connects to the server URL specified in the constructor by url
.
client.anyOtherMethod(...args)
Note:
anyOtherMethod
is just a placeholder, you can (and should) put any desired method name here.
When run, tries to execute anyOtherMethod
on the Server
. If the server chooses to handle this call, the Promise
will resolve with the value that the server has replied with.
...args
: <...any> The arguments to pass toanyOtherMethod
if the server chooses to execute it. Note that each value must be serializable viaencode
.- returns: <Promise<any>>
Server
class: constructor(url[, protocols], [options])
Constructs a new instance of Server
. but in contrast to WS.WebSocket.Server
clients, this deliberately avoids a connection to url
until you call server.open
— it merely serves the construction of the class.
url
: <string> The WebSocket URL to the instance of yourServer
class.protocols
: <?string | ?[string]> Analogous to the browser'sWebSocket
API.options
: <?Object>decode
: <function(string | Uint8Array): Object>: A function that will parse incoming values. Defaults toMessagePackLite.decode
.encode
: <function(Object): string | Uint8Array>: A function that will be used to serialize outgoing values. Defaults toMessagePackLite.encode
.engine
: <function(string, ?string | ?[string], Object): WS.WebSocket.Server> The server engine that should power the server. Defaults toWS.WebSocket.Server
.engineOptions
: <Object> The options passed to the underlying WebSocket server engine.
- returns: <Server>
server.close()
Closes the server.
server.open()
Starts listening.