Skip to content

drenther/izod

Repository files navigation

izod

NOTE: This is very early stage, documentation is not complete and breaking API changes likely ahead. Please use at your own risk. Lock your version in case you use. (Even though I will adhere to semver for updates)

Bundle Size npm version types

izod leverages zod to provide a type safe Promise oriented API to manage iframe communication.

Installation

core (only)

npm i zod @izod/core

react

npm i zod @izod/core @izod/react

Usage

@izod/core

// common.ts

import { z } from 'zod';
import type { EventMap } from '@izod/core';

export const parentOriginEvents = {
  askQuestion: z.object({
    question: z.string(),
  }),
  shout: z.object({
    message: z.string(),
  }),
} as const satisfies EventMap;

export const childOriginEvents = {
  answerQuestion: z.object({
    answer: z.string(),
  }),
  whisper: z.object({
    message: z.string(),
  }),
} as const satisfies EventMap;
// parent.html

import { createChild } from '@izod/core';

// create the child instance (not mounted until handshake is executed)
const child = createChild({
  container: document.body, // required
  url: 'http://127.0.0.1:3010', // required
  inboundEvents: childOriginEvents, // optional
  outboundEvents: parentOriginEvents, // optional
  handshakeOptions: {
    // optional
    maxHandshakeRequests: 10, // default 5
    handshakeRetryInterval: 100, // default 1000
  },
});

// perfect time to setup event listeners so that they are ready once the handshake is over
// type safe event listeners for events coming from the child
child.on('whisper', (data) => {
  console.log(`Child whispered: ${data.message}`);
});

const childApi = await child.executeHandshake();

// type safe event emitters
childApi.emit('shout', { message: 'Hello' });
// child.html

import { connectToParent } from '@izod/core';

// sets the boilerplate
const parent = connectToParent({
  inboundEvents: parentOriginEvents, // optional
  outboundEvents: childOriginEvents, // optional
});

// type safe event listeners for events coming from the parent
parent.on('shout', (data) => {
  console.log(`Parent shouted: ${data.message}`);
});

const parentApi = await parent.executeHandshake();

// type safe event emitters
parentApi.emit('whisper', { message: 'Hi' });

@izod/react

// parent.tsx

import { child } from '@izod/react';

function Parent() {
  //  accepts all the parameters that `createChild` from @izod/core does
  // `api` is the same that is returned from `connectToParent.executeHandshake` from @izod/core
  // `on` can be used to attach event listeners
  const { on, api, executeHandshake } = child.useCreate({
    container: document.body, // required
    url: 'http://127.0.0.1:3010', // required
    inboundEvents: parentOriginEvents, // optional
    outboundEvents: childOriginEvents, // optional
    handshakeOptions: {
      // optional
      maxHandshakeRequests: 10, // default 5
      handshakeRetryInterval: 100, // default 1000
    },
    onHandshakeComplete(api) {
      // callback called when handshake is successful
    },
    onHandshakeError(error) {
      // callback called when handshake fails
    },
    // remove the iframe on component unmount
    destroyOnUnmount: false, // default false - optional
  });

  // `child.useEventListener` takes care of this boilerplate for you but is not fully type safe as of now
  // to add event listeners
  // prefer this over `onHandshakeComplete` for attaching event listeners
  useEffect(() => {
    if (api) {
      // function is returned from `.on` that can be called to unsubscribe
      const off = on('askQuestion', (data) => {
        console.log('Question: ', data.question);
      });

      // return that from the useEffect for cleanup
      return off;
    }
  }, [api]);

  const ranOnce = useRef(false);
  useEffect(() => {
    if (ranOnce.current) {
      return;
    }

    executeHandshake();
    ranOnce.current = true;
  }, []);

  const shout = () => {
    api.emit('shout', { message: 'Hello' });
  };
}
// child.tsx

import { parent } from '@izod/react';

function Child() {
  // `api` is the same that is returned from `connectToParent.executeHandshake` from @izod/core
  const { on, api, executeHandshake } = parent.useConnect({
    inboundEvents: parentOriginEvents,
    outboundEvents: childOriginEvents,
    onHandshakeComplete(api) {
      // callback called when handshake is successful
    },
    onHandshakeError(error) {
      // callback called when handshake fails
    },
  });

  // `parent.useEventListener` takes care of this boilerplate for you but is not fully type safe as of now
  // to add event listeners
  useEffect(() => {
    // function is returned from `.on` that can be called to unsubscribe
    if (api) {
      const off = api.on('shout', (data) => {
        console.log(`Parent shouted: ${data.message}`);
      });

      // return that from the useEffect for cleanup
      return off;
    }
  }, [api]);

  const ranOnce = useRef(false);
  useEffect(() => {
    if (ranOnce.current) {
      return;
    }

    executeHandshake();
    ranOnce.current = true;
  }, []);

  const whisper = () => {
    api.emit('whisper', { message: 'Hi' });
  };
}

Prior Art (packages I copied adapted code from)

About

type safe, validated, namespaced iframe communication made simple!

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published