@evecalm/message-hub v1.0.19
Features
- Tinny: less than 2kb gzipped, no external dependencies required
- Compatibility: use
postMessageunder the hood, support all modern browser(even IE8) - Consistency: use same api every where(parent window, iframe window, worker, etc)
- Simple API: use api
onemitoffto handle all messages in current context from any window(parent window, child window, workers) - Dedicated API: use api
createDedicatedMessageHubto create a dedicated message-hub to communicate with specified window(parent window, child window or worker) - Responsible:
emitwill return a promise that you can get response from the other side. You can respondemiton 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 -Sor
yarn add @evecalm/message-hubUsage
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
yarnto install the project dependencies - run
yarn run devto 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
peertoself - if you are using it in normal window thread and want to handle message from worker, just set
peerto 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
peertoself - if you are using it in normal window thread and want to handle message from worker, just set
peerto a instance ofWorker(akanew Worker('./xxxx.js')) orServiceWorker( not tested yet, should works fine 🧐 ) - you can set
peerto*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 frame1WinMessageHub.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.