# Bridge, Security, and Native Capabilities Use this when adding JavaScript-to-Zig calls, builtin commands, permissions, windows, child WebViews, dialogs, navigation policies, or external links. ## Bridge architecture JavaScript calls native Zig through: ```javascript const result = await window.zero.invoke("native.ping", { source: "webview" }); ``` The runtime: 1. Parses the JSON request. 2. Enforces message size limits. 3. Checks origin and permissions. 4. Looks up a registered handler. 5. Runs the handler and returns a JSON response. Bridge commands are default-deny. A command must be registered in Zig and allowed by policy. ## Handler pattern ```zig fn ping(context: *anyopaque, invocation: native_sdk.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}); } ``` Dispatcher pattern: ```zig fn bridge(self: *App) native_sdk.BridgeDispatcher { self.handlers = .{.{ .name = "native.ping", .context = self, .invoke_fn = ping }}; return .{ .policy = .{ .enabled = true, .commands = &policies }, .registry = .{ .handlers = &self.handlers }, }; } ``` When returning user-controlled strings, escape them: ```zig return native_sdk.bridge.writeJsonStringValue(output, user_name); ``` ## Size limits - Request message: 16 KiB. - Response: 16 KiB. - Handler result: 12 KiB. - Request ID: 64 bytes. - Command name: 128 bytes. For large data, do not force everything through one bridge response. Use native files/resources or chunking patterns. ## Security policy Core defaults: - No permissions granted unless listed. - Bridge commands denied unless policy allows them. - Navigation blocked unless origin is allowlisted. - External links denied unless configured. - Dialog builtin commands always denied unless explicitly listed in `builtin_bridge`. Manifest examples: ```zig .permissions = .{ "window" }, .capabilities = .{ "webview", "js_bridge" }, .bridge = .{ .commands = .{ .{ .name = "native.ping", .origins = .{ "zero://app" } }, }, }, .security = .{ .navigation = .{ .allowed_origins = .{ "zero://app", "http://127.0.0.1:5173" }, .external_links = .{ .action = "deny" }, }, }, ``` Prefer exact origins over `"*"`. Use `"*"` only for commands that expose no native state and only when the project already accepts that risk. ## Builtin commands The Native SDK includes builtin bridge commands for windows, layered WebViews, and dialogs. These are controlled separately from app-defined commands via `builtin_bridge`. Window commands: - `native-sdk.window.list` - `native-sdk.window.create` - `native-sdk.window.focus` - `native-sdk.window.close` Layered WebView commands: - `native-sdk.webview.create` - `native-sdk.webview.list` - `native-sdk.webview.setFrame` - `native-sdk.webview.navigate` - `native-sdk.webview.setZoom` - `native-sdk.webview.setLayer` - `native-sdk.webview.close` Dialog commands: - `native-sdk.dialog.openFile` - `native-sdk.dialog.saveFile` - `native-sdk.dialog.showMessage` Enable explicitly: ```zig const app_permissions = [_][]const u8{native_sdk.security.permission_window}; .security = .{ .permissions = &app_permissions, .navigation = .{ .allowed_origins = &.{ "zero://app" } }, }, .builtin_bridge = .{ .enabled = true, .commands = &.{ .{ .name = "native-sdk.window.create", .permissions = .{ "window" }, .origins = .{ "zero://app" } }, .{ .name = "native-sdk.webview.create", .permissions = .{ "window" }, .origins = .{ "zero://app" } }, .{ .name = "native-sdk.dialog.openFile", .origins = .{ "zero://app" } }, }, }, ``` `js_window_api = true` exposes `window.zero.windows.*` and `window.zero.webviews.*`, but it does not bypass origin or permission checks. ## Windows from JavaScript ```javascript const win = await window.zero.windows.create({ label: "tools", title: "Tools", width: 420, height: 320, }); const all = await window.zero.windows.list(); await window.zero.windows.focus(win.id); await window.zero.windows.close(win.id); ``` Window state persistence uses stable labels. Use meaningful labels like `main`, `settings`, `tools`, or `preview`. ## Layered WebViews Child WebViews are native WebViews layered inside a native window: ```javascript const preview = await window.zero.webviews.create({ label: "preview", url: "https://example.com", frame: { x: 24, y: 24, width: 480, height: 320 }, layer: 10, bridge: false, }); await preview.setZoom(1.25); await preview.setLayer(20); await preview.close(); ``` Rules: - WebView URLs must pass navigation policy. - Commands target only the calling native window. - `main` is reserved for the startup WebView. - Child WebViews receive `window.zero` only with `bridge: true`. - Backend gaps should reject with `invalid_request`. ## Dialogs Dialogs require explicit `builtin_bridge` policy. ```javascript const files = await window.zero.invoke("native-sdk.dialog.openFile", { title: "Select a file", defaultPath: "/home", allowMultiple: true, allowDirectories: false, }); const path = await window.zero.invoke("native-sdk.dialog.saveFile", { title: "Save as", defaultName: "untitled.txt", }); const result = await window.zero.invoke("native-sdk.dialog.showMessage", { style: "warning", title: "Confirm", message: "Delete this item?", primaryButton: "Delete", secondaryButton: "Cancel", }); ``` Use native dialogs for trusted app UI. Do not expose arbitrary filesystem access to remote or untrusted origins. ## Error handling JavaScript bridge calls reject with `error.code`: - `invalid_request`: malformed input, unsupported operation, denied navigation URL, missing target, duplicate/reserved label. - `unknown_command`: no registered handler. - `permission_denied`: origin or permission failed. - `handler_failed`: handler returned an error. - `payload_too_large`: request too large. - `internal_error`: unexpected runtime failure. Always handle errors in frontend code: ```javascript try { await window.zero.invoke("native.save", payload); } catch (error) { console.error(error.code, error.message); } ```