
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
postmessage-duplex
Advanced tools
Lightweight duplex communication library based on postMessage API. Supports iframe and Service Worker.
A lightweight, type-safe duplex communication library based on postMessage API. Supports both iframe and Service Worker communication scenarios.
基于 postMessage API 的轻量级、类型安全的双工通讯库。支持 iframe 和 Service Worker 两种通讯场景。
📖 在线文档 / Documentation · 🚀 快速开始 · 🎮 在线演示 · 📚 API 参考
| Feature | Native postMessage | postmessage-duplex |
|---|---|---|
| 请求-响应模式 | ❌ 需要手动实现 | ✅ 内置支持 |
| Promise 支持 | ❌ 回调模式 | ✅ async/await |
| 超时处理 | ❌ 需要手动实现 | ✅ 自动超时 |
| 消息队列 | ❌ 需要手动实现 | ✅ 自动队列 |
| 类型安全 | ❌ any 类型 | ✅ 完整类型定义 |
| Service Worker | ❌ API 不同 | ✅ 统一接口 |
npm install postmessage-duplex
# or
yarn add postmessage-duplex
# or
pnpm add postmessage-duplex
CDN:
<script src="https://unpkg.com/postmessage-duplex/dist/index.umd.js"></script>
<script>
const { IframeChannel, ServiceWorkerChannel } = window.PostMessageChannel
</script>
┌─────────────────────────────────────────────────────────────┐
│ Parent Page (父页面) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ const channel = new IframeChannel(iframe) │ │
│ │ channel.publish('getData', {id: 1}) ──────────────│────│───┐
│ │ channel.subscribe('notify', handler) <─────────────│────│───│─┐
│ └─────────────────────────────────────────────────────┘ │ │ │
│ ▲ │ │ │
│ │ iframe │ │ │
│ ┌───────────────────────┴─────────────────────────────┐ │ │ │
│ │ Child Page (子页面) │ │ │ │
│ │ const channel = new IframeChannel(parentOrigin) │ │ │ │
│ │ channel.subscribe('getData', handler) <───────────│────│───┘ │
│ │ channel.publish('notify', data) ──────────────────│────│─────┘
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Parent Page / 父页面:
import { IframeChannel } from 'postmessage-duplex'
const iframe = document.getElementById('my-iframe') as HTMLIFrameElement
const channel = new IframeChannel(iframe)
// 发送消息并等待响应
const response = await channel.publish('getUserInfo', { userId: 123 })
console.log('User info:', response.data)
// 监听子页面消息
channel.subscribe('notification', ({ data }) => {
console.log('Received:', data)
return { received: true } // 返回响应
})
Child Page / 子页面:
import { IframeChannel } from 'postmessage-duplex'
// 传入父页面的 origin
const channel = new IframeChannel('https://parent-domain.com')
// 监听父页面消息
channel.subscribe('getUserInfo', async ({ data }) => {
const user = await fetchUser(data.userId)
return user // 返回给父页面
})
// 向父页面发送消息
channel.publish('notification', { type: 'ready' })
Page Side / 页面端:
import { ServiceWorkerChannel } from 'postmessage-duplex'
// 等待 Service Worker 就绪
const channel = await ServiceWorkerChannel.createFromPage()
// 发送消息到 Service Worker
const response = await channel.publish('fetchData', { url: '/api/data' })
console.log('Data:', response.data)
// 监听 Service Worker 推送
channel.subscribe('push', ({ data }) => {
showNotification(data)
})
Service Worker Side (推荐使用全局路由):
// sw.js
import { ServiceWorkerChannel } from 'postmessage-duplex'
const channels = new Map()
// 共享的消息处理器
const subscribeMap = {
fetchData: async ({ data }) => {
const response = await fetch(data.url)
return await response.json()
}
}
// 启用全局路由 - 自动处理 SW 重启后的消息
ServiceWorkerChannel.enableGlobalRouting((clientId, event) => {
const channel = ServiceWorkerChannel.createFromWorker(clientId, { subscribeMap })
channels.set(clientId, channel)
channel.handleMessage(event) // 处理当前消息
})
💡 全局路由的优势: 当 Service Worker 重启后,客户端的消息可以被自动处理,无需重新连接。详见 Service Worker 指南。
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { IframeChannel } from 'postmessage-duplex'
const iframeRef = ref<HTMLIFrameElement>()
let channel: IframeChannel
onMounted(() => {
channel = new IframeChannel(iframeRef.value!)
channel.subscribe('childEvent', ({ data }) => {
console.log('From child:', data)
return { ok: true }
})
})
onUnmounted(() => {
channel?.destroy()
})
const sendToChild = async () => {
const res = await channel.publish('parentEvent', { msg: 'hello' })
console.log('Response:', res)
}
</script>
<template>
<iframe ref="iframeRef" src="./child.html" />
<button @click="sendToChild">Send</button>
</template>
import { useEffect, useRef } from 'react'
import { IframeChannel } from 'postmessage-duplex'
function ParentComponent() {
const iframeRef = useRef<HTMLIFrameElement>(null)
const channelRef = useRef<IframeChannel>()
useEffect(() => {
if (iframeRef.current) {
channelRef.current = new IframeChannel(iframeRef.current)
channelRef.current.subscribe('childEvent', ({ data }) => {
console.log('From child:', data)
return { ok: true }
})
}
return () => channelRef.current?.destroy()
}, [])
const sendToChild = async () => {
const res = await channelRef.current?.publish('parentEvent', { msg: 'hello' })
console.log('Response:', res)
}
return (
<>
<iframe ref={iframeRef} src="./child.html" />
<button onClick={sendToChild}>Send</button>
</>
)
}
// 创建通道
const channel = new IframeChannel(target, options?)
// target:
// - 父页面传入: HTMLIFrameElement
// - 子页面传入: string (父页面 origin,如 'https://parent.com')
// options:
interface ChannelOption {
timeout?: number // 超时时间,默认 5000ms
log?: Console // 自定义日志
subscribeMap?: Record<string, Function> // 预定义订阅
}
| Method | Return | Description |
|---|---|---|
publish(cmdname, data?) | Promise<PostResponse> | 发送消息并等待响应 |
subscribe(cmdname, callback) | this | 订阅消息 |
unSubscribe(cmdname) | this | 取消订阅 |
destroy() | void | 销毁通道 |
getTargetOrigin() | string | 获取目标 origin |
getTargetUrl() | string | 获取目标 URL |
| Property | Type | Description |
|---|---|---|
isReady | boolean | 通道是否就绪 |
isSon | boolean | 是否为子页面 |
// 页面端 - 推荐方式
const channel = await ServiceWorkerChannel.createFromPage(options?)
// 页面端 - 手动传入 ServiceWorker
const channel = new ServiceWorkerChannel(navigator.serviceWorker.controller, options?)
// Worker 端 - 从事件创建
const channel = ServiceWorkerChannel.createFromEvent(event, options?)
// Worker 端 - 从 clientId 创建
const channel = ServiceWorkerChannel.createFromWorker(clientId, options?)
interface PostResponse {
requestId?: string // 请求 ID
ret: ReturnCode // 返回码
data?: any // 响应数据
msg?: string // 错误信息
time?: number // 时间戳
}
import { ReturnCode } from 'postmessage-duplex'
ReturnCode.Success // 0: 成功
ReturnCode.ReceiverCallbackError // -1: 接收方回调错误
ReturnCode.SendCallbackError // -2: 发送方回调错误
ReturnCode.NoSubscribe // -3: 未订阅该事件
ReturnCode.TimeOut // -99: 请求超时
const response = await channel.publish('getData', { id: 1 })
if (response.ret === ReturnCode.Success) {
console.log('Success:', response.data)
} else if (response.ret === ReturnCode.TimeOut) {
console.error('Request timeout')
} else if (response.ret === ReturnCode.NoSubscribe) {
console.error('Event not subscribed on the other side')
} else {
console.error('Error:', response.msg)
}
const channel = new IframeChannel(iframe, {
timeout: 10000 // 10 秒超时
})
const channel = new IframeChannel(iframe, {
log: {
log: (...args) => console.log('[Channel]', ...args),
warn: (...args) => console.warn('[Channel]', ...args),
error: (...args) => console.error('[Channel]', ...args)
}
})
const channel = new IframeChannel(iframe, {
subscribeMap: {
'ping': () => ({ pong: true, time: Date.now() }),
'getVersion': () => ({ version: '1.0.0' })
}
})
import { enableDebugger } from 'postmessage-duplex'
// 在开发环境启用调试器
if (process.env.NODE_ENV === 'development') {
enableDebugger()
}
// 然后在浏览器控制台使用:
__POSTMESSAGE_DUPLEX__.debug.help() // 显示帮助
__POSTMESSAGE_DUPLEX__.debug.getChannels() // 查看所有通道
__POSTMESSAGE_DUPLEX__.debug.getHistory() // 查看消息历史
__POSTMESSAGE_DUPLEX__.debug.enableLiveLog(true) // 开启实时日志
__POSTMESSAGE_DUPLEX__.debug.getStats() // 查看统计信息
__POSTMESSAGE_DUPLEX__.debug.exportReport() // 导出调试报告
| Browser | Iframe | Service Worker |
|---|---|---|
| Chrome | ✅ 4+ | ✅ 40+ |
| Firefox | ✅ 3+ | ✅ 44+ |
| Safari | ✅ 4+ | ✅ 11.1+ |
| Edge | ✅ 12+ | ✅ 17+ |
| IE | ✅ 8+ | ❌ |
Q: 子页面刷新后连接会断开吗?
A: 不会。通道会自动处理子页面的重新加载,父页面无需重新创建通道。
Q: 可以同时与多个 iframe 通讯吗?
A: 可以。为每个 iframe 创建独立的 IframeChannel 实例即可。
Q: 跨域 iframe 可以通讯吗?
A: 可以,但子页面需要正确配置父页面的 origin。
Q: Service Worker 端如何使用这个库?
A: Service Worker 不支持直接 import,需要使用内联实现或打包工具。参考 demo 中的 sw.js 示例。
# 安装依赖
npm install
# 启动开发服务器
npm run dev
# 访问演示页面
open http://localhost:7100/demo/
| Script | Description |
|---|---|
npm run dev | 构建并启动开发服务器 |
npm run build | 构建生产版本 |
npm run build:watch | 监听模式构建 |
npm test | 运行测试 |
npm run test:coverage | 测试覆盖率 |
src/
├── index.ts # 入口,统一导出
├── base-channel.ts # 抽象基类
├── iframe-channel.ts # Iframe 通道
├── sw-channel.ts # Service Worker 通道
├── interface.ts # 类型定义
└── trace.ts # 消息追踪
demo/
├── iframe/ # Iframe 示例
├── service-worker/ # SW 示例
└── debugger/ # 调试工具
完整文档请访问:https://ljquan.github.io/postmessage-duplex/
| 文档 | 链接 |
|---|---|
| 快速开始 | Getting Started |
| Iframe 通讯指南 | Iframe Communication |
| Service Worker 指南 | Service Worker |
| TypeScript 支持 | TypeScript |
| 调试指南 | Debugging |
| API 参考 | API Reference |
| 在线演示 | Playground |
See CHANGELOG.md
欢迎提交 Issue 和 Pull Request!
Made with ❤️ by liquidliang
FAQs
Lightweight duplex communication library based on postMessage API. Supports iframe and Service Worker.
We found that postmessage-duplex demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.