Docs Bridge
Alpha documentation — APIs may change between 0.x releases.

Bridge protocol

Most applications use generated APIs and never construct protocol messages directly. This reference explains what crosses the WebView boundary and how the host rejects incompatible contracts.

Message round-trip

sequenceDiagram
    participant App as Web application
    participant SDK as Generated SDK
    participant Host as MAUI host
    participant Contract as Native contract

    App->>SDK: filesystem.readText({ path })
    SDK->>Host: request
    Host->>Contract: dispatch by contract and member
    Contract-->>Host: typed result
    Host-->>SDK: response
    SDK-->>App: Promise result

Event contracts and JavaScript contracts use the same bridge in the opposite direction.

Transport

JavaScript sends messages through the first available channel:

  1. Native message channel
    • Apple: window.webkit.messageHandlers.vidra.postMessage(frame)
    • Windows: window.chrome.webview.postMessage(frame)
  2. Custom-scheme fallback
    • vidra://bridge for native requests.
    • vidra://reverse for JavaScript-contract responses.

C# pushes messages through WebView JavaScript callbacks:

  • window.__vidra_callback(response) for native-contract responses.
  • window.__vidra_onevent(event) for event contracts.
  • window.__vidra_invoke(request) for JavaScript contracts.
  • window.__vidra_initialize(handshake) for startup negotiation.

Startup negotiation

After navigation, the host sends:

{
  "protocolVersion": 2,
  "coreFingerprint": "sha256",
  "appFingerprint": "sha256"
}

The SDK verifies the wire version and both generated manifest fingerprints before accepting traffic. The numeric wire version is an implementation compatibility field; there is only one supported public bridge protocol.

Native request

{
  "id": "req_1",
  "contract": "filesystem",
  "member": "readText",
  "payload": { "path": "/tmp/notes.txt" }
}
FieldTypeRequiredDescription
idstringyesCorrelation identifier
contractstringyesTarget native contract
memberstringyesNative member
payloadobjectnoTyped member payload

Response

{
  "id": "req_1",
  "success": true,
  "data": { "content": "hello" }
}
FieldTypeRequiredDescription
idstringyesMatching correlation identifier
successbooleanyesWhether the call succeeded
dataanynoTyped result on success
errorobjectno{ code, message } on failure

Event

{
  "contract": "connectivity",
  "member": "changed",
  "payload": { "access": "internet", "profiles": ["wifi"] }
}

Events have no correlation identifier or response.

JavaScript-contract request

{
  "id": "js_1",
  "contract": "counter",
  "member": "increment"
}

JavaScript sends the result back in a response envelope. The bridge has a 30-second timeout; caller cancellation may finish sooner. Neither path stops a JavaScript handler that is already running.

Error codes

CodeMeaning
PARSE_ERRORThe envelope could not be deserialized
NATIVE_CONTRACT_NOT_FOUNDNo native contract has the requested name
NATIVE_MEMBER_ERRORA native member rejected or failed the request
JS_HANDLER_NOT_FOUNDNo JavaScript handler is registered
JS_HANDLER_ERRORA JavaScript handler threw or rejected
JS_RESPONSE_INVALIDA JavaScript-contract response could not be decoded
BROWSER_ONLYA native contract was called outside the host

Generated APIs are manifest-backed and fingerprinted. Dynamic calls through vidra.unsafe and Bridge.Unsafe use the same envelopes but are not protected by generated contracts.