Simple data exchange between tabs with the same origin
ThruTab
This module is designed to simplify communication between tabs in the browser, for example to avoid loading some information twice, or to synchronize the results of your requests in time.
In real-world tasks it is very useful to combine with Redux
storage, but nobody can forbid you to use it for localStorage
.
Works on Vanilla, React Hooks, Vue 3 (testing)!
This module has an exposed callback, which will need to be added to a message event in the Service Worker
So Service Worker
is required!
Basic Usage
There’s a list of basic functions:
- getTypedSwTunnel()
- manageSubscription(signature: string)
- registerHandler(requestHandler, syncHandler)
- unregisterHandler()
- requestData({…requestOptions})
- syncData(key, data)
Usage with frameworks:
React
Implementations
getTypedSwTunnel()
Used in Service Worker
// Returns Typed SW Tunnel for managing messages
getTypedSwTunnel()
// Usage in service-worker.ts
self.addEventListener('message', getTypedSwTunnel<any>())
// NOTE: self is ServiceWorkerGlobalScope
manageSubscription()
Used in Service Worker registration
// Will add your signature to SW Tunnel clientStore
manageSubscription(ONLOAD_GENERATED_SIGNATURE)
// Somewhere in registration Service Worker with Workbox Window
// For example you can use this in your index.tsx
...
const localSign = crypto.randomBytes(10).toString('hex')
wb.register()
.then((reg) => {
manageSubscription(localSign)
})
...
requestData()
// Request key someKey in other tabs
requestData({
requestKey:'someKey',
timeout: CUSTOM_TIMEOUT_IN_MS
})
.then((result) => {
setData(result)
})
.catch((e) => console.log('Rejected by timeout, probably', e))
syncData()
// Patch someKey value in all tabs (besides the current)
syncData('someKey', cachedData)
registerHandler()
Used in a module that has access to the cache (for example with @redux/toolkit
)
See also in React.js section
const handleRequests: RequestHandlerFn = useCallback(
(data, reply, reject) => {
console.log('Got to handle', data)
if (data && Object(cache).hasOwnProperty(data.requestKey)) {
console.log('request is going to be fulfilled')
reply(cache[data.requestKey].data)
} else {
reject()
}
},
[cache],
)
const handleSync: SyncHandlerFn = useCallback(
(data) => {
dispatch(
save({
key: data.key,
data: data.data,
}),
)
},
[dispatch],
)
const handlersRef = registerHandler(handleRequests, handleSync)
...
// Do not forget to unregister handler before page closes
unregisterHandler(handlersRef)
unregisterHandler()
Check last lines of the previous section
As React Hooks
useConnectorFn(requestHandler, syncHandler)
const handleRequests: RequestHandlerFn = useCallback(
(data, reply, reject) => {
console.log('Got to handle', data)
if (data && Object(cache).hasOwnProperty(data.requestKey)) {
console.log('request is going to be fulfilled')
reply(cache[data.requestKey].data)
} else {
reject()
}
},
[cache],
)
const handleSync: SyncHandlerFn = useCallback(
(data) => {
dispatch(
save({
key: data.key,
data: data.data,
}),
)
},
[dispatch],
)
useConnectorFn(handleRequests, handleSync)
useSharedData()
const { requestData, syncData } = useSharedData()
// Request key someKey in other tabs
requestData('someKey', CUSTOM_TIMEOUT_IN_MS)
.then((result) => {
setData(result)
})
.catch((e) => console.log('Rejected by timeout, probably', e))
// Patch someKey value in all tabs (besides the current)
syncData('someKey', cachedData)