Talk to popups, child tabs, and detached windows like they were local functions. Register on one side, invoke from the other — with timeouts, abort signals, and a typed error hierarchy.
import { useIwpcWindow } from '@silurus/iwpc';
// Parent
const iwpc = useIwpcWindow();
const child = await iwpc?.open('./child');
const name = await child.invoke<void, string>('ASK_NAME');
// Child
iwpc?.register('ASK_NAME', () => prompt('What is your name?') ?? '');Each row is the same demo over two transports. The call shape is identical — the difference is what happens to your event loop.
Open the parent and child side by side and watch DevTools for verbose logs.
Each side increments the other. The simplest pattern: register a handler, invoke it from the other window. No return value.
Open demoSame demo. Child opens with noopener and runs on its own event loop — a busy parent never blocks the child or vice versa.
Open demoOpen a child window as a remote dialog and await a typed result — pick a color, confirm, or enter text. Shared event loop with the parent.
Open demoSame dialog flow, but with thread isolation: the popup runs on its own event loop so its UI stays responsive even when the parent is doing heavy work.
Open demoThe BroadcastChannel transport opens the child with noopener, so the two windows live on independent event loops. Heavy work in one window — a long parse, a layout thrash, a CPU spike — does not stall the other. The API surface is identical to the postMessage transport; reach for it when the popup needs to stay responsive regardless of what the parent is doing.