JavaScript Bridge preview
The Hermes interop bridge provides bidirectional communication between your JavaScript frontend and C# backend. It works with any framework — React, Vue, Svelte, Angular, or vanilla JS.
Overview
The bridge uses a JSON-based protocol over the native WebView message channel. From JavaScript, you call C# methods and receive results as promises. From C#, you can push events to the frontend. No HTTP server, no WebSocket — messages flow directly through the WebView's native messaging API.
Installation
Install the bridge package in your frontend project:
npm install @hermes/bridgeInvoking C# Methods
Registering Handlers (C#)
Register methods in your Program.cs using UseInteropBridge:
builder.UseInteropBridge(bridge =>
{
// No arguments, returns a value
bridge.Register("getRuntime", () => $".NET {Environment.Version}");
// One argument, returns a value
bridge.Register<string, string>("greet", name => $"Hello, {name}!");
// Async handler
bridge.RegisterAsync<string, string>("fetchData", async (query) =>
{
var result = await _dataService.SearchAsync(query);
return result;
});
});Calling from JavaScript
Use bridge.invoke() to call registered C# methods. It returns a Promise that resolves with the result or rejects on error:
import { bridge } from '@hermes/bridge';
// Simple call
const runtime = await bridge.invoke<string>('getRuntime');
// With arguments
const greeting = await bridge.invoke<string>('greet', 'World');
// With timeout
const data = await bridge.invoke<SearchResult>(
'fetchData',
{ timeout: 5000 },
'search query'
);Error Handling
If the C# handler throws an exception, the promise rejects with an Error containing the exception message:
try {
const result = await bridge.invoke<string>('riskyMethod');
} catch (err) {
console.error('C# method failed:', err.message);
}Events
Events provide fire-and-forget messaging in both directions.
JavaScript to C# Events
Send events from JavaScript:
bridge.send('user-action', { action: 'clicked', target: 'save-button' });Handle them in C#:
builder.UseInteropBridge(bridge =>
{
bridge.On<UserAction>("user-action", action =>
{
Console.WriteLine($"User clicked: {action.Target}");
});
});C# to JavaScript Events
Listen for events in JavaScript:
const unsubscribe = bridge.on<ProgressData>('download-progress', (data) => {
console.log(`Progress: ${data.percent}%`);
});
// Clean up when done
unsubscribe();Environment Detection
Check if your code is running inside a Hermes desktop window:
if (bridge.isHermes) {
// Running in Hermes — native features available
const runtime = await bridge.invoke<string>('getRuntime');
} else {
// Running in a browser — fall back gracefully
}This is useful for apps that need to work both as a desktop app and in a browser.
Protocol Reference
The bridge uses JSON envelopes over the WebView's native window.external channel:
| Direction | Type | Shape |
|---|---|---|
| JS → C# | Invoke | {"type":"invoke","id":"...","method":"greet","args":["World"]} |
| C# → JS | Result | {"type":"result","id":"...","value":"Hello, World!"} |
| C# → JS | Error | {"type":"error","id":"...","message":"..."} |
| Either | Event | {"type":"event","name":"...","data":{...}} |
Each invoke gets a unique id, and the corresponding result or error carries the same id so the bridge can match responses to promises.
Registration API Reference
| C# Method | Signature | Description |
|---|---|---|
Register | (string, Func<object?>) | No-arg handler returning a value |
Register<TResult> | (string, Func<TResult>) | Typed no-arg handler |
Register<TArg, TResult> | (string, Func<TArg, TResult>) | Single-arg typed handler |
RegisterAsync<TResult> | (string, Func<Task<TResult>>) | Async no-arg handler |
RegisterAsync<TArg, TResult> | (string, Func<TArg, Task<TResult>>) | Async single-arg handler |
On | (string, Action) | Event listener (no data) |
On<T> | (string, Action<T>) | Typed event listener |
Next Steps
- React Integration — React hooks for the bridge
- Web Quick Start — Getting started with Hermes.Web
