Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/modernize-rpc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"duplex-message": patch
"simple-electron-ipc": patch
---

Upgrade the workspace to pnpm 12, TypeScript 7, Vite 8 and Vitest 5. Provide explicit ESM/CommonJS package entries while retaining legacy browser distribution filenames.

Clean up pending requests, heartbeat timers and unused Worker listeners; reject pending calls on destroy and serialization failures. Preserve caller-owned options and handlers, ignore malformed or mismatched responses, handle non-Error exceptions, recreate destroyed shared hubs, and deliver repeated storage progress updates without cleanup timers.
8 changes: 8 additions & 0 deletions .changeset/request-controls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"duplex-message": minor
"simple-electron-ipc": minor
---

Add optional local `AbortSignal` cancellation and total request deadlines using `requestTimeout`, with new `REQUEST_ABORTED` and `REQUEST_TIMEOUT` error codes. Defaults preserve unlimited handler execution. Caller controls are never serialized, and completed/failed/cancelled requests release their timers and abort listeners.

Mark heartbeats separately from business progress so new peers can deliver the legacy heartbeat string as ordinary progress. Continue accepting legacy heartbeats, and preserve the legacy heartbeat wire shape for older callers. Worker listeners are attached only for requests that will be sent.
8 changes: 8 additions & 0 deletions .changeset/review-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"duplex-message": major
"simple-electron-ipc": major
---

Published declarations now require TypeScript 4.1 or later for recursive RPC result inference; compilers predating 4.1 must be upgraded. Runtime emit signatures and the legacy wire protocol remain compatible. Pending calls now reject with UNKNOWN when their hub is destroyed instead of remaining unresolved; callers must handle rejected promises.

Preserve the legacy zero-heartbeat-timeout fallback and extensionless distribution imports. Node import/require share the same implementation and shared hubs; browser bundlers retain the ESM entry. Keep outgoing target origins independent of incoming messages, require an explicit synchronous true from Electron sender validators, safely convert unreadable handler exceptions, and consolidate pending request bookkeeping to reduce allocation and cleanup overhead.
6 changes: 6 additions & 0 deletions .changeset/runtime-performance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"duplex-message": patch
"simple-electron-ipc": patch
---

Preserve ES2018 syntax in published artifacts and verify that syntax after packing. Reduce Promise allocation, argument copying and repeated validation in the RPC path while retaining heartbeat, cleanup and origin/sender checks. Expand browser coverage to Chromium, Firefox and WebKit and real Electron checks to older Electron 31 and current Electron 44 across desktop platforms.
10 changes: 10 additions & 0 deletions .changeset/typed-ready-origins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"duplex-message": minor
"simple-electron-ipc": minor
---

Add typed RPC clients with method-specific arguments and inferred results, plus bounded, cancellable readiness probes using an explicit ready endpoint. Existing emit APIs and wire messages remain compatible.

Add configurable incoming Window origin restrictions, default outgoing target origins and origin-pinned replies. Existing origin behavior is preserved when no policy is configured. Add a fail-closed Electron sender/frame validator and re-export the shared RPC helpers from the Electron package.

Replace the Electron demo with a bundled sandboxed, isolated preload and a fixed contextBridge API. Add a real Electron smoke test to CI. Rewrite package documentation around runnable setup, progress, typed calls and safe integration; update npm descriptions, keywords, repository directories and the Electron package's workspace dependency range.
10 changes: 0 additions & 10 deletions .eslintignore

This file was deleted.

38 changes: 0 additions & 38 deletions .eslintrc.js

This file was deleted.

93 changes: 66 additions & 27 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,36 +1,75 @@
# This is a basic workflow to help you get started with Actions

name: CI

# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
branches: [ main ]
branches: [main]
pull_request:
branches: [ main ]
branches: [main]

permissions:
contents: read

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
check:
runs-on: ubuntu-latest

# Steps represent a sequence of tasks that will be executed as part of the job
strategy:
fail-fast: false
matrix:
node: [22, 24]
env:
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: 20
- name: Install pnpm
run: npm install -g pnpm

- name: install deps
run: pnpm install
- name: install webkit
run: cd packages/duplex-message && pnpm playwright install chromium
- name: build lib
run: pnpm build
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v5
with:
node-version: ${{ matrix.node }}
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm --filter duplex-message exec playwright install --with-deps chromium firefox webkit
- run: pnpm lint
- run: pnpm build
- run: pnpm typecheck
- run: pnpm test
- run: pnpm --filter duplex-message build:demo
- run: pnpm --filter simple-electron-ipc build:demo
- run: pnpm check:package
electron:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
electron: workspace
- os: ubuntu-latest
electron: '31.7.7'
- os: windows-latest
electron: workspace
- os: macos-latest
electron: workspace
env:
NODE_USE_ENV_PROXY: '1'
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v5
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- if: runner.os == 'Linux'
run: pnpm --filter duplex-message exec playwright install-deps chromium
- if: runner.os == 'Linux'
run: sudo apt-get install -y xvfb
- if: matrix.electron != 'workspace'
shell: bash
run: |
mkdir -p "$RUNNER_TEMP/electron-compat"
pnpm --dir "$RUNNER_TEMP/electron-compat" add electron@${{ matrix.electron }} --allow-build electron
- if: runner.os == 'Linux' && matrix.electron == 'workspace'
run: xvfb-run -a pnpm check:electron
- if: runner.os == 'Linux' && matrix.electron != 'workspace'
run: ELECTRON_SMOKE_PACKAGE_DIR="$RUNNER_TEMP/electron-compat" xvfb-run -a pnpm check:electron
- if: runner.os != 'Linux'
run: pnpm check:electron
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,5 @@ cache
test-cache
test/cache
**/dist
__screenshots__
__screenshots__
.vitest/
3 changes: 0 additions & 3 deletions .npmrc

This file was deleted.

13 changes: 13 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"ignorePatterns": ["**/dist/**", "**/coverage/**"],
"rules": {
"no-console": "off"
},
"overrides": [
{
"files": ["**/test/**", "**/demo/**"],
"rules": { "no-unused-vars": "off" }
}
]
}
118 changes: 96 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,96 @@
<h1 align="center">duplex-message</h1>
<div align="center">
<a href="https://github.com/oe/duplex-message/actions">
<img src="https://github.com/oe/duplex-message/actions/workflows/main.yml/badge.svg" alt="github actions">
</a>
<a href="#readme">
<img src="https://badgen.net/badge/Built%20With/TypeScript/blue" alt="code with typescript" height="20">
</a>
</div>


<h3 align="center">A tinny(~3kb) utility that can makes one way message responsible <br>
enhance postMessage/storageEvent/electron IPC/chrome extension scripts</h3>

## packages
1. For browser: check [Duplex-Message](https://github.com/oe/duplex-message/tree/main/packages/duplex-message)
2. For electron: check [Simple-Electron-IPC](https://github.com/oe/duplex-message/tree/main/packages/simple-electron-ipc)

## publish steps
1. `pnpm changeset` to create a changeset
2. `pnpm changeset version` to update the version
3. `pnpm publish -r` to publish the package
# duplex-message

**Promise-based RPC for iframes and Web Workers, with progress feedback.** The browser library shares one API across window messages, BroadcastChannel, storage events and page-script events. An Electron adapter uses the same request lifecycle.

[![CI](https://github.com/oe/duplex-message/actions/workflows/main.yml/badge.svg)](https://github.com/oe/duplex-message/actions)

Use it for file processing in Workers, embedded editor tasks, or other calls that need a result and progress updates. Typed clients check methods/arguments; optional deadlines and `AbortSignal` cancellation bound local waiting.

| Package | Use it for | Documentation |
| --- | --- | --- |
| [duplex-message](https://www.npmjs.com/package/duplex-message) | Iframes, Workers and same-origin browser tabs; no runtime dependencies | [Browser guide](packages/duplex-message/README.md) |
| [simple-electron-ipc](https://www.npmjs.com/package/simple-electron-ipc) | Bidirectional main ↔ isolated preload RPC | [Electron guide](packages/simple-electron-ipc/README.md) |

## Quick start

```sh
pnpm add duplex-message
# or: npm install duplex-message
```

**worker.ts**

```ts
import { PostMessageHub, READY_METHOD } from 'duplex-message'

const hub = new PostMessageHub()
hub.on(self, 'add', (a: number, b: number) => a + b)
hub.on(self, READY_METHOD, () => true)
```

**main.ts**

```ts
import { PostMessageHub, createRpcClient, waitForPeer } from 'duplex-message'

interface WorkerApi { add(a: number, b: number): number }
const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' })
const hub = new PostMessageHub({ requestTimeout: 5000 })
const rpc = createRpcClient<WorkerApi>((method, ...args) => hub.emit(worker, method, ...args))

try {
await waitForPeer(method => hub.emit(worker, method))
console.log(await rpc.call('add', 2, 3)) // 5
} finally {
hub.destroy()
worker.terminate()
}
```

This example uses module Workers with Vite. See the browser guide for [progress and cancellation](packages/duplex-message/README.md#progress-cancellation-and-deadlines), [iframe origin restrictions](packages/duplex-message/README.md#iframes-and-origin-restrictions), [transport selection](packages/duplex-message/README.md#choose-a-transport) and [comparisons](packages/duplex-message/README.md#when-to-choose-another-approach).

For Electron, keep Node integration disabled and context isolation enabled. The [runnable Electron demo](packages/simple-electron-ipc/demo/) uses a bundled sandboxed preload and exposes specific application methods through `contextBridge`.

## Development

Use Node.js 22.12+, 24, or 26+ and pnpm 12.8.1. The workspace pins TypeScript 7.0.2, Vite 8.3.2 and Vitest 5.0.3.

```sh
npm install -g pnpm@12.8.1
pnpm install --frozen-lockfile
pnpm --filter duplex-message exec playwright install --with-deps chromium firefox webkit
pnpm lint
pnpm build
pnpm typecheck
pnpm test
pnpm check:package
```

`pnpm test:unit` runs Node and mocked Electron IPC tests without a browser. `pnpm test` also exercises Chromium, Firefox and WebKit Workers, real cross-origin iframes, proxies, page-script events and storage messaging. CI checks Node 22 and 24, builds both demos, and validates packed CJS/ESM imports, declarations, UMD globals and tree shaking. Packed type checks include invalid methods/arguments and inferred return types.

```sh
pnpm --filter duplex-message dev
pnpm --filter duplex-message build:demo
pnpm --filter simple-electron-ipc build:demo
pnpm --filter simple-electron-ipc dev
pnpm check:electron
# Headless Linux: xvfb-run -a pnpm check:electron
```

The separate Electron smoke test launches an actual main/preload/renderer application and checks context isolation, restricted bridge methods, bidirectional RPC, progress, local cancellation and renderer controls. CI runs Electron 31 and 44 on Linux, and Electron 44 on Windows and macOS. Set `ELECTRON_SKIP_BINARY_DOWNLOAD=1` during installation if you only need library builds/unit tests; leave it unset for the Electron demo/smoke test.

Library output targets ES2018; packed JavaScript is checked against that syntax level. Transport APIs must exist in the runtime. `waitForPeer` and cancellation require `AbortController`/`AbortSignal`; ordinary calls do not. Legacy browser distribution filenames and the UMD global remain available; package imports use explicit `.mjs`/CommonJS entries. TypeScript emits declarations directly. Existing `emit` APIs remain available; new typed clients and ready endpoints are additive. Origin/sender filters are opt-in to preserve existing integrations; configure them explicitly for trusted peers.

## Releasing

```sh
pnpm changeset
pnpm changeset version
pnpm publish -r
```

Review changesets, validation and release versions before publishing. Cancellation ends local waiting; it does not stop peer-side work or undo effects.

## License

MIT. See [LICENSE](LICENSE).
Loading
Loading