@r_wohl/web-channel-message
v3.0.2
Published
A light weight type-safe library for communicating via the Channel Message Web API
Downloads
3
Maintainers
Readme
Web Channel Message
A light-weight library that lets multiple browser sessions interact with each other, through one or more Shared Workers.
Note: Package name might change soon as -unlike initially intended- it does not directly use the Web Channel Message API. Instead, the package functions as an abstraction layer around the SharedWorker API
Example
const channel = new SharedWebChannel();
channel.registerCallback("logout", logoutUser);
function handleClick() {
channel.sendMessage({
type: "callback",
action: "all",
callbackKey: "logout",
});
}
For a working example check out this demo of a NextJS application that uses this library to synchronize state between instances of the same application in different tabs/windows.
Install
NPM
npm i @r_wohl/web-channel-message
YARN
yarn add @r_wohl/web-channel-message
Usage
To use this library, first instantiate a new SharedWebChannel
, which is a wrapper around the SharedWorker API:
import { SharedWebChannel } from "@r_wohl/web-channel-message";
...
const channel = new SharedWebChannel();
If you -for some reason- want to use more then one shared worker, you can instantiate SharedWebChannel
with a name argument:
const anotherChannel = new SharedWebChannel('second-channel');
Tracking open connections
As of version 3.0.0 you can keep track of the number of open connections when the SharedWebChannel
is instantiated, as well as register a callback that is called when this number changes:
const numberOfConnections = channel.connections;
...
channel.onConnectionsUpdate(myCustomHandler);
If a callback is registered to be executed when the number of open connections changes, it will receive the new number of connections as input.
NOTE: Updating channel.connections
when a page/session is closed relies on beforeunload
/unload
/pagehide
events, which do not fire in some browsers/circumstances. See caveats.
Sending messages
You can now send a message to all instances of your application with the sendMessage
method:
function handleClickColor(color: BackgroundColor) {
channel.sendMessage({
type: "callback",
action: "all",
payload: color,
callbackKey: "set-bg-color",
});
}
For the type
property, you can choose between callback mode and observer mode, which determines how the message is handled when it is received.
For the action
property, you can choose between "all"
and "broadcast"
. Messages with "all"
will be sent to all open connections, including the instance from which it was sent. Messages with "broadcast"
will be sent to all other connections.
The payload
property is optional. In "callback"
mode this will be the input for your registered callback function. The same is true for "observer"
mode, but in this case you have more control over what happens with the data in this property (see observer section)
Furthermore, in "callback"
mode you'll need to specify a callbackKey
. In "observer"
mode you can specify a key as well, if you want the message to be received by specific ChannelObservers
only:
channel.sendMessage({
type: "observer",
action: "broadcast",
key: "my-custom-event"
});
Handling messages
When messages are received in the SharedWebChannel
you can either handle them with a registered callback, or with a ChannelObserver
.
Callback
The easiest way to handle message is to register a callback function, together with a specific key:
channel.registerCallback("set-bg-color", setBgColor);
Whenever a message is received with that specific key in the callbackKey
property, the registered callback function is executed with the value of the received payload
property as input.
Observer
Handling incoming messages with ChannelObserver
instances gives you more control over data flow between different instances of your application.
Start with importing the ChannelObserver
:
import { ChannelObserver } from "@r_wohl/web-channel-message";
You can now make a ChannelObserver
instance:
const observer = new ChannelObserver(channel, (data) => {
const payload = data.payload as SomeCustomType;
doSomethingWithPayload(payload)
});
Optionally, you can specify a key when instantiating the ChannelObserver
:
const observer = new ChannelObserver(channel, (data) => {
const payload = data.payload as SomeCustomType;
handlePayload(payload)
}, "my-custom-key");
For any ChannelObserver
with a key, it will only be updated when messages are received with either that specific key, or with the message key property set to "all"
.
Please note that for this reason, you shouldn't set the message key to either "all"
or "default"
for messages in "observer"
mode (the "default"
key is set for ChannelObserver
instances where no key is specified).
Finally, you can unsubscribe a ChannelObserver
from the SharedWebChannel
subject when appropriate, for instance in React.useEffect's return:
useEffect(() => {
const observer = new ChannelObserver(channel, (data) => {
const payload = data.payload as SomeCustomType;
if (payload) {
handlePayload(payload);
}
});
return () => {
channel.subject.unsubscribe(observer);
};
}, []);
Caveats
Shared Worker modules compatibility
Not every browser supports Shared Worker modules at the moment. If instantiating a Shared Worker fails in the SharedWebChannel
constructor, the SharedWebChannel
will detect this and fall back to executing callbacks and updating ChannelObserver
instances for that application instance only.
Updating connection status
It is not possible to accurately keep track of the number of active application sessions in all circumstances and on all browsers in a straightforward manner. In almost all environments closing a tab/navigating away/reloading the page will cause the connection count to be updated properly. However, on iOS -for example-, terminating a page with a close button does not cause a unique event to fire that can be identified to close that connection port inside the shared worker. So until I come up with a better solution, the connection count cannot be fully trusted in environments where closing/reloading pages can be done without firing beforeunload
/unload
/pagehide
events.
Contributors
This is my first NPM library, and I'd would be delighted if anyone would like to comment on the code or contribute in any way. Whether it be a long list of my failures or compliments on the idea/implementation, your input is more than welcome!