@dynamic-labs/iframe-setup

中文文档
5.16.1发布于 2026/11/13MITTypeScriptESM + CJS近 30 天下载 3029

Collection of utilities to use the Dynamic SDK in an iframe

原始使用文档
文档目录8 节
  1. Installation
  2. Features
  3. Implementation Guide
  4. Step 1: Parent Page Setup
  5. Step 2: Iframe Application Setup
  6. Enabling Social Redirect
  7. Alternative Implementation
  8. Manual Setup (Without Package Installation in Parent App)

@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.

Enabling Social Redirect

To enable social redirect functionality within your iframe application, you need to configure the redirectUrl setting in your DynamicContextProvider.

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

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

const App = () => {
  return (
    <DynamicContextProvider
      settings={{
        redirectUrl: PlatformService.getUrl().toString(),
        // other settings
      }}
    >
      {/* Your application */}
    </DynamicContextProvider>
  );
};

This configuration ensures that after social authentication, users are properly redirected back to your application and not the iframe URL.


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,
    iframe,
  );
  const messageSender = createMessageSender<IFRAME_EVENTS>(iframe);

  // Add event listener to handle deeplink
  const setupOpenUrlHandler = () =>
    messageHandler('OPEN_URL', ({ url, target = 'self', features }) => {
      // This navigates the host top window, so never honor a URL whose scheme
      // can execute script in the host origin (iframe->host XSS).
      if (hasDangerousScheme(url)) return;

      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 notifyUrlChange = () => {
      try {
        messageSender('UPDATE_PARENT_URL', { url: window.location.href });
      } catch {
        // Never let a notification failure surface to the host application
        // (e.g. an SPA router calling history.pushState)
      }
    };

    patchHistoryOnce();

    // popstate fires on browser back/forward navigation; the custom event
    // fires on programmatic pushState/replaceState (which never emit popstate)
    window.addEventListener('popstate', notifyUrlChange);
    window.addEventListener(URL_CHANGE_EVENT, notifyUrlChange);

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

  /**
   * 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

/**
 * URL schemes that can execute script in the host origin. Navigating the host
 * top window to any of these turns an OPEN_URL message into arbitrary code
 * execution in the integrator's origin, so they are blocked.
 *
 * A deny-list (rather than an http/https allow-list) is used deliberately:
 * wallet flows legitimately open custom-scheme deep links, and allow-listing
 * http(s) would break them.
 */
const DANGEROUS_SCHEMES = [
  'javascript:',
  'data:',
  'blob:',
  'vbscript:',
  'file:',
];

/**
 * Leading characters the browser ignores when resolving a URL scheme: spaces,
 * tabs, newlines and other C0 control chars (U+0000–U+0020). Stripping them
 * defeats obfuscation such as "\tjavascript:alert(1)".
 */
// eslint-disable-next-line no-control-regex
const LEADING_IGNORED_CHARS = /^[\u0000-\u0020]+/;

/**
 * Returns true when the URL begins with a script-executing scheme.
 *
 * Non-string input is treated as dangerous, leading ignored chars are stripped
 * first, and the comparison is case-insensitive ("JavaScript:" is blocked).
 */
const hasDangerousScheme = (url: unknown): boolean => {
  if (typeof url !== 'string') return true;

  const normalized = url.replace(LEADING_IGNORED_CHARS, '').toLowerCase();

  return DANGEROUS_SCHEMES.some((scheme) => normalized.startsWith(scheme));
};

/**
 * Custom event dispatched when the parent URL changes via the History API.
 * Used instead of re-patching history per setupIframe call so that multiple
 * instances can observe changes without clobbering each other's patches.
 */
const URL_CHANGE_EVENT = 'dynamic-labs:url-change';

const HISTORY_PATCHED_FLAG = '__dynamicLabsHistoryPatched';

/**
 * Patch history.pushState/replaceState to emit URL_CHANGE_EVENT.
 *
 * popstate does not fire for programmatic navigation, so we patch the History
 * API to broadcast a custom event instead. The patch is installed only once,
 * retains no references, and is intentionally never restored — it merely
 * dispatches an event, which keeps it safe to leave in place and composable
 * across multiple setupIframe instances.
 */
const patchHistoryOnce = (): void => {
  const history = window.history as History & Record<string, unknown>;

  if (history[HISTORY_PATCHED_FLAG]) return;
  history[HISTORY_PATCHED_FLAG] = true;

  const originalPushState = history.pushState.bind(history);
  const originalReplaceState = history.replaceState.bind(history);

  history.pushState = (...args: Parameters<History['pushState']>) => {
    originalPushState(...args);
    window.dispatchEvent(new Event(URL_CHANGE_EVENT));
  };

  history.replaceState = (...args: Parameters<History['replaceState']>) => {
    originalReplaceState(...args);
    window.dispatchEvent(new Event(URL_CHANGE_EVENT));
  };
};

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

      // Only trust messages coming from this iframe's own window. Checking the
      // origin alone lets any frame/tab at the SDK-iframe origin forge events.
      if (event.source !== iframe.contentWindow) 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>
  );
};

版本与发布记录

@dynamic-labs/iframe-setup 共发布 329 个版本,首次发布于 2025/03/07;过去 12 个月发布了 179 个版本。

版本发布日期距今
5.16.1latest2026/11/13今天
5.16.02026/11/112 天前
5.15.12026/11/103 天前
5.15.02026/11/103 天前
4.104.0v4-lts2026/11/0310 天前
5.14.02026/10/3014 天前

其他发布通道:alpha → 4.83.2-alpha.0,rc → 5.0.0-rc.4,v4-lts → 4.104.0。

关于 @dynamic-labs/iframe-setup 的常见问题

@dynamic-labs/iframe-setup 是什么?

Collection of utilities to use the Dynamic SDK in an iframe

如何安装 @dynamic-labs/iframe-setup?

在项目目录运行 npm install @dynamic-labs/iframe-setup,或使用 pnpm add / yarn add / bun add。

@dynamic-labs/iframe-setup 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@dynamic-labs/iframe-setup 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@dynamic-labs/iframe-setup 可以商用吗?

@dynamic-labs/iframe-setup 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@dynamic-labs/iframe-setup 的下载量有多少?

根据 npm 官方下载统计,@dynamic-labs/iframe-setup 在 2026/10/13 – 2026/11/11 这 30 天内被下载 3,029 次,日均约 101 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@dynamic-labs/iframe-setup 还在维护吗?

最新版本 5.16.1 发布于 2026/11/13(今天);至今共发布 329 个版本,其中过去 12 个月发布了 179 个;首次发布于 2025/03/07。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。