Wimp.js

Making window.postMessage practical and pleasurable

Introduction

Wimp's goal is to make window.postMessage easy and viable. It's inspired by both HTTP and websockets, making it familiar (hopefully) to web developers. It's an es6 class, and supports both promises and callbacks.

Installation

Just include a script tag in your html
<script src="wimp.min.js"></script>

Basic example

In this example we're going to have index.html fetch the height of an iframe via a request. Note that this is an example, and far from the best way to do it (you could do it better with or without Wimp).
index.html
<body>
    <iframe src="iframe1.html" id="frame1"></iframe>

    <script src="wimp.min.js"></script>
    <script>
        Wimp.init() // Listens for messages. Needs to be called BEFORE the outgoing request is made (or else you can risk missing the response message)

        const frame1 = new Wimp("#frame1") // Initialze a new Wimp, which will be able to cumminicate with #frame1

        frame1.ready()
            .then(() => {
            // A request sends a...request. This is much like making a HTTP GET request. The response we are expecting will be the height of the iframe.
            return frame1.request("height")
        })
            .then(req => {
            document.getElementById("frame1").height = req.data; // Duh
        })
    </script>
</body>
frame1.html
<body>
    ...
    <script src="wimp.min.js"></script>
    <script>
        const container = new Wimp(window.parent) // Can also take a window object

        // Defines a "height" route. On a "height" request it will pass both the request and a response function to your callback.
        container.on("height", (req, res) => {
            res(document.body.scrollHeight)
        })

        Wimp.init() // Init should be called AFTER any routes are defined
    </script>
</body>
See this example live

Basic stream example

Now we're going to build a simple counter. Our iframe will send a stream message out whenever a button is clicked.
index.html
<body>

    <h1 id="counterValue"></h1>

    <iframe id="frame1" src="frame1.html"></iframe>

    <script src="wimp.min.js"></script>

    <script>
        Wimp.init() // Init is called BEFORE any outgoing wimp requests or listeners

        const counterValue = document.getElementById("counterValue")

        const counterWimp = new Wimp("#frame1")

        counterWimp.ready().then(() => {
            counterWimp.listen("click", (count) => {
                counterValue.innerHTML = count
            })
        })
    </script>
</body>
frame1.html
<body>

    <button id="counter">Count + 1</button>

    <script src="wimp.min.js"></script>

    <script>
        let counter = 0;

        const buttonStream = new Wimp(window.parent).createStream("click", (data, res) => {
            res(counter)
        })

        Wimp.init() // Note that init is called AFTER the stream has been defined

        const counterButton = document.getElementById("counter")

        counterButton.onclick = () => {
            counter++
            buttonStream.emit(counter)
        }

    </script>
</body>
See this example live

Proxy example

With proxying iframes can commuicate with each other through their parent...or any other medium. In this example we'll build a counter again, but this time instead of showing the counter value in index.html, we will show it in frame2.html. Note that frame1 remains unchanged from above.
index.html
<body>

    <iframe id="frame1" src="frame1.html"></iframe>
    
    <iframe id="frame2" src="frame2.html"></iframe>

    <script src="wimp.min.js"></script>

    <script>
        Wimp.proxyingEnabled = true // Allow frames to use index.html as a proxy
        
        Wimp.init() // Still needed
    </script>
</body>
frame2.html
<body>

    <h1 id="counterValue"></h1>

    <script src="../../src/wimp.min.js"></script>

    <script>
        Wimp.init();

        const counterValue = document.getElementById("counterValue")

        const counterWimp = new Wimp("#frame1", window.parent)

        counterWimp.ready()
            .then(() => {
                counterWimp.listen("click", (count) => {
                    counterValue.innerHTML = count
                })
            })
    </script>
</body>
See this example live

API docs


const myWimp = new Wimp(target [, proxy]) Creates a new Wimp instance for cross frame communication.

target - The iframe/window we're communicating with. Can be any of the following, or an array of any number of the following.

Query selector String e.g. "#frame1"
Content window Object e.g. window.parent or document.getElementByID("#frame1").contentWindow. Can not be used in conjuction with proxy
"*" - Wildcard String Uses window.frames. Note that new iframes will not automatically be added.
Registered target String A preregistered iframe's name. See **LINK ME UP BOIIIII**
Object Object Can be an object like {selector: "#frame1", origin: "mydomain.com"}, where selector can be any of the above.

proxy - If included, requests etc. will be sent through the proxy and to the target within it's DOM. It's the same as target, however when proxying is enabled, target can not be a content window.

myWimp.ready([fn]) Called when Wimp.init() has been called in all this Wimp instance's targets.

fn - Function - Function called when all the targets are ready. Can also be omiited and then a promise will be returned, which will resolve when all the targets are ready.

myWimp.request(request [, options, callback]) Sends a request, much like a HTTP GET/POST request

request - String - The request string you're trying to make. Think of it as a URL.

options - Object Specifies options for the request. Can be omitted.

Request Request can also be omitted and defined within options
expectResponse Boolean - Default: true. If false callback will not be called. Makes the request one way.
data Data to be sent with the request. Can be anything, but note that it will go through JSON.stringify and then JSON.parse

callback(response) - Function Optional - called when the request completes. `data` is the response which the request receives. If omitted a promise will be returned instead. response is an object containing the following. .then((response) => {}) is identical.

success Boolean - true if the request was successful, false if not
data Any - The received data from the request. This is exactly what is supplied by res(someData) from the route (myWimp.on(...))
requestID The request's ID. Can be treated as unique
error.message If success is false an error message will be specified here.
myWimp.on(request, fn) Much like a server route. Receives a request with data(passed to fn), and replies (again with a function passed to fn).

request - String - corresponds to the request coming from myWimp.request.

fn(data, res) - Function Called whenever a new request comes in.

data Data that came with the request. Already parsed
res(response), res.error(message) Function - Response function which sends response back to the client. Use res.error(message) in case of an error, with message being the error message.
myWimp.addTarget(target) Adds a target to the instance of Wimp. Useful for example when trying to communicate with all frames AND a popup window.
myWimp = new Wimp("*") // All iframes
myWimp.addTarget(window.open("popup.html")) // Add the popup window to the instance

Target - Many - Same as new Wimp(target).

myWimp.requery() Requeries the targets. This will delete alll the targets, and requery all the selectors. So if you passed window.parent or anything other than a string selector as the target to the constructor, they will be deleted and NOT readded. You can add them again with myWimp.addTarget(window.parent) however.
myWimp.readyCheck(reset) Can be called to wait for targets to be ready again. reset should be set to true, otherwise it will use cache, resulting in an instant call of any ready callbacks (note that a promise can not fulfill twice).
myWimp.hashSync() Will sync the url hash between two iframes. myWimp.hashSync() must be called on both frames' wimp instance.

Streams


myStream = new Wimp("*").createStream(name [, options], joinFn) Creates a new stream, much like a websocket.

name - String - Name of the stream (kinda like the URL) for client to connect to.

options - Object Optional options object. Specifies options for the stream.

Name Name can be omitted and specified in options instead. Has higher priority.

joinFn(data, res) - Function Called upon every new client connection

data Client can send data along with the connection request. This would be it
res(response) Function - Sends a response back to the client. This response will be treated exactly the same as myStream.emit(response), but will only be sent to this particular client
myStream.emit(data) Sends data to all clients connected to the stream.

data - Any - Can be anything, but keep in mind that it will go through JSON.stringify and then JSON.parse

new Wimp("#frame1").listen(name, fn) Connects to a stream.

name - String - Corresponds to the name in createStream(name...)

fn(data) - Function - Called whenever a new message is sent from the stream. Data is the...data.

Static properties and methods


Wimp.init([selector]) Needs to be called in order to start listening for messages. (e.g. responses for requests and stream response). Should be called after any routes or streams are defined, but before any outgoing requests are made.

selector - Function - You can replace the default query selector by passing a new one. Defaults to document.querySelectorAll.bind(document). Can be handy for working with Polymer/shadow DOM

Wimp.registerTarget(name, target) Saves an iframe/popup with a name. Can be useful when using proxying. E.g. Wimp.registerTarget("lovelyPopup", window.open(...)), as this would be the only way to access the popup from outside of the window.

name - String - Unique (within the window) identifier. The associated iframe/popup can then be accessed under this name.

target - Many - See new Wimp(...), as this target is the same

Wimp.proxy Property. Defaults to false. When set to true will allow iframes and popups to use it as a proxy to talk to other iframes/popups within it's DOM.
Wimp.pendingRequest Object. List of pending requests for all instances.
Wimp.registeredTargets Object. List of registered targets.