npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

liaison-core

v0.0.17

Published

A simple messaging library meant to enable easy CORS communication between windows and embedded iframes - using the browser postMessage API.

Downloads

12

Readme

Liaison

Liaison is a simple library with 0 dependencies that enables easy, secure communication between a browser window and embedded iframes, using the browser postMessage API.

The window.postMessage() method safely enables cross-origin communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it.

Parent Model

The Parent model is used to:

  • Define side effects (effects) that the IFrame model can expect to have the parent window run - whenever it requests the parent window to run them.

For most use cases, these side effects are going to be used for 2 general purposes:

  • Requesting the parent window send data to an iframe
    • Ex.) Authorization tokens
  • Notifiying the parent window that some event has occurred within an iframe.
    • Ex.) Informing the parent window that an iframe has finished logging a user out of the iframe.

Initialization

The Parent factory function specifies the id of the iframe we expect to receive messages from, and the origin we should validate that those messages originate from.

const parent = Parent({
    iframe: {
        id: 'my-iframe-id',
        src: 'https://embedded.com',
    }
    ...
});

Both iframe.id and iframe.src are required.

Lifecycle Methods

The Parent model sets all event handlers when it is initially called (Parent({ ... }))

The destroy() removes all event listeners on the parent window that are listening for signals from the specified iframe:

// initialize event handlers
const parent = Parent({ ... });

// remove event handlers if needed
parent.destroy();

Message Handling (Signals)

When the parent window receives a MessageEvent, the Parent model checks if:

  • If the MessageEvent has an origin exactly matching src.
    • If the message has any other origin than src, it is completely ignored.
  • If the origin matches src, the Parent model checks to ensure that the data passed in the MessageEvent matches the expected API (i.e., contains a Signal)
  • If the MessageEvent contains a Signal, this Signal is then used to call a corresponding Effect on the IFrame model.

Effects

Effects (a.k.a., "side effects") are functions defined on the Parent model, that the Parent model can expect to call on the IFrame model.

const parent = Parent({
    ...
    effects: {
        // each `effect` can be synchronous
      sendToken: () => {
        const token = nanoid();
        // ...
      },
        // ... or asynchronous
        sendTokenAsync: async () => {
            await timeout(3000);
            const token = nanoid();
            // ... 
        }
    }
});

// ...
function timeout(ms: number) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Each effect has access to an args object, which contains all the arguments passed from the IFrame model when it requests a certain side effect occur in the parent window:

// ... in parent window
const parent = Parent({
    ...
    effects: {
      logMessageFromIFrame: ({ args }) => {
        console.log(`Message received from iframe: ${args.message}`)
      },
    }
});

// ... in iframe window
const iframe = IFrame({ ... });
iframe.callParentEffect({ 
    name: 'logMessageFromIFrame',
    args: { message: 'Greetings' },
});

// logs "Message received from iframe: Greetings"

Each effect defined in the call to Parent has access to the callIFrameEffect function, allowing it to call back to the iframe:

// ... in parent window
const parent = Parent({
    ...
    effects: {
      sendToken: ({ callIFrameEffect }) => {
        const token = nanoid();
        callIFrameEffect({
            name: 'saveToken',
            args: { token }
        });
      },
    }
});

// ... in iframe window
const iframe = IFrame({
    ...
    effects: {
        saveToken: ({ token }) => {
            localStorage.setItem('authToken', token);
        }
    }
});

You can also use both args and callIFrameEffect together:

// ... in parent window
const parent = Parent({
    ...
    effects: {
      sendToken: ({ args, callIFrameEffect }) => {
        if (args.system === 'client1') {
            token = 'xyz';
        } else {
            token = 'zyx';
        }
        callIFrameEffect({
            name: 'saveToken',
            args: { token }
        });
      },
    }
});

All Together:

const parent = Parent({
    iframe: {
        id: 'my-iframe-id',
        src: 'https://embedded.com',
    },
    effects: {
      sendToken: ({ args, callIFrameEffect }) => {
        if (args.system === 'client1') {
            token = 'xyz';
        } else {
            token = 'zyx';
        }
        callIFrameEffect({
            name: 'saveToken',
            args: { token }
        });
      },
    }
});

IFrame Model

The IFrame model is used to:

  • Define side effects (effects) that the Parent model can expect to have the iframe window run - whenever it requests the iframe window to run them.

Similarly to the Parent model, these effects can be used to enable the parent window to:

  • Request data from the iframe
  • Notify the iframe that some event has occurred in the parent window.

Configuration

The iframe model will only initiate side effects in response to messages that have been verified to come from a recognized domain (parentOrigin):

const iframe = IFrame({
    parentOrigin: 'https://parent.com',
    ...
});

Effects

Each effect defined on the IFrame model can be synchronous or asynchronous:

const iframe = IFrame({
    ...
    effects: {
        load: () => {
            // fetch some data
        },
        lazyLoad: async () => {
            timeout(3000);
            // fetch some data
        }
    }
});

// ...
function timeout(ms: number) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Each effect defined on the IFrame model has access to the args object containing all the arguments passed from the parent window to be used with this effect:

const iframe = IFrame({
    parentOrigin: 'https://parent.com'
    effects: {
        saveData: ({ args }) => {
            // save this data to a db
        },
    }
});

Each effect defined on the IFrame model has access to the callParentEffect function so it can call back to the parent window:

const iframe = IFrame({
    parentOrigin: 'https://parent.com'
    effects: {
        notifyParent: ({ callParentEffect }) => {
            callParentEffect({ name: 'notify', args: { notification: 'Something happened' } })
        },
    }
});

// ... in window with url of 'https://parent.com'
const parent = Parent({
    ...
    effects: {
        notify: ({ args }) => {
            console.log(`Notification: ${args.notification}`)
        },
    }
});

// logs "Notification: Something happened"

API Glossary:

Parent window

The browser window that contains an embedded window

Parent model

The function that can be used to define which iframe the parent window expects to receive signals from, and what effects can run when the iframe requests them to be run.

// use named import
import { Parent } from 'liaison-core';

IFrame window

The embedded iframe window within the parent window

IFrame model

The function that can be used to define which origin it can expect to receive signals from, and what effects can be run when the that origin requests them to be run.

// use named import
import { IFrame } from 'liaison-core';

Signals:

A Signal is an object that contains all the data needed for one client to understand what function it needs to run and the arguments it needs to call that function with.

  • The name property indicates the name of the Effect one client (Parent or IFrame) wants to initiate on the other.

  • The args property is an object containing all of the arguments that the client was the other to include in its call to that effect.

Effects

An Effect is a function that can be run on one of the clients, whenever the other client requests it be run.