Message contract between room.html running inside a native embedding host (WebView2 on Windows, WKWebView on macOS, CEF, or an equivalent) and the host process. The host owns consent and OS-level input injection; the page owns WebRTC and signaling. When the page detects a host with the remote-control-target capability, it never grants control on its own — every allow/deny decision comes from the host, and every data-channel event is forwarded back to the host for the host's own event injection.
Deployed as:/js/room-native-bridge.js, loaded from room.html before conference.js. Detection: window.chrome.webview is present AND a hello arrives naming a supported capability set.
Overview
Every message crossing the bridge is a JSON object with two fixed top-level fields:
bridge: the string "codeb-native-v1". Any message missing this label or naming a different version is ignored by both sides.
type: the message kind. Kinds are enumerated in this document.
Transport:
Page → host:window.chrome.webview.postMessage(obj) on Windows WebView2. On other hosts the analogous native APIs are:
webkit.messageHandlers.codebNative.postMessage(obj) for WKWebView,
window.cefQuery({request: JSON.stringify(obj)}) for CEF.
The bridge script exposes CodebNativeBridge.postToHost(obj) that picks the right one at runtime.
Host → page:WebView2.CoreWebView2.PostWebMessageAsJson(jsonString) on Windows; equivalent on other hosts. The bridge script listens on window.chrome.webview.message and delivers to registered handlers via CodebNativeBridge.on(type, fn).
Threading: all handlers run on the page's main thread; the host must serialise its own dispatch.
Detection
On page load, room-native-bridge.js checks for window.chrome?.webview (or the platform equivalent) and, if present, listens for the host's hello message. The host is expected to send this before the page joins the room, ideally within 200 ms of DOM ready. When the hello arrives, the bridge fires a codeb-native-ready event on window with the negotiated capability set, and conference.js checks for it before wiring the in-page consent dialog.
If no hello arrives within 1000 ms, the page fires a codeb-native-absent window event and assumes a normal browser (in-page fallback, no host forwarding). Late arrivals after that deadline are rejected — a hello that lands after the page has committed to the plain-browser path would leave the two sides out of sync and is treated as an attempted downgrade.
Transport isolation. Exactly one inbound transport is installed per host, chosen by feature detection: WebView2 → window.chrome.webview.addEventListener('message'); WKWebView → the global __codebNativeInbound(json) (installed by the bridge as non-writable, non-configurable); CEF and Electron → the host installs its own inbound function via CodebNativeBridge.setInboundTransport(). There is no window.postMessage fallback: a page script cannot impersonate the host by posting a message to itself.
P→HPage → host messages
Sent by the page whenever a signaling event that the host cares about occurs. The host does not need to acknowledge; the page does not wait for a reply.
Sent once, right after welcome is received. Lets the host bind its UI to a specific peer and tenant.
control-request
{ "bridge":"codeb-native-v1", "type":"control-request",
"requestId":"req-1a2b3c", "from":"<peerId>", "name":"Alice Walker",
"verified":true, "verifiedIdentity":"alice@example.com",
"attest":"eyJhbGci...", "attestExpiresUtc":"2026-09-28T15:24:00Z",
"purpose":"You asked me for help with the printer setup",
"requestedFeatures":["pointer","keyboard","wheel"] }
The page forwards the WS control-request unchanged (server-stamped fields are preserved so the host can verify the attestation locally). The page also keeps a private requestId → pending map with a 60 s TTL; a later control-decision is routed by requestId, never by "whoever asked last".
The page refuses a NEW control-request while a session is already active. It does not surface such a request to the host; it answers the helper directly with a WS control-revoke (reason: "policy").
Sent immediately after the page has sent its WS control-grant in response to a host control-decision. The host needs the sessionId for later control-revoke and screen-info messages (both are ignored when the id does not match). Nothing else tells the host what session id the page picked, since hb events are not forwarded.
Every data-channel event of the active session is mirrored to the host. from comes from the RTCPeerConnection the event arrived on — never from the payload — so the host cannot be tricked by a spoofed event.from.
Sent after the page sees a control-revoke (WS or DC end), a data-channel close, or a PeerConnection close. The page has already released its local UI state; the host must do the same.
Fired when getDisplayMedia resolves or the underlying track ends. Lets the host draw the "you are sharing your screen" banner and stop-button. surface is the real value from the shared MediaStreamTrack.getSettings().displaySurface (monitor, window, or browser). width / height are the track's physical pixel dimensions so a host on a multi-monitor setup can identify which monitor is being shared.
Fired for every peer whose peer-left arrives on the WS, so the host can clean up its own per-peer UI state. If the departing peer was the target of an active control session, the page also sends control-ended with reason helper-disconnected or target-disconnected as appropriate.
Fired when the page's Stop-share button is pressed (mirrors the host's own Ctrl+Alt+F12). Lets the host clear its own state without racing the page.
H→PHost → page messages
Sent by the host to drive the page. The page validates every message: unknown bridge, missing required fields, or a message that arrives before hello is ignored.
First message. caps currently recognised: remote-control-target (the host owns consent and injection), share-indicator (the host draws the sharing banner), screen-info (the host provides multi-monitor metadata).
Consent verdict. On allow:true the page sends the WS control-grant (with the fields above) and opens the codeb-control-v1 RTCDataChannel. On allow:false the page sends a WS control-revoke with reason user-stopped and the request ends.
Host asks the page to end the session. The page sends a WS control-revoke, sends end on the DC, and closes the DC. The host is expected to have already released any pressed keys and mouse buttons.
Host announces the current shared surface and monitor topology. The page forwards this as a screen-info event on the codeb-control-v1 DC. Sent immediately after DC open and again whenever the surface changes.
Host asks the page to stop sharing its screen (Ctrl+Alt+F12 or a Stop button in the host UI). The page ends the local MediaStreamTrack. If a control session was active, it also revokes.
Security
Origin gating. The bridge script accepts host messages only when the page is loaded from a permitted origin (https://phone.codeb.io, https://phone.aloaha.com, or a host explicitly configured in the client bundle). Any other origin ignores every message.
No decision without hello.control-decision, control-revoke, screen-info, and stop-share are all ignored until a valid hello has been received in the same document.
Attestation is passed through unchanged. The page never edits the attest JWS or the identity fields before handing them to the host. The host verifies locally.
Bridge scope. Only the top document of the meeting page is a bridge peer. Frames and workers are not addressed.
No page-side grant. When a host hello with remote-control-target was received, the in-page consent dialog is disabled. The page never sends a WS control-grant without a preceding host control-decision.
Reference transcript
Windows tray host, dev tenant, single control session, target's perspective.
// Host launches WebView2, navigates to https://phone.codeb.io/room.html?room=hostinghelp&cam=off
// DOMContentLoaded fires; room-native-bridge.js runs
H → P { "bridge":"codeb-native-v1", "type":"hello",
"client":"AloahaRDPSystemTray/5.1.0.0",
"caps":["remote-control-target","share-indicator","screen-info"] }
// bridge fires window CustomEvent("codeb-native-ready"). conference.js
// suppresses its in-page consent dialog and joins the room.
P → H { "bridge":"codeb-native-v1", "type":"ready",
"room":"hostinghelp", "peerId":"dd44ee55ff66",
"protoVersion":"2", "tenant":"phone.codeb.io" }
// user opens getDisplayMedia (host's Share button, or the room UI):
P → H { "bridge":"codeb-native-v1", "type":"share-started",
"surface":"monitor", "width":2560, "height":1440 }
// helper Alice sends control-request on the WS. Page relays to host:
P → H { "bridge":"codeb-native-v1", "type":"control-request",
"requestId":"req-1a2b3c", "from":"aa11bb22cc33", "name":"Alice Walker",
"verified":true, "verifiedIdentity":"alice@example.com",
"attest":"eyJhbGci...", "attestExpiresUtc":"2026-09-28T15:24:00Z",
"purpose":"Printer setup", "requestedFeatures":["pointer","keyboard","wheel"] }
// host verifies attest, shows consent dialog, user clicks Allow:
H → P { "bridge":"codeb-native-v1", "type":"control-decision",
"requestId":"req-1a2b3c", "allow":true,
"grantedFeatures":["pointer","keyboard","wheel"],
"expiresUtc":"2026-09-28T16:20:00Z", "hbIntervalMs":5000 }
// page sends WS control-grant, opens codeb-control-v1 DC.
// host immediately announces multi-monitor topology:
H → P { "bridge":"codeb-native-v1", "type":"screen-info", "sessionId":"s7v8h1kw2q3r4p5m",
"surface":"monitor", "surfaceRect":{"x":0,"y":0,"w":2560,"h":1440},
"virtual":{"x":-1920,"y":0,"w":4480,"h":1440},
"monitors":[{"id":"\\\\.\\DISPLAY1","x":0,"y":0,"w":2560,"h":1440,"scale":1.25,"primary":true}] }
// page forwards as DC screen-info event. Alice starts sending pointer events:
P → H { "bridge":"codeb-native-v1", "type":"control-event",
"sessionId":"s7v8h1kw2q3r4p5m", "from":"aa11bb22cc33",
"event":{"s":"s7v8h1kw2q3r4p5m","seq":1,"t":"pointer","nx":0.4531,"ny":0.1093} }
// (host maps to desktop coords using screen-info and SendInputs...)
// user clicks Stop-share (Ctrl+Alt+F12):
H → P { "bridge":"codeb-native-v1", "type":"control-revoke",
"sessionId":"s7v8h1kw2q3r4p5m", "reason":"emergency" }
H → P { "bridge":"codeb-native-v1", "type":"stop-share" }
// page sends WS control-revoke, DC end, closes DC, ends the local share track:
P → H { "bridge":"codeb-native-v1", "type":"control-ended",
"sessionId":"s7v8h1kw2q3r4p5m", "reason":"emergency" }
P → H { "bridge":"codeb-native-v1", "type":"share-stopped" }
Helper JS API. For hosts that also act as the helper (drive another participant's screen), room.html exposes window.CodebControl with a full send API: sendPointer, sendButton, sendWheel, sendKey, sendClipRead, sendClipWrite. See the Helper JS API section of /remote_control_api.html for the full surface.