@ liframe- setup
一个能使动态SDK(@distract-labs/sdk-react-core)在iframe环境中无缝工作的通用包.
安装
npm install @dynamic-labs/iframe-setup
特征
这个包提供了用于设置iframe环境的设置Iframe方法.
实施指南
步骤1: 父页面设置
配置包含 iframe 的母页 .
要设置 Iframe, 您需要使用 initial-parent-url 查询参数
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>
);
};
步骤2: Iframe 应用程序设置
在您的 Iframe 应用程序中, 初始化 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>
);
};
一旦两个步骤完成,动态SDK将在您iframe环境中全面运行.
促成社会调整
要在您的 iframe 应用程序中实现社会重定向功能, 您需要配置 redirectUrl 设置 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>
);
};
这个配置确保了在社交认证后,用户会被正确重定向回您的应用程序,而不是iframe URL.
备选实施
手动设置( 在父应用程序中不安装软件包)
如果您无法在父应用程序中安装软件包, 您可以直接执行 setupIframe 通过复制以下代码实现功能:
/**
* 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,
);
};
现在你可以导入并使用 setupIframe 函数
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>
);
};