npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zero2x/sdk

v0.2.0

Published

Generic typed parent–iframe messaging SDK with origin validation

Readme

@zero2x/sdk

通用的父页面与内嵌 iframe 双向通信 SDK。调用方定义消息名与 payload 类型,SDK 负责握手、 来源校验、消息分发和资源释放,不规定业务通信类型。

npm 包 · 当前版本:0.2.0

无需框架或运行时依赖,可用于原生 JavaScript、React、Vue 等浏览器项目。包提供 ESM、CommonJS 和 TypeScript 类型声明。

安装

父页面和 iframe 页面都需要安装:

npm install @zero2x/sdk
# 或
pnpm add @zero2x/sdk

定义消息

在接入项目中共享类型定义;消息名可以是任意非空字符串:

export interface HostMessages {
  configure: { locale: string; theme: 'light' | 'dark' };
}

export interface ChildMessages {
  resize: { height: number };
  completed: { id: string };
}

第一个泛型定义发送的消息,第二个泛型定义接收的消息。只传一个泛型时,双向使用同一映射。 传入泛型后,send 与 on 根据消息名推断 payload,并检查消息名及参数类型。

父页面接入

import { createParentClient } from '@zero2x/sdk/parent';
import type { HostMessages, ChildMessages } from './message-types';

const iframe = document.querySelector<HTMLIFrameElement>('#embedded-frame');
if (!iframe) throw new Error('iframe not found');

const client = createParentClient<HostMessages, ChildMessages>({
  iframe,
  targetOrigin: 'https://embedded.example.com',
});

const unsubscribe = client.on('resize', ({ height }) => {
  iframe.style.height = `${height}px`;
});

await client.connect();
client.send('configure', { locale: 'zh-CN', theme: 'light' });

// 不再使用时调用;不要在刚建立连接后立即销毁。
// unsubscribe();
// client.destroy();

iframe 页面接入

import { createIframeClient } from '@zero2x/sdk/iframe';
import type { HostMessages, ChildMessages } from './message-types';

// 子页面的发送与接收方向和父页面相反。
const client = createIframeClient<ChildMessages, HostMessages>({
  parentOrigin: 'https://host.example.com',
});

const unsubscribe = client.on('configure', ({ locale, theme }) => {
  document.documentElement.lang = locale;
  document.documentElement.dataset.theme = theme;
});

await client.ready();
client.send('resize', { height: document.documentElement.scrollHeight });

// 不再使用时调用:unsubscribe(); client.destroy();

不指定泛型

不指定泛型时,可以发送任意非空消息名与 payload;接收的 payload 为 unknown,由调用方校验后使用:

const client = createParentClient({ iframe, targetOrigin });
client.on('custom.event', (payload) => {
  // payload: unknown,在这里执行调用方的业务校验。
});
await client.connect();
client.send('custom.event', { value: 1 });

泛型只提供编译期检查,不验证远端数据。payload 可以是对象、数组、字符串、数字、null 或 undefined 等浏览器 structured clone 支持的数据;函数等不可克隆内容会抛出 SDKError(SERIALIZATION_FAILED)。SDK 不提供 RPC、消息持久化或送达确认;发送成功仅表示 postMessage 调用成功。

从 0.1.0 升级

  • 不再自动合并内置 USER_INFO 消息,也不根据消息名强制校验 token + lang。
  • SDK_MESSAGE_TYPE、SDKUserInfo、BuiltinMessages 和 isSDKUserInfo 保留为可选的兼容导出。
  • 旧调用没有泛型时仍可发送用户信息。需要旧类型约束时显式使用 BuiltinMessages:
import { type BuiltinMessages, isSDKUserInfo, SDK_MESSAGE_TYPE } from '@zero2x/sdk';

const client = createParentClient<BuiltinMessages>({ iframe, targetOrigin });
await client.connect();
client.send(SDK_MESSAGE_TYPE.USER_INFO, { token: '', lang: 'zh-CN' });

// 接收端需要运行时校验时,在自己的回调里调用 isSDKUserInfo(payload)。

原先使用自定义映射且依赖内置消息的调用,需要改为 createParentClient<BuiltinMessages & CustomMessages>(options);iframe 同理。 通信信封与握手版本保持不变;两端沿用原 USER_INFO 数据时可以与旧客户端通信,但旧客户端 仍会拒绝该消息名下不符合旧结构的数据。

API

| 工厂 | 说明 | | ------------------------------------------------- | ------------- | | createParentClient<Outgoing, Incoming>(options) | 父页面客户端 | | createIframeClient<Outgoing, Incoming>(options) | iframe 客户端 |

| 选项 | 适用端 | 类型 | 必填 | 说明 | | ---------------- | ------ | ------------------- | ---- | ---------------------------- | | iframe | 父页面 | HTMLIFrameElement | 是 | 要通信的 iframe 元素 | | targetOrigin | 父页面 | string | 是 | iframe 的精确 origin | | parentOrigin | iframe | string | 是 | 父页面的精确 origin | | connectTimeout | 两端 | number | 否 | 握手超时毫秒数,默认 10000 |

| 方法 | 说明 | | --------------------- | ---------------------------------------------- | | connect() | 父页面等待 iframe 完成握手 | | ready() | iframe 注册监听器并与父页面完成握手 | | send(type, payload) | 向对端发送消息;必须先等待握手完成 | | on(type, listener) | 订阅对端消息,返回取消订阅函数;可在握手前调用 | | destroy() | 释放监听器、定时器和消息订阅者 |

错误处理

握手超时或连接被销毁时,connect() / ready() 的 Promise 会被拒绝;未连接就发送、 消息名为空或 payload 无法克隆时,send() 会同步抛出 SDKError。调用方负责处理这些错误:

import { SDKError } from '@zero2x/sdk';

try {
  await client.connect();
  client.send('configure', { locale: 'zh-CN', theme: 'light' });
} catch (error) {
  if (error instanceof SDKError) {
    console.error('iframe 通信失败:', error.code, error.message);
  } else {
    throw error;
  }
}

常见错误码:INVALID_CONFIG、CONNECT_TIMEOUT、NOT_CONNECTED、INVALID_MESSAGE、 SERIALIZATION_FAILED 和 DESTROYED。订阅回调中的业务异常由调用方自行处理。

来源与生命周期

  • 两端必须配置精确的 HTTP/HTTPS origin,例如 https://embedded.example.com;不允许 * 或带路径的 URL。
  • 不要从不可信查询参数直接读取 origin;SDK 同时校验消息的 origin、来源窗口及当前连接 ID。
  • 页面卸载或 iframe 永久移除时调用 destroy(),不再订阅时调用 unsubscribe()。
  • 泛型与来源校验不能替代业务数据校验、身份认证和权限判断。

SDK 不在导入时访问 window 或注册监听器,可以被 SSR 工具链解析;实例只能在浏览器中创建。 子页面客户端面向内嵌 iframe,当前不覆盖 popup/opener 场景。

手工验收

  1. 父页面和 iframe 分别按上述示例配置精确 origin,交换双向泛型;握手后发送 configure、 resize,预期两端各收到对应 payload。
  2. 在同一映射中分别用对象、数组和 null 作为 payload,预期原样接收;业务校验由调用方处理。
  3. 从其他窗口或其他 origin 发送相同信封,预期订阅回调不被触发。
  4. 取消订阅或销毁客户端后再次发送,预期回调不再执行;重建 iframe 客户端并重新握手后可继续通信。

维护者发布

在 monorepo 根目录执行:

# 先更新 package.json 版本,以及本 README 和 CHANGELOG。
npm login --registry=https://registry.npmjs.org/
pnpm publish:sdk

# 只执行包级验证、构建并预览发布内容,不上传 npm。
pnpm publish:sdk --dry-run

脚本复用 SDK 的 prepublishOnly 完成检查、测试与构建,发布到公开 npm registry 的 latest 标签。身份检查或任一步骤失败时立即停止;不会自动修改版本、提交或推送 Git。

License

MIT