@dynamic-labs/iframe-setup
TypeScript icon, indicating that this package has built-in type declarations

4.18.3 • Public • Published

@dynamic-labs/iframe-setup

A utility package that enables the Dynamic SDK (@dynamic-labs/sdk-react-core) to work seamlessly within an iframe environment.

Installation

npm install @dynamic-labs/iframe-setup

Features

This package provides the setupIframe method thats used to setup the iframe environment.

Implementation Guide

Step 1: Parent Page Setup

Configure the parent page that will contain the iframe.

To setup the iframe, you need to pass the parent URL to the iframe using the initial-parent-url query parameter

import { setupIframe } from '@dynamic-labs/iframe-setup';

const iframeURL = new URL('https://iframe.com');

if (typeof window !== 'undefined') {
  iframeURL.searchParams.set('initial-parent-url', window.location.href);
}

const App = () => {
  const iframeRef = useRef<HTMLIFrameElement>(null);

  useEffect(() => {
    const cleanUp = setupIframe(iframeRef.current!, iframeURL.origin);

    return () => {
      cleanUp();
    };
  }, []);

  return (
    <div>
      <iframe ref={iframeRef} src={iframeURL.toString()} />
    </div>
  );
};

Step 2: Iframe Application Setup

Inside your iframe application, initialize the SDK:

import { DynamicContextProvider } from '@dynamic-labs/sdk-react-core';
import { setupInsideIframe } from '@dynamic-labs/utils';

// Initialize as early as possible in your iframe application
setupInsideIframe();

const App = () => {
  return (
    <DynamicContextProvider>{/* Your application */}</DynamicContextProvider>
  );
};

Once both steps are completed, the Dynamic SDK will be fully operational within your iframe environment.


Alternative Implementation

Manual Setup (Without Package Installation in Parent App)

If you're unable to install the package in the parent application, you can directly implement the setupIframe functionality by copying the code below:

/**
 * Setup an iframe to communicate with the Dynamic SDK
 * running inside the iframe
 *
 * This file is self contained and should not import or depend on any
 * other file or package.
 *
 * @see https://www.npmjs.com/package/@dynamic-labs/iframe-setup
 */
/* eslint no-restricted-syntax: ["error", "ImportDeclaration"] */

/**
 * The same implementation was added to the utils package
 * and they should be kept in sync
 * The type is duplicated here to avoid a dependency on the utils package
 * @see packages/utils/src/setupInsideIframe/types.ts
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type MESSAGE_HANDLER = (...params: any[]) => Promise<any> | void;
type IFRAME_EVENTS = {
  OPEN_URL: (args: {
    url: string;
    target?: 'self' | 'blank';
    features?: string;
  }) => void;
  UPDATE_PARENT_URL: (args: { url: string }) => void;
  APP_FOCUS: () => void;
};

/**
 * Setup an iframe to comunicate with the Dynamic SDK
 * running inside the iframe
 *
 * @returns a function to teardown the iframe setup
 */
export const setupIframe = (
  iframe: HTMLIFrameElement,
  origin: string,
): VoidFunction => {
  if (typeof window === 'undefined') return () => {};

  const messageHandler = createMessageHandler<IFRAME_EVENTS>(window, origin);
  const messageSender = createMessageSender<IFRAME_EVENTS>(iframe);

  // Add event listener to handle deeplink
  const setupOpenUrlHandler = () =>
    messageHandler('OPEN_URL', ({ url, target = 'self', features }) => {
      if (target === 'blank') {
        window.open(url, '_blank', features);
      } else {
        window.location.assign(url);
      }
    });

  // Watch for url changes and update Dynamic SDK in iframe
  const setupUrlUpdateHandler = (): VoidFunction => {
    const onPopState = () => {
      const url = window.location.href;

      messageSender('UPDATE_PARENT_URL', { url });
    };

    window.addEventListener('popstate', onPopState);

    return () => {
      window.removeEventListener('popstate', onPopState);
    };
  };

  /**
   * Watch for app focus and send event to Dynamic SDK in iframe
   */
  const watchForAppVisibilityChange = (): VoidFunction => {
    let lastFired = false;

    const onVisibilityOrFocus = () => {
      if (!lastFired && document.visibilityState === 'visible') {
        lastFired = true;
        messageSender('APP_FOCUS');

        // Reset flag after a short delay to allow next valid trigger
        setTimeout(() => (lastFired = false), 100);
      }
    };

    document.addEventListener('visibilitychange', onVisibilityOrFocus);
    window.addEventListener('focus', onVisibilityOrFocus);

    return () => {
      document.removeEventListener('visibilitychange', onVisibilityOrFocus);
      window.removeEventListener('focus', onVisibilityOrFocus);
    };
  };

  // Holds all the cleanup handlers
  const cleanupHandlers: VoidFunction[] = [];

  cleanupHandlers.push(setupUrlUpdateHandler());
  cleanupHandlers.push(setupOpenUrlHandler());
  cleanupHandlers.push(watchForAppVisibilityChange());

  return () => cleanupHandlers.forEach((offHandler) => offHandler());
};

// Utils

/**
 * Create a message handler for the iframe
 */
const createMessageHandler =
  <T extends Record<string, MESSAGE_HANDLER>>(window: Window, origin: string) =>
  <EVENT_NAME extends Extract<keyof T, string>>(
    eventName: EVENT_NAME,
    handler: T[EVENT_NAME],
  ): VoidFunction => {
    const messageHandler = (event: MessageEvent) => {
      if (event.origin !== origin) return;

      const { eventName: incomingEventName, args } = event.data;

      if (eventName === incomingEventName) {
        handler(...args);
      }
    };

    window.addEventListener('message', messageHandler);

    return () => {
      window.removeEventListener('message', messageHandler);
    };
  };

/**
 * Create a function to send messages to the iframe
 */
const createMessageSender =
  <T extends Record<string, MESSAGE_HANDLER>>(iframe: HTMLIFrameElement) =>
  <EVENT_NAME extends Extract<keyof T, string>>(
    eventName: EVENT_NAME,
    ...args: Parameters<T[EVENT_NAME]>
  ) => {
    iframe.contentWindow?.postMessage(
      { args, eventName },
      new URL(iframe.src).origin,
    );
  };

Now you can import and use the setupIframe function in your iframe application:

import { setupIframe } from './iframe-setup';

const iframeURL = new URL('https://iframe.com');

if (typeof window !== 'undefined') {
  iframeURL.searchParams.set('initial-parent-url', window.location.href);
}

const App = () => {
  const iframeRef = useRef<HTMLIFrameElement>(null);

  useEffect(() => {
    const cleanUp = setupIframe(iframeRef.current!, iframeURL.origin);

    return () => {
      cleanUp();
    };
  }, []);

  return (
    <div>
      <iframe ref={iframeRef} src={iframeURL.toString()} />
    </div>
  );
};

Readme

Keywords

none

Package Sidebar

Install

npm i @dynamic-labs/iframe-setup

Weekly Downloads

166

Version

4.18.3

License

MIT

Unpacked Size

458 kB

Total Files

13

Last publish

Collaborators

  • packaging-at-dynamic-labs