Skip to content

createNativeBridge

Creates a typed JavaScript bridge for WebKit message handlers. It can send fire-and-forget commands with call(), send typed request messages with request(), and resolve native responses through DOM events.

Each request gets a generated request ID and is stored until native code responds, the timeout expires, or the bridge is disposed. Instead of throwing for request failures, request() resolves to a discriminated BridgeResponse, so callers can handle success and failure with an explicit response.ok branch.

Importing

ts
import { createNativeBridge } from '@almighty-shogun/webkit-native-bridge'

Usage

The TypeScript side defines the bridge contract and sends requests through the configured WebKit message handler. The C++ side receives those messages, handles the native work, and dispatches a response event with the same request ID.

ts
import { createNativeBridge } from '@almighty-shogun/webkit-native-bridge'

type Requests = {
    getUser: {
        body: { id: string }
        response: { id: string; name: string }
        errorCode: 'USER_NOT_FOUND'
        errorDetails: { id: string }
    }
    ping: {
        body: void
        response: 'pong'
    }
}

type Commands = 'close' | 'openSettings';

const bridge = createNativeBridge<Requests, Commands>({
    handlerName: 'nativeBridge',
    requestTimeout: 15000
});

bridge.call('openSettings');

const response = await bridge.request('getUser', { id: '1' });

if (response.ok) {
    console.log(response.data.name);
} else {
    console.error(response.error.code, response.message);
}

const ping = await bridge.request('ping');

bridge.dispose();
cpp
// Sketch of the native-side flow. Exact WebKit APIs differ per platform.
void handleScriptMessage(const std::string& message) {
    // request:<requestId>|getUser|{"id":"1"}
    auto requestId = parseRequestId(message);
    auto method = parseMethod(message);

    if (method == "getUser") {
        dispatchBridgeResponse(requestId, true, R"({"id":"1","name":"Ada"})");
        return;
    }

    dispatchBridgeError(requestId, "UNKNOWN_METHOD", "Unsupported native method");
}

void dispatchBridgeResponse(
    const std::string& requestId,
    bool ok,
    const std::string& payloadJson
) {
    std::string script =
        "window.dispatchEvent(new CustomEvent('webkit-native-bridge', {"
        "detail: { requestId: '" + requestId + "', ok: true, payload: " + payloadJson + ", error: null }"
        "}))";

    webView.evaluateJavaScript(script);
}

Parameters

options.eventName?: string
DOM event name used for native responses.
Default: webkit-native-bridge

options.handlerName?: string
WebKit message handler name under window.webkit.messageHandlers.
Default: nativeBridge

options.requestTimeout?: number | null
Request timeout in milliseconds. Use null to disable request timeouts.
Default: 30000

options.window?: NativeBridgeWindow
Window-like object. Useful for tests or custom runtimes.

Returns

call(method: TCommands): void
Sends a fire-and-forget command string to native code.

request(method, body, options): Promise<BridgeResponse>
Sends a typed request and resolves to a BridgeResponse.

postMessage(message: string): void
Sends a raw message through the configured WebKit handler.

handleResponse(detail: NativeResponseEventDetail): void
Manually resolves a pending request from a native response detail.

isAvailable(): boolean
Returns whether the configured WebKit message handler exists.

dispose(): void
Removes the response listener and resolves pending requests as disposed failures.

Type signature

ts
declare function createNativeBridge<
    TRequests extends NativeBridgeRequestMap = Record<never, never>,
    TCommands extends string = never
>(options?: NativeBridgeOptions): NativeBridge<TRequests, TCommands>;

All packages are released under the MIT License.