frame-bridge
@mhanzelka/react-frame-bridge

react-frame-bridge

React provider and hooks. Manages bridge lifecycle — opens on mount, closes on unmount.

Peer dependencies: react ≥ 19.0.0, react-dom ≥ 19.0.0, @mhanzelka/frame-bridge (installed separately).

BridgeProvider

Creates and owns the bridge instance. The bridge is created once on mount — changes tochannelName, role, or enabledTransports after mount are ignored. Remount the provider to apply new configuration.

import { BridgeProvider, IframeBridgeHost, useBridge } from "@mhanzelka/react-frame-bridge";

type Messages = { type: "ping" | "pong"; value: number };

// Parent page
function Parent() {
    return (
        <BridgeProvider<Messages>
            open={true}
            channelName="my-channel"
            role="parent"
            targetOrigin="https://child.example.com"
            enabledTransports={["post-message-channel"]}
        >
            <IframeBridgeHost
                src="https://child.example.com/app"
                targetOrigin="https://child.example.com"
                style={{ width: "100%", height: 500 }}
            />
            <Controls />
        </BridgeProvider>
    );
}

// Child iframe at child.example.com
function Child() {
    return (
        <BridgeProvider<Messages>
            open={true}
            channelName="my-channel"
            role="child"
            targetOrigin="https://parent.example.com"
            enabledTransports={["post-message-channel"]}
        >
            <Handler />
        </BridgeProvider>
    );
}
Prop / MethodTypeDefaultDescription
openbooleanSet to false to keep the bridge closed.
channelNamestringMust match on both sides.
role"parent" | "child"Role of this side.
enabledTransportsTransportType[]["post-message-channel"]Which transports to use.
targetOriginstringRequired for "post-message-channel".
prefixstringOptional prefix for the bridge ID.
options.observer(event) => voidCalled for every message sent/received.
bridgeOptionsCreateBridgeOptionsAdvanced options passed to createBridge.

useBridge<T>()

Returns the Bridge instance from the nearest BridgeProvider. Throws if used outside a provider.

function Controls() {
    const bridge = useBridge<Messages>();

    const ping = async () => {
        const reply = await bridge.send({ type: "ping", value: 1 });
        console.log(reply); // { type: "pong", value: 2 }
    };

    return <button onClick={ping}>Ping</button>;
}

Handling incoming messages

Use bridge.onMessage() inside a useEffect. The return value is the unsubscribe function.

function Handler() {
    const bridge = useBridge<Messages>();

    useEffect(() => {
        return bridge.onMessage(async (msg) => {
            if (msg.type === "ping")
                return { type: "pong", value: msg.value + 1 };
        });
    }, [bridge]);

    return null;
}

useBridgeState()

Subscribes to bridge state changes via useSyncExternalStore. Re-renders on every state update.

function StatusBar() {
    const state = useBridgeState();

    return (
        <div>
            {state.state === "open" && <span>● Connected</span>}
            {state.state === "closed" && <span>● Disconnected</span>}
            <span>Pending: {state.pendingCount}</span>
        </div>
    );
}

// state shape:
// state.state          → "open" | "closed" | "partially-open"
// state.pendingCount   → number of unanswered requests
// state.transports     → { [type]: { state, messageCount } }

IframeBridgeHost

Drop-in <iframe> wrapper that automatically sets contentWindow as the postMessage target on the parent bridge.

Prop / MethodTypeDefaultDescription
srcstringiframe URL.
targetOriginstringOrigin to use for postMessage.
...restIframeHTMLAttributesAll standard iframe attributes.

useBridgeWindow<T>()

Opens a popup window with a bridge connection. Tears down the bridge when the popup closes.

import { useBridgeWindow } from "@mhanzelka/react-frame-bridge";

function OpenPopupButton() {
    const { open } = useBridgeWindow<Messages>({
        onMessage: async (msg) => ({ ...msg, handled: true }),
        onBridgeReady: async (bridge) => {
            await bridge.send({ type: "init" });
        },
    });

    return (
        <button onClick={() => open({ url: "/popup", name: "settings" })}>
            Open Settings
        </button>
    );
}