Bridge

The bridge connects JavaScript in the WebView to native Zig handlers via JSON messages.

Architecture

WebView JS                       Zig Runtime
──────────                       ───────────
window.zero.invoke(cmd, payload)
        │                              │
        ├──── JSON message ───────────►│
        │                         Size check (16 KiB max)
        │                         Policy check (origin + permissions)
        │                         Handler lookup + execute
        │◄─── JSON response ──────────┤

Defining a handler

fn ping(context: *anyopaque, invocation: zero_native.bridge.Invocation, output: []u8) anyerror![]const u8 {
    _ = invocation;
    const self: *App = @ptrCast(@alignCast(context));
    self.ping_count += 1;
    return std.fmt.bufPrint(output, "{{\"message\":\"pong\",\"count\":{d}}}", .{self.ping_count});
}

The handler writes its JSON result into the provided output buffer (max 12 KiB) and returns a slice of it. Results must be valid JSON values; invalid raw text is rejected with handler_failed. When returning user data as a string, use the bridge helper so quotes and control characters are escaped:

return zero_native.bridge.writeJsonStringValue(output, user_supplied_name);

Wiring the dispatcher

fn bridge(self: *App) zero_native.BridgeDispatcher {
    self.handlers = .{.{ .name = "native.ping", .context = self, .invoke_fn = ping }};
    return .{
        .policy = .{ .enabled = true, .commands = &policies },
        .registry = .{ .handlers = &self.handlers },
    };
}

Calling from JavaScript

const result = await window.zero.invoke("native.ping", { source: "webview" });
console.log(result); // { message: "pong from Zig", count: 1 }

Invocation

When a handler is called, it receives an Invocation with:

  • request.id -- caller-provided request ID (max 64 bytes)
  • request.command -- command name (max 128 bytes, no / or spaces)
  • request.payload -- JSON payload string
  • source.origin -- origin of the requesting page (e.g. zero://app)
  • source.window_id -- which window sent the request

Size limits

ConstantValue
max_message_bytes16 KiB
max_response_bytes16 KiB
max_result_bytes12 KiB
max_id_bytes64
max_command_bytes128

Error codes

When a bridge call fails, the JS promise rejects with an error containing a code field:

CodeCause
invalid_requestMalformed JSON message
unknown_commandNo handler registered
permission_deniedOrigin or permission check failed
handler_failedHandler returned an error
payload_too_largeMessage exceeds 16 KiB
internal_errorUnexpected runtime error
try {
  const result = await window.zero.invoke("native.ping", {});
} catch (error) {
  console.error(error.code, error.message);
}

Bridge types

TypeDescription
BridgeDispatcherCombines policy and registry
BridgePolicyWhether the bridge is enabled and which commands are allowed
BridgeCommandPolicyPer-command: name, permissions, origins
BridgeRegistryMaps command names to handler functions
BridgeHandlername, context, invoke_fn

See also: Builtin Commands for zero-native.window.* and zero-native.dialog.*.