JavaScript Bridge Guide

The JavaScript bridge enables bidirectional communication between your Unity C# code and JavaScript running inside the webview.

Use cases:

  • Calling C# functions from web pages
  • Pushing data and events from Unity to web pages
  • Building hybrid apps with web-based UI and native Unity rendering
  • Embedding interactive web dashboards inside your game

Enabling the Bridge

In the Inspector, enable Bridge Enabled on the LightweightBrowser component. This:

  1. Wraps the native webview in a BridgedWebView decorator
  2. Creates a BridgeEventBus for message pub/sub
  3. Creates a JavaScriptBridge for structured communication
  4. Auto-injects the window.unityBridge JS API on every page load

Architecture Overview

┌────────────────────┐          ┌──────────────────────┐
│   Unity C#         │          │   Web Page (JS)       │
│                    │          │                       │
│  JavaScriptBridge  │◄────────►│  window.unityBridge   │
│  BridgeEventBus    │          │                       │
│                    │  JSON    │                       │
│  Your MonoBehaviour│ envelope │  Your JavaScript      │
└────────────────────┘          └──────────────────────┘

All messages use a single JSON envelope format:

{
    "id": "uuid",
    "type": "command|event|call|response|error",
    "name": "kebab-case-name",
    "payload": "json-string-or-null",
    "callbackId": "uuid-or-null"
}

JavaScript API (window.unityBridge)

The bridge is automatically injected into every page. Available after DOMContentLoaded.

Send an Event (JS → C#)

Fire-and-forget notification from JavaScript to C#.

// Simple event
window.unityBridge.send('button-clicked');

// Event with data
window.unityBridge.send('form-submitted', {
    name: 'John',
    email: 'john@example.com'
});

Call a C# Handler (JS → C#, with response)

Request-response pattern. Returns a Promise.

// Call a registered C# handler and await the response
const result = await window.unityBridge.call('getPlayerStats');
console.log(result); // { health: 100, score: 42 }

// With parameters
const item = await window.unityBridge.call('getInventoryItem', { slot: 3 });

Listen for Events from C# (C# → JS)

// Subscribe
window.unityBridge.on('score-updated', (data) => {
    document.getElementById('score').textContent = data.score;
});

// Unsubscribe
function myHandler(data) { /* ... */ }
window.unityBridge.on('health-changed', myHandler);
window.unityBridge.off('health-changed', myHandler);

Check Bridge Availability

if (window.unityBridge) {
    window.unityBridge.send('app-ready');
} else {
    console.log('Unity bridge not available — running in regular browser');
}

C# API

Register a Handler

Register a function that JavaScript can call with window.unityBridge.call().

using Hexora.LightweightBrowser;

public class GameBridge : MonoBehaviour
{
    [SerializeField] private LightweightBrowser _browser;

    void Start()
    {
        _browser.JavaScript.RegisterHandler("getPlayerStats", data =>
        {
            var stats = new PlayerStats { health = player.Health, score = player.Score };
            return JsonUtility.ToJson(stats);
        });

        _browser.JavaScript.RegisterHandler("buyItem", data =>
        {
            var request = JsonUtility.FromJson<BuyRequest>(data);
            bool success = shop.Purchase(request.itemId);
            return JsonUtility.ToJson(new { success });
        });
    }

    void OnDestroy()
    {
        _browser.JavaScript?.UnregisterHandler("getPlayerStats");
        _browser.JavaScript?.UnregisterHandler("buyItem");
    }
}

Push Events to JavaScript (C# → JS)

// Simple event
_browser.JavaScript.PushEvent("game-started");

// With data
_browser.JavaScript.PushEvent("score-updated",
    JsonUtility.ToJson(new { score = 42 }));

Subscribe to Events from JavaScript (JS → C# via EventBus)

void Start()
{
    // Subscribe with filter — only events from JavaScript
    _sub = _browser.Bridge.Subscribe(
        msg => HandleJSEvent(msg),
        BridgeMessageFilter.Create()
            .FromJavaScript()
            .EventsOnly()
            .Build()
    );

    // Subscribe to a specific named event with typed payload
    _scoreSub = _browser.Bridge.Subscribe<ScorePayload>(
        "submit-score",
        payload => SaveScore(payload.score, payload.playerName)
    );
}

void OnDestroy()
{
    _sub?.Dispose();
    _scoreSub?.Dispose();
}

Execute Raw JavaScript

// Fire and forget
_browser.JavaScript.ExecuteScript("document.title = 'Hello from Unity'");

// With result callback
_browser.JavaScript.ExecuteScript(
    "document.querySelectorAll('a').length",
    result => Debug.Log($"Found {result} links")
);

Complete Example: Score Dashboard

Unity Side (C#)

using UnityEngine;
using Hexora.LightweightBrowser;
using Hexora.LightweightBrowser.Bridge;

public class DashboardBridge : MonoBehaviour
{
    [SerializeField] private LightweightBrowser _browser;
    private int _score = 0;

    void Start()
    {
        // JS can ask for current score
        _browser.JavaScript.RegisterHandler("getScore", _ =>
            JsonUtility.ToJson(new ScoreData { score = _score })
        );

        // Listen for JS events
        _browser.Bridge.Subscribe(
            msg =>
            {
                if (msg.Envelope.Name == "player-name-set")
                {
                    var data = JsonUtility.FromJson<NameData>(msg.Envelope.Payload);
                    Debug.Log($"Player name: {data.name}");
                }
            },
            BridgeMessageFilter.Create().FromJavaScript().EventsOnly().Build()
        );
    }

    public void AddScore(int points)
    {
        _score += points;
        _browser.JavaScript.PushEvent("score-updated",
            JsonUtility.ToJson(new ScoreData { score = _score }));
    }

    [System.Serializable]
    struct ScoreData { public int score; }

    [System.Serializable]
    struct NameData { public string name; }
}

Web Side (HTML/JS)

<!DOCTYPE html>
<html>
<head><title>Score Dashboard</title></head>
<body>
    <h1>Score: <span id="score">0</span></h1>
    <input id="name" placeholder="Enter your name" />
    <button onclick="submitName()">Submit</button>

    <script>
        async function init() {
            if (!window.unityBridge) return;
            const data = await window.unityBridge.call('getScore');
            document.getElementById('score').textContent = data.score;
        }

        if (window.unityBridge) {
            window.unityBridge.on('score-updated', (data) => {
                document.getElementById('score').textContent = data.score;
            });
        }

        function submitName() {
            const name = document.getElementById('name').value;
            if (window.unityBridge) {
                window.unityBridge.send('player-name-set', { name });
            }
        }

        init();
    </script>
</body>
</html>

Message Flow Diagram

JS: window.unityBridge.call('getScore')
 │
 ▼
_postToNative(envelope)                    ← auto-detected channel
 │
 ▼
NativeMessageReceiver.OnNativeCallback()   ← MonoPInvokeCallback
 │
 ▼
MessageDispatcher.DispatchFromNative()      ← deserializes envelope
 │
 ├─► BridgeEventBus.Publish()              ← notifies all subscribers
 │
 └─► JavaScriptBridge.HandleCall()          ← looks up handler
      │
      ▼
      handler("getScore") returns JSON      ← your C# code
      │
      ▼
      SendToWebView(responseEnvelope)       ← sends response
      │
      ▼
      EvaluateJavaScript("_onMessageFromUnity(...)")
      │
      ▼
      Promise resolves in JS                ← caller gets result

Tips

  1. Bridge adds overhead — If you don’t need JS ↔ C# communication, leave Bridge Enabled unchecked.
  2. JSON payloads — Use JsonUtility.ToJson() / JsonUtility.FromJson<T>() on the C# side.
  3. Handler names — Use kebab-case: get-player-stats, buy-item, submit-score.
  4. Error handling — If a C# handler throws, the bridge sends an error envelope back to JS.
  5. Page reloads — The bridge script is re-injected on every PageFinished event. Re-register JS listeners on DOMContentLoaded.
  6. Debug overlay — Enable during development to see all bridge traffic. Toggle with F12.