open-tauri-remote-webview 0.1.0

A Tauri plugin that exposes the application’s UI to a web browser, allowing full interaction while the native app continues running. This enables frontend debug, end-to-end UI testing using existing web-based testing tools without requiring modifications to the app itself.
docs.rs failed to build open-tauri-remote-webview-0.1.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.

中文版

Open Tauri Remote WebView

A Tauri v2 plugin that exposes your application's UI to any web browser, enabling remote interaction, frontend debugging, and E2E testing using standard web tools.

This project is based on tauri-remote-ui v0.14.0 by DraviaVemal, modified under the MIT license. All modifications are Copyright (c) 2026 ant-cave.

Fork features: transparent @tauri-apps/api proxy over WebSocket — all native Tauri APIs (app, window, event, core, etc.) work in the browser without any import changes. Just add one side-effect import.

Original by DraviaVemal. Enhanced by ant-cave.


Features

Capability WebView (IPC) Browser (WS)
invoke (any command)
Event listen / once
@tauri-apps/api/app (getName, getVersion, …) ✅ via bridge
@tauri-apps/api/window (title, size, …) ✅ via bridge
@tauri-apps/api/event (listen, emit) ✅ via bridge
Rust emit → browser ✅ via EmitterExt
Custom security & origin control

Migration from Native Tauri

Migrating an existing Tauri app to use open-tauri-remote-webview requires only a few changes. Most of your frontend code stays exactly the same.

What changes

Area Before (native Tauri) After (remote)
Rust plugin Add open-tauri-remote-webview crate + .plugin(open_tauri_remote_webview::init())
Rust window.emit() use tauri::Emitter use open_tauri_remote_webview::EmitterExt (same call)
Rust start WS server Add app.start_remote_ui(RemoteUiConfig::default()) in setup
Frontend install npm install open-tauri-remote-webview
Frontend import import "..." from "@tauri-apps/api" No change — add ONE line: import "open-tauri-remote-webview/bridge-init" at entry
vite.config.ts Add /remote_ui_ws proxy to dev server
if (isTauri()) guards Common pattern Remove them — bridge makes everything work everywhere

Step-by-step

  1. Rust: cargo add open-tauri-remote-webview, then register the plugin and start the WS server in setup (see Usage > Rust side).
  2. Frontend: npm install open-tauri-remote-webview, then add import "open-tauri-remote-webview/bridge-init" at the top of your entry file (before any @tauri-apps/api import).
  3. Vite: Add the /remote_ui_ws proxy if you use Vite dev server (see Usage > Vite dev proxy).
  4. Clean up: Delete all isTauri() / isRunningInTauri() branches — the bridge transparently proxies IPC calls to WebSocket when running in a browser, and passes through to real Tauri IPC when in WebView.
  5. Optional: Replace use tauri::Emitter with use open_tauri_remote_webview::EmitterExt in your Rust code so events emitted from the backend also reach browser clients.

What stays the same

  • All @tauri-apps/api/* imports and usage
  • All Tauri command definitions on the Rust side
  • All frontend build tooling (Vite, Webpack, etc.)
  • All event listen/once/emit patterns
  • All window and app API calls

Caveats

  • Single-window mode only: getCurrentWindow() always returns with label "main"
  • No asset protocol: convertFileSrc() returns the path as-is; use raw URLs for assets
  • No __TAURI__ env: the bridge sets __TAURI_REMOTE_UI_SHIM__ instead — check this flag if you need to detect the shim

Usage

1. Rust side

cargo add open-tauri-remote-webview
use open_tauri_remote_webview::{RemoteUiConfig, RemoteUiExt};

tauri::Builder::default()
    .plugin(open_tauri_remote_webview::init())
    .setup(|app| {
        // Auto-start WebSocket server on launch
        let handle = app.handle().clone();
        tauri::async_runtime::spawn(async move {
            handle.start_remote_ui(
                RemoteUiConfig::default().set_port(Some(9090)),
            ).await.ok();
        });
        Ok(())
    })
    .invoke_handler(tauri::generate_handler![/* your commands */])
    .run(tauri::generate_context!())
    .expect("error running app");

If you use window.emit() in Rust, replace use tauri::Emitter with use open_tauri_remote_webview::EmitterExt so events also get forwarded to browser clients.

2. Frontend — zero-effort (recommended)

npm install open-tauri-remote-webview

In your app entry point (main.ts / main.js), add one line at the top:

// Auto-inject __TAURI_INTERNALS__ WS proxy — @tauri-apps/api works everywhere
import "open-tauri-remote-webview/bridge-init";

// Keep using @tauri-apps/api as-is — no import changes needed
import { invoke } from "@tauri-apps/api/core";
import { getName } from "@tauri-apps/api/app";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { listen } from "@tauri-apps/api/event";

That's it. All @tauri-apps/api calls automatically go through IPC in WebView and WebSocket in the browser — no if (isTauri()) branches needed.

3. Frontend — explicit API (alternative)

If you prefer explicit imports from this package instead of the transparent bridge:

import { invoke, setBaseUrl } from "open-tauri-remote-webview/api/core";
import { listen, once } from "open-tauri-remote-webview/api/event";

4. Vite dev proxy

// vite.config.ts
server: {
  proxy: {
    "/remote_ui_ws": {
      target: "ws://127.0.0.1:9090",
      ws: true,
    },
  },
},

5. Start / Stop server manually

async fn enable_server(app: AppHandle, port: u16) -> Result<String, String> {
    app.start_remote_ui(
        RemoteUiConfig::default().set_port(Some(port))
    ).await.map_err(|e| e.to_string())?;
    Ok(format!("Server started on {}", port))
}

async fn disable_server(app: AppHandle) -> Result<String, String> {
    app.stop_remote_ui().await.map_err(|e| e.to_string())?;
    Ok("Server stopped".to_string())
}

How the bridge works

The __TAURI_INTERNALS__ proxy shim (installTauriBridge) is injected when your app runs in a browser. It mimics the real Tauri runtime:

Browser (@tauri-apps/api/*)
  ↓
window.__TAURI_INTERNALS__.invoke(cmd, args)
  ├── "plugin:event|listen"  ──→  local WS event system (no round-trip)
  ├── "plugin:event|unlisten" ──→  remove local listener
  └── everything else         ──→  WS → Rust → WebView IPC → response
  • transformCallback / runCallback — managed locally in ShimCallbackManager
  • metadata — hardcoded to { label: "main" } (single-window mode)
  • convertFileSrc — returns path as-is (browser has no asset protocol)
  • plugins.path — inferred from navigator.platform
  • __TAURI_REMOTE_UI_SHIM__ flag — lets apps distinguish shim from real Tauri

Package exports

Import path What it provides
open-tauri-remote-webview/bridge-init Side-effect — auto-install bridge (recommended)
open-tauri-remote-webview/api/bridge { installTauriBridge } — manual install
open-tauri-remote-webview/api/core { invoke, setBaseUrl }
open-tauri-remote-webview/api/event { listen, once }
open-tauri-remote-webview Re-exports all of the above

Plugin Development

# Build Rust
cargo build

# Build JS
cd guest-js && npm run build

# Test app
cd test/vue-app && npm run dev

License

MIT — see LICENSE for the full text.

Based on tauri-remote-ui v0.14.0 Copyright (c) 2025 DraviaVemal, used under MIT.

Modifications and additions Copyright (c) 2026 ant-cave (antmmmmm@126.com / https://github.com/ant-cave).