modern-websocket v0.1.0
modern-websocket
modern-websocket
is a small wrapper around the standard WebSocket
class that provides a modern interface (i.e. async iterators, promises, abort controllers) similar to that of WebTransport. It does not offer any additional functionality compared to its standard counterpart.
Installation
$ npm install modern-websocket
Usage
Creating a connection
import ModernWebSocket from 'modern-websocket';
// This controller can be used later on to close the connection.
let controller = new AbortController();
let socket = new ModernWebSocket('ws://localhost:1234', {
protocols: ['...'], // Defaults to [] as the WebSocket constructor.
signal: controller.signal
});
Listening for messages
Messages are obtained using an asynchronous iterator:
for await (let message of socket.iter()) {
processMessage(message);
}
let { done, value: message } = await socket.iter().next();
If the connection is closed while waiting for a message, the corresponding promise will be resolved and the iterator will end gracefully if the connection closed cleanly, otherwise the promise will reject in the same circumstances as socket.closed
. The promise is never fulfilled if the connection could not be established.
If the connection is closed while not waiting for a message (e.g. while processing the previous message), buffered messages will not be yielded and the behavior described above will occur when awaiting the next message.
Sending messages
socket.send(message);
Listening for status changes
Status changes can be detected using promises:
socket.ready.then(() => { ... });
socket.closed.then(() => { ... });
When the connection closes, socket.closed
will be resolved if the connection closed cleanly, and will be rejected otherwise. The code and reason invoked can be obtained from the resolved object or rejected error, respectively.
socket.closed.then((result) => {
result.code
result.reason
}, (err) => {
err.code
err.reason
});
If the connection to the server cannot be established or if the socket is closed (i.e. with socket.close()
) before the connection can be established, socket.ready
will be rejected in the same circumstances as would otherwise be socket.closed
, and the latter will instead remain unfulfilled.
The absence of listeners on these promises will lead to uncaught rejection errors when they are rejected.
Closing the connection
Unlike its standard counterpart, socket.close()
is asynchronous as it awaits socket.closed
, and will be rejected in the same conditions as socket.closed
. Calling socket.close()
multiple times or when the connection is already closed is a no-op.
When aborting the signal provided to the constructor, the socket will be closed with the code 1000 (normal closure) instead of the default code 1005 used when calling socket.close()
with no arguments.
await socket.close();
await socket.close(code, reason);
// or
controller.abort();
Using other WebSocket properties
Standard WebSocket
properties such as binaryType
or bufferedAmount
, among others, all behave as expected.
Using listen()
listen()
is a small wrapper used to simplify communication. In particular:
- It first awaits
socket.ready
and will thus wait for the connection to be established before calling its handler. The promise returned will also be rejected in the same conditions assocket.ready
. - If an error is thrown by the handler, the connection will be subsequently closed with error code 4000 and the promise returned by
listen()
will be rejected with that error. - The returned value of the handler will be returned by
listen()
itself.
await socket.listen(async (conn) => {
for await (let message of conn.iter()) {
}
let { done, value } = await conn.iter().next();
});
Closing the connection when the page closes
It is good practice to close the connection when the page enters the frozen (through the freeze
event) or terminated (through the pagehide
event) states as defined by the Page Lifecycle API in order to avoid blocking the back/forward cache.
let socket;
// The 'pageshow' event is triggered both when loading a new page and when
// restoring a terminated page.
window.addEventListener('pageshow', () => {
socket = new ModernWebSocket();
});
window.addEventListener('freeze', () => {
// Note that the 'socket.ready' promise will be rejected if the connection
// had not been established yet.
socket?.close();
});
window.addEventListener('pagehide', () => {
socket?.close();
});
2 years ago