Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/le0pard/cable-shared-worker
ActionCable and AnyCable Shared Worker support
https://github.com/le0pard/cable-shared-worker
actioncable anycable shared visibility worker
Last synced: 6 days ago
JSON representation
ActionCable and AnyCable Shared Worker support
- Host: GitHub
- URL: https://github.com/le0pard/cable-shared-worker
- Owner: le0pard
- License: mit
- Created: 2021-12-11T13:04:55.000Z (about 3 years ago)
- Default Branch: main
- Last Pushed: 2024-09-24T14:11:41.000Z (3 months ago)
- Last Synced: 2024-10-29T22:31:45.557Z (about 2 months ago)
- Topics: actioncable, anycable, shared, visibility, worker
- Language: JavaScript
- Homepage:
- Size: 6.87 MB
- Stars: 60
- Watchers: 4
- Forks: 4
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
# Cable-shared-worker (CableSW) - ActionCable and AnyCable Shared Worker support [![Test/Build/Deploy](https://github.com/le0pard/cable-shared-worker/actions/workflows/release.yml/badge.svg?branch=main)](https://github.com/le0pard/cable-shared-worker/actions/workflows/release.yml)
![schema](https://user-images.githubusercontent.com/98444/146681981-2e87a26e-9a5b-4109-9b05-73c1329b3ccc.jpg)
Cable-shared-worker is running ActionCable or AnyCable client in a Shared Worker allows you to share a single websocket connection for multiple browser windows and tabs.
## Motivation
- It's more efficient to have a single websocket connection
- Page refreshes and new tabs already have a websocket connection, so connection setup time is zero
- The websocket connection runs in a separate thread/process so your UI is 'faster'
- Cordination of event notifications is simpler as updates have a single source
- Close connection for non active (on background) tabs (by [Page Visibility API](https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API))
- It's the cool stuff...## Install
```bash
npm install @cable-shared-worker/web @cable-shared-worker/worker
# or
yarn add @cable-shared-worker/web @cable-shared-worker/worker
```Both packages should be the same version.
## Web
You need to initialize worker inside your JS file:
```js
import {initWorker} from '@cable-shared-worker/web'await initWorker('/worker.js')
```Second argument accept different options:
```js
await initWorker(
'/worker.js',
{
workerOptions: { // worker options - more info https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker/SharedWorker
name: 'CableSW'
},
onError: (error) => console.error(error), // subscribe to worker errors
fallbackToWebWorker: true, // switch to web worker on safari
visibilityTimeout: 0, // timeout for visibility API, before close channels; 0 is disabled
onVisibilityChange: () => ({}) // subscribe for visibility changes
}
)
```After this you can start subscription channel:
```js
import {createChannel} from '@cable-shared-worker/web'// Subscribe to the server channel via the client
const channel = await createChannel('ChatChannel', {roomId: 42}, (data) => {
console.log(data)
})// call `ChatChannel#speak(data)` on the server
channel.perform('speak', {msg: 'Hello'})// Unsubscribe from the channel
channel.unsubscribe()
```You can manually close worker (for shared worker this will only close current tab connection, but not worker itself):
```js
import {closeWorker} from '@cable-shared-worker/web'// close tab connection to worker
closeWorker()
```This helpers may help to get info what kind of workers available in browser:
```js
import {
isWorkersAvailable,
isSharedWorkerAvailable,
isWebWorkerAvailable
} from '@cable-shared-worker/web'isWorkersAvailable // return true, if Shared or Web worker available
isSharedWorkerAvailable // return true, if Shared worker available
isWebWorkerAvailable // return true, if Web worker available
```### Visibility API
You can use [Page Visibility API](https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API) to detect, that user move tab on background and close websocket channels. Shared Worker websocket connection can be closed, if no active channels (behaviour controlled by option `closeWebsocketWithoutChannels` in worker component).
```js
import {initWorker} from '@cable-shared-worker/web'initWorker(
'/worker.js',
{
visibilityTimeout: 60, // 60 seconds wait before start close channels, default 0 is disable this functionality
onVisibilityChange: (isVisible, isChannelsWasPaused) => { // callback for visibility changes
if (isVisible && isChannelsWasPaused) {
// this condition can be used to fetch data changes, because channels was closed due to tab on background
}
}
}
)
```## Worker
In worker script (in example `/worker.js`) you need initialize websocket connection.
For actioncable you need installed [@rails/actioncable](https://www.npmjs.com/package/@rails/actioncable) package:
```js
import * as actioncableLibrary from '@rails/actioncable'
import {initCableLibrary} from '@cable-shared-worker/worker'// init actioncable library
const api = initCableLibrary({
cableType: 'actioncable',
cableLibrary: actioncableLibrary
})// connect by websocket url
api.createCable(WebSocketURL)
```For anycable you need install [@anycable/web](https://www.npmjs.com/package/@anycable/web) package:
```js
import * as anycableLibrary from '@anycable/web'
import {initCableLibrary} from '@cable-shared-worker/worker'// init anycable library
const api = initCableLibrary({
cableType: 'anycable',
cableLibrary: anycableLibrary
})// connect by websocket url
api.createCable(WebSocketURL)
```You can also use Msgpack and Protobuf protocols supported by [AnyCable Pro](https://anycable.io/#pro) (you must install the corresponding encoder package yourself):
```js
import * as anycableLibrary from '@anycable/web'
import {MsgpackEncoder} from '@anycable/msgpack-encoder'
import {initCableLibrary} from '@cable-shared-worker/worker'const api = initCableLibrary({
cableType: 'anycable',
cableLibrary: anycableLibrary
})api.createCable(
webSocketURL,
{
protocol: 'actioncable-v1-msgpack',
encoder: new MsgpackEncoder()
}
)// or for protobuf
import * as anycableLibrary from '@anycable/web'
import {ProtobufEncoder} from '@anycable/protobuf-encoder'
import {initCableLibrary} from '@cable-shared-worker/worker'const api = initCableLibrary({
cableType: 'anycable',
cableLibrary: anycableLibrary
})api.createCable(
webSocketURL,
{
protocol: 'actioncable-v1-protobuf',
encoder: new ProtobufEncoder()
}
)
```If you need manually close websocket connection, you can use `destroyCable` method:
```js
import * as actioncableLibrary from '@rails/actioncable'
import {initCableLibrary} from '@cable-shared-worker/worker'const api = initCableLibrary({
cableType: 'actioncable',
cableLibrary: actioncableLibrary
})api.createCable(WebSocketURL)
// later in code
api.destroyCable()
```Method `initCableLibrary` accept additional option `closeWebsocketWithoutChannels`:
```js
const api = initCableLibrary({
cableType: 'actioncable',
cableLibrary: actioncableLibrary,
// if true (default), worker will close websocket connection, if have zero active channels
// example: all tabs on the background send a signal to close all channels by visibility API timeout
closeWebsocketWithoutChannels: false
})
```## Custom communication between window and worker
You can use cable-shared-worker for custom communication between window and worker. In window you can use method `sendCommand` to send custom command to worker:
```js
import {initWorker} from '@cable-shared-worker/web'const worker = await initWorker('/worker.js')
worker.sendCommand('WINDOW_CUSTOM_COMMAND', {data: 'example'})
```On worker side you need define `handleCustomWebCommand` function. First argument will be custom command (in example `WINDOW_CUSTOM_COMMAND`), second one - command data (in example `{data: 'example'}`), third one - response function, which can send response command to window:
```js
import * as actioncableLibrary from '@rails/actioncable'
import {initCableLibrary} from '@cable-shared-worker/worker'const api = initCableLibrary({
cableType: 'actioncable',
cableLibrary: actioncableLibrary,
handleCustomWebCommand: (command, data, responseFunction) => {
responseFunction('WORKER_CUSTOM_COMMAND', {another: 'data'})
}
})
```To handle custom commands from worker in window, you need provide `handleCustomWorkerCommand` method in `initWorker`:
```js
import {initWorker} from '@cable-shared-worker/web'const worker = await initWorker(
'/worker.js',
{
handleCustomWorkerCommand: (command, data) => {
console.log('worker response', command, data)
}
}
)worker.sendCommand('WINDOW_CUSTOM_COMMAND', {data: 'example'})
```Note: You cannot [send commands](https://github.com/le0pard/cable-shared-worker/blob/main/shared/constants.js), that the package uses itself for communication.
## Browser Support
Supported modern browsers, that support Shared Worker (IE, Opera Mini not supported).
Safari supports [Shared Worker](https://caniuse.com/sharedworkers) only from version 16.0 (Sep, 2022). For older version, package will switch to Web Worker, which cannot share connection between tabs. You can disable fallback to Web Worker by `fallbackToWebWorker: false` (or use `isSharedWorkerAvailable` for own logic).
## Development
```bash
$ yarn # install all dependencies
$ yarn dev # run development build with watch functionality
$ yarn build # run production build
$ yarn lint # run eslint checks
$ yarn test # run tests
```