tauri-plugin-hotswap-api
v0.0.4
Published
Open-source OTA frontend updates for Tauri v2 apps. Self-hosted, signed bundles, auto-rollback, runtime channels.
Downloads
634
Maintainers
Readme
What is this?
An open-source Tauri v2 plugin that pushes OTA frontend updates to users instantly — without rebuilding the native binary, without app store review, and without requiring a cloud service. Self-hosted, bring your own CDN.
It works by swapping Tauri's embedded asset provider at startup. The WebView keeps loading from tauri://localhost — the swap is invisible. Your keys, your server, your infrastructure. If anything goes wrong, the app rolls back to embedded assets on next launch.
Platform Support
| Platform | Supported | |----------|-----------| | macOS | ✅ | | Windows | ✅ | | Linux | ✅ | | Android | ✅ | | iOS | ✅ |
⚠️ App Store / Google Play note: OTA updates that swap frontend assets (HTML, CSS, JS) within a WebView are generally permitted, but policies can change. Review Apple's App Store Review Guidelines (3.3.2) and Google Play's Device and Network Abuse policy before shipping to ensure your use case complies with the latest rules.
How it works
flowchart TD
A["Your CDN / S3 / any HTTPS host
manifest.json
signed frontend.tar.gz"] -- "download + verify signature" --> B
B["Tauri App
HotswapAssets::get(key)
1. filesystem (cached)
2. embedded (fallback)"]🚀 Quickstart
1. Install
# src-tauri/Cargo.toml
[dependencies]
tauri-plugin-hotswap = "0.0.4"npm install tauri-plugin-hotswap-api2. Configure
Add to your tauri.conf.json:
{
"plugins": {
"hotswap": {
"endpoint": "https://your-server.com/api/updates/{{current_sequence}}",
"pubkey": "<YOUR_MINISIGN_PUBKEY>"
}
}
}Config source matters:
init(context)readsplugins.hotswapfromtauri.conf.jsonand requires it.init_with_config(context, config)andHotswapBuilderare programmatic paths;plugins.hotswapin JSON is optional for these.
3. Register the plugin
// src-tauri/src/lib.rs
pub fn run() {
let context = tauri::generate_context!();
// init() consumes the context to swap the asset provider,
// then returns the modified context alongside the plugin.
let (hotswap, context) = tauri_plugin_hotswap::init(context)
.expect("failed to initialize hotswap");
tauri::Builder::default()
.plugin(hotswap)
.run(context)
.expect("error running app");
}Programmatic alternative (no plugins.hotswap required in tauri.conf.json):
let context = tauri::generate_context!();
let config = tauri_plugin_hotswap::HotswapConfig::new("<YOUR_MINISIGN_PUBKEY>")
.endpoint("https://your-server.com/api/updates/{{current_sequence}}");
let (hotswap, context) = tauri_plugin_hotswap::init_with_config(context, config)
.expect("failed to initialize hotswap");4. Add capability
In src-tauri/capabilities/default.json:
{
"identifier": "default",
"windows": ["main"],
"permissions": [
"core:default",
"hotswap:default"
]
}5. Use from the frontend
import { checkUpdate, applyUpdate, notifyReady } from 'tauri-plugin-hotswap-api';
// ✅ Confirm current version works (call on every startup)
await notifyReady();
// 🔍 Check for updates
const result = await checkUpdate();
if (result.available) {
// ⬇️ Download, verify, and activate
await applyUpdate();
// 🔄 Reload to serve new assets
window.location.reload();
}That's it. A few lines to add OTA updates to your Tauri app.
You can also change configuration at runtime — for example, to switch channels without restarting:
import { configure } from 'tauri-plugin-hotswap-api';
// Switch to a beta channel at runtime
await configure({ channel: 'beta' });✨ Features
| Feature | Description |
|---------|-------------|
| 🔐 Signed bundles | Every download is verified with minisign before extraction |
| ↩️ Auto-rollback | If notifyReady() isn't called, the next launch rolls back automatically |
| 📡 Channels | Route users to production, staging, beta — switchable at runtime via configure() |
| 🔑 Custom headers | Auth tokens, API keys — sent on every check and download request |
| 🔄 Retry with backoff | Failed downloads retry automatically (1s → 2s → 4s → 8s) |
| 🔀 Download/activate split | Download now, apply later — you control the timing |
| 📊 Lifecycle events | hotswap://lifecycle events for telemetry (Sentry, PostHog, etc.) |
| 📏 Bundle size + mandatory flag | Warn users on mobile data, force security patches |
| 🌍 Platform-aware | Sends platform, arch, channel on every check request |
| 🛡️ Size limits | Configurable max bundle size prevents memory exhaustion |
| 🔒 HTTPS enforced | Non-HTTPS URLs rejected by default |
| ⚡ Atomic operations | Temp dir extraction + rename; temp file pointer writes |
| 🤖 Custom resolvers | HotswapResolver trait — bring your own update source |
| 📦 Zip support | Enable with features = ["zip"] |
📖 Documentation
| Document | Description | |----------|-------------| | Design Philosophy | Opinionated defaults, extensible when you need it | | Configuration | All config options, builder API, tauri.conf.json reference | | API Reference | Full JS and Rust API with examples | | Server Contract | What your update endpoint needs to return | | Security | Threat model, mitigations, signing guide | | Architecture | How the plugin works internally | | Creating Bundles | Build, sign, upload your frontend bundles | | CONTRIBUTING | How to contribute to this project | | CHANGELOG | Version history |
🛡️ Security
Every update is cryptographically signed with minisign and verified before extraction. The plugin is designed to fail safely — if anything goes wrong, the app falls back to embedded assets.
See the full Security documentation for the threat model and all mitigations.
License
MIT OR Apache-2.0 (same as Tauri)
