MessageHub
A tinny(~2kb) utility that can simplify cross window(iframes, even workers) communications over `postMessage` and `addEventListener('message', xxx)`
Features
- Tinny: less than 2kb gzipped, no external dependencies required
-
Compatibility: use
postMessage
under the hood, support all modern browser(even IE8) - Consistency: use same api every where(parent window, iframe window, worker, etc)
-
Simple API: use api
on
emit
off
to handle all messages in current context from any window(parent window, child window, workers) -
Dedicated API: use api
createDedicatedMessageHub
to create a dedicated message-hub to communicate with specified window(parent window, child window or worker) -
Responsible:
emit
will return a promise that you can get response from the other side. You can respondemit
on other side by return result inon
's callback -
Proxy Message: with api
createProxy
, you can proxy all messages from a window(iframe, webworker, etc) to another window(worker) - Typescript support: this utility is written in typescript, has type definition inborn
Install
There are too many packages with similar names, and it's so hard to pick a pretty package name, so I use scoped package :)
npm install @evecalm/message-hub -S
or
yarn add @evecalm/message-hub
Usage
The following demo shows you how to use it to make normal window and its iframe communicate easily
in main window
import MessageHub from "@evecalm/messagehub"
// get child iframe's window, peerWin could be `self.parent` or `new Worker('./worker.js')`
const iframeWin1 = document.getElementById('child-iframe-1').contentWindow
const iframeWin2 = document.getElementById('child-iframe-2').contentWindow
// ----- listen messages from peer ----
// listen the message pageTitle from iframeWin1, and respond it
MessageHub.on(iframeWin1, 'pageTitle', () => {
return document.title
})
// respond to message getHead from iframeWin2
MessageHub.on(iframeWin2, 'getHead', () => {
return document.head.outHTML
})
// listen multi messages by passing a handler map
MessageHub.on(iframeWin1, {
// no return, then the response is undefined
notice: (name, msg) => {
console.log(`notice message from ${name} with message ${msg}`)
},
getToken () {
return Math.random()
}
})
// ---- send message to peer ---
// send a message to iframeWin1, and get the response by `.then`
MessageHub.emit(iframeWin1, "fib", 10).then(resp => {
console.log("fibonacci of 10 is", resp)
})
// sending a message not handled by the peer will catch an error
MessageHub.emit(iframeWin1, "some-not-existing-method").then(resp => {
console.log('response', resp) // this won't run
}).catch(err => {
console.warn('error', err) // bang!
})
in iframe window
import MessageHub from "@evecalm/messagehub"
const peerWin = window.parent
// send a message to the parent and get its response
MessageHub.emit(peerWin, "pageTitle").then(title => {
console.log("page title of main thread", title)
})
// create a dedicated message hub, so you won't need to pass `peerWin` every time
const messageHub = MessageHub.createDedicatedMessageHub(peerWin)
// send message to the parent, don't need the response
messageHub.emit("notice", 'Jim', 'hello!')
// calc fibonacci, respond by a return
messageHub.on("fib", async (num) => {
// emit a message and wait its response
const title = await messageHub.emit("pageTitle")
console.log(title)
return fib(num)
});
// listen multi messages by passing a handler map
messageHub.on({
method1 () {},
method2 () {},
})
// use a recursive algorithm which will take more than half a minute when n big than 50
function fib(n) {
if (n < 2) return n
return fib(n - 1) + fib(n - 2)
}
To see a real world example, you may:
- clone this repo
- check the code in folder
test
- run
yarn
to install the project dependencies - run
yarn run dev
to view the sample - navigate to http://localhost:1234/worker/index.html to see worker example
- navigate to http://localhost:1234/frame/index.html to see iframe example
API
MessageHub.emit(peer: Window | Worker, methodName: string, ...args: any[])
Send a message to peer, invoking methodName
registered on the peer via on
with all its arguments args
.
This api return a promise, you can get response or catch the exception via it.
peer
- if you are using it in worker thread and want to send message to parent, just set
peer
toself
- if you are using it in normal window thread and want to handle message from worker, just set
peer
to a instance ofWorker
(akanew Worker('./xxxx.js')
) orServiceWorker
( not tested yet, should works fine🧐 )
methodName
method name you can want to call(emit) which registered(on) in peer
args
args
vary with methodName
's handler registered via on
in peer's context
MessageHub.on
Listen messages sent from peer, it has following forms:
// register(listen)) one handler for methodName when message received from peer
MessageHub.on(peer: Window | Worker | '*', methodName: string, handler: Function)
// register(listen)) multi handlers
MessageHub.on(peer: Window | Worker | '*', handlerMap: Record<string, Function>)
// register only one handler to deal with all messages from peer
MessageHub.on(peer: Window | Worker | '*', singleHandler: Function)
peer
- if you are using it in Worker thread and want to handle message from parent, just set
peer
toself
- if you are using it in normal window thread and want to handle message from worker, just set
peer
to a instance ofWorker
(akanew Worker('./xxxx.js')
) orServiceWorker
( not tested yet, should works fine🧐 ) - you can set
peer
to*
to listen all messages from all peers(parent, children, workers) to current window. Due to worker's restrictions, you need register worker so that*
could works worker's message byMessageHub.on(worker, {})
methodName
Method name to register, a methodName
can only has one handler
, the handler
will be overrode if you set same methodName
multi times
handler
- handler could be an async function
- if handlers with same methodName registered both in specified peer and
*
, only handler for peer will be triggered when a message sent to peer
handlerMap
A object of handlers, keys are methodNames, values are handlers
singleHandler
singleHandler
will receive all parameters, i.e. (methodName, ...args)
MessageHub.off(peer: Window | Worker | '*', methodName?: string)
Remove message listener. if methodName
presented, remove methodName
's listener, or remove the whole peer's listener
MessageHub.createDedicatedMessageHub(peer?: Window | Worker)
Create a dedicated message-hub for specified peer, so that you won't need to pass peer every time.
It returns a new messageHub with following properties:
{
/** if you didn't set a peer when invoking createDedicatedMessageHub, then you can use `setPeer` to set it when it's ready*/
setPeer: (peer: Window | Worker) => void;
emit: (methodName: string, ...args: any[]) => any;
on: (methodName: string, handler: Function) => void;
on: (handlerMap: Record<string, Function>) => void;
off: (methodName?: string) => any;
}
MessageHub.createProxy(fromWin: Window | Worker, toWin: Window | Worker)
Forward all messages from fromWin
to toWin
then forward toWin
's response to the fromWin
, instead of handle messages by self
There is a funny use case:
If you got two iframes in your page, you can make them communicate directly by following code
MessageHub.createProxy(frame1Win, frame2Win) // forward message from frame1Win to frame2Win
MessageHub.createProxy(frame2Win, frame1Win) // forward message from frame2Win to frame1Win
MessageHub.createProxyFor(peer: Window | Worker) * deprecated *
Deprecated, but still working, you should use MessageHub.createProxy(peer, window.parent)
instead.
Forward all messages from peer to parent window then forward parent's response to the peer, instead of handle messages by self.