protoframe

A small (dependency-free) library for 2-way iframe communication.

Usage no npm install needed!

<script type="module">
  import protoframe from 'https://cdn.skypack.dev/protoframe';
</script>

README

Protoframe

Build Status

A small (dependency-free) library for 2-way iframe communication.

Problem

You are embedding an iframe where you would like to communicate with the following facilities:

  1. From your parent page, you need to use iframe.contentWindow.postMessage
  2. From your iframe, you need to subscribe to these messages using window.addEventListener('message', (ev) => ...)
  3. And vice versa, sometimes at the same time

Solution

This library allows you to define a protocol for this type of communication supporting both fire-and-forget (tell) semantics, as well as request/response (ask) semantics. It provides connector constructors that will facilitate the sending and receiving of these messages across parent and iframe windows.

How?

npm install protoframe --save

ProtoframeDescriptor

import { ProtoframeDescriptor } from 'protoframe';

const cacheProtocol: ProtoframeDescriptor<{
  getFoo: {
    body: { key: string };
    response: { value: string };
  };
  setBar: {
    body: { key: string; value: string };
  };
}> = { type: 'cache' };

The ProtoframeDescriptor defines 2 elements of your protocol:

  1. The message types. The type argument is a structural type that defines the message types as its top level key. For each message type, it defines the body type of the payload (for ask and tell requests), and an optional response types (for ask requests). An ask message must define a response key, a tell message must not
  2. The value of the descriptor ({type: 'cache'}) defines a type key for the protocol, which is used to key on specific types of messages to listen to between the pages

Connectors

The connector allows 2-way communication between a page and its iframe. For example, we have a page that wishes to use an iframe as a "cache" server:

Iframe:

import { ProtoframePubsub } from 'protoframe';

// Will hold our cache state
const data: { [key: string]: string } = {};

// The connector. This function creates a connector that listens for messages
// on `window` and sends messages and responses over `window.parent`
const cache = ProtoframePubsub.iframe(cacheProtocol);

// Implement our handlers for the ask and tell requests defined in the protocol
cache.handleTell('setBar', ({ key, value }) => (data[key] = value));
cache.handleAsk('getFoo', async ({ key }) => {
  const value = key in data ? data[key] : null;
  return { value };
});

Parent:

import { ProtoframePubsub } from 'protoframe';

const iframe = document.getElementById('myCacheServerIframe');
const client = ProtoframePubsub.parent(cacheProtocol, iframe);

// Wait for the iframe page to load and for the connector to be instantiated
ProtoframePubsub.connect(client).then(() => {
  client.tell('setBar', { key: 'my key', value: 'my value' });

  // value = { value: 'my value' }
  const value = await client.ask('getFoo', { key: 'my key' });
});