Webview Bundle

Remote updates & local testing

Publish a new bundle version, the server contract, how clients pick it up, and how to run the whole loop locally.

This is the end-to-end guide to shipping bundle updates over the air: how you publish a new version, what the server contract is, how clients pick it up, and — importantly — how to run the whole loop locally before you involve a real server.

If you haven't yet, read Concepts for the meaning of bundle, source, manifest, channel, integrity, and signature.

The lifecycle at a glance

  developer machine / CI                         remote server                 end-user device
┌──────────────────────────┐               ┌──────────────────────┐        ┌──────────────────────┐
│ wvb pack ./dist          │               │                      │        │                      │
│   → app_1.1.0.wvb        │               │                      │        │  updater.getUpdate() │
│ wvb upload  ───────────────── upload ───▶ │  store version       │        │   isAvailable? ──┐   │
│   (+integrity +signature)│               │                      │        │                  │   │
│ wvb deploy  ───────────────── deploy  ──▶ │  mark deployed       │ ◀──────── HEAD /bundles/app  │
│   (--channel beta)       │               │  (per channel)       │ ───────▶ GET  /bundles/app   │
└──────────────────────────┘               └──────────────────────┘        │  verify + install ◀┘ │
                                                                            └──────────────────────┘
  1. Pack your build into a .wvb.
  2. Upload it to the server (optionally attaching an integrity hash and a signature).
  3. Deploy that version so clients on a given channel start seeing it.
  4. The client's updater checks for a newer deployed version, downloads it, verifies it, and installs it into the remote source — after which it's served instead of the builtin bundle.

Publishing from the command line

All publishing flows through the wvb CLI (@wvb/cli) and a wvb.config.ts. A minimal config that targets a real server:

// wvb.config.ts
import { defineConfig } from '@wvb/config';

export default defineConfig({
  pack: {
    srcDir: './dist',          // your build output
    outFile: 'app',            // → app.wvb (name from package.json if omitted)
  },
  remote: {
    endpoint: 'https://updates.example.com',
    bundleName: 'app',
    // version: () => readVersionSomehow(),   // defaults to package.json version
    uploader: /* a provider's uploader */,
    deployer: /* a provider's deployer */,
    // integrity: { algorithm: 'sha384' },     // attach an integrity hash (optional)
    // signature: { /* see below */ },          // attach a signature (optional)
  },
});

uploader and deployer come from a remote provider package — @wvb/remote-aws, @wvb/remote-cloudflare, or @wvb/remote-local for local testing (below). See the Configuration reference for every field.

Then, from your project:

wvb pack                       # build the .wvb from pack.srcDir
wvb upload app 1.1.0 --deploy  # upload + deploy in one step
# or, separately:
wvb upload app 1.1.0           # upload only
wvb deploy app 1.1.0           # make it the deployed version

wvb upload packs nothing by default — it uploads an existing .wvb (run wvb pack first, or pass --file). With --deploy it also deploys; add --channel beta to deploy to a channel. See the CLI reference for all flags.

The remote HTTP contract

A Webview Bundle server is any HTTP server that implements four endpoints. You can use a provider (AWS/Cloudflare/local) or build your own to this spec.

Method & pathPurpose
GET /bundlesList deployed bundles: [{ "name", "version" }]
HEAD /bundles/{name}Current version's metadata (headers only)
GET /bundles/{name}Download the current version
GET /bundles/{name}/{version}Download a specific version

Metadata travels in response headers:

  • Webview-Bundle-Name — bundle name (required)
  • Webview-Bundle-Version — version (required)
  • Webview-Bundle-Integrity — integrity hash (optional)
  • Webview-Bundle-Signature — signature (optional)
  • plus standard ETag / Last-Modified

Status codes: 404 when a bundle/version isn't deployed; 403 when fetching a specific non-deployed version is disallowed (the allowOtherVersions option). Downloads use Content-Type: application/webview-bundle. The full spec, including request/response examples, is in the wvb crate docs.

Channels (staged rollouts)

A channel routes different versions to different audiences. Deploy to one with --channel:

wvb upload app 1.2.0 --deploy --channel beta   # only beta clients see 1.2.0
wvb deploy app 1.2.0                            # later, promote to the default channel

Clients select a channel when checking for updates (e.g. updater_get_update with a channel-aware config, or Remote::list_bundles(Some(&"beta".into())) in Rust). No channel = the default channel.

Integrity and signatures

Attach verification material at upload time, and verify it at download time.

Publish side (wvb.config.ts): the uploader computes an integrity hash and/or signs the bundle.

remote: {
  endpoint: 'https://updates.example.com',
  uploader, deployer,
  integrity: { algorithm: 'sha384' },           // → "sha384:…"
  signature: {                                    // sign the integrity string
    algorithm: 'ecdsa',
    curve: 'p256',
    hash: 'sha256',
    key: { format: 'pkcs8', data: privateKeyDer },
  },
}

Skip them per-run with wvb upload --skip-integrity / --skip-signature.

Client side: the updater enforces an integrity policy and, if you configure a public key, a signature verifier. In Rust:

use wvb::integrity::{IntegrityChecker, IntegrityPolicy};
use wvb::signature::{Ed25519Verifier, SignatureVerifier};
use wvb::updater::{Updater, UpdaterConfig};
use std::sync::Arc;

let verifier = SignatureVerifier::Ed25519(Arc::new(
    Ed25519Verifier::from_public_key_pem(PUBLIC_KEY_PEM)?,
));
let config = UpdaterConfig::new()
    .integrity_policy(IntegrityPolicy::Strict)   // require & verify integrity
    .signature_verifier(verifier);               // require & verify signature
let updater = Updater::new(source, remote, Some(config));

The Electron and Tauri updaters expose the same options through their config; the Android/iOS bindings expose the IntegrityChecker/policy types directly.

How clients install an update

The high-level Updater ties a source to a remote and does check → download → verify → install:

let update = updater.get_update("app").await?;
if update.is_available {
    updater.download_update("app", None).await?; // verifies, then writes into the remote source
    // reload the webview to pick up the new version
}

download_update writes the verified bundle into the remote source directory and records it as the current version, so it's served on the next load. Each platform guide shows the idiomatic call: Electron, Tauri, Android, iOS.

Testing locally

You don't need a cloud account to exercise the full update loop. There are two local tools.

wvb remote local — a real local update server

@wvb/remote-local provides an uploader/deployer that write to a local directory (default ~/.wvb/local), and wvb remote local serves that directory over HTTP using the same contract a production server implements.

1. Point your config at the local provider:

// wvb.config.ts
import { defineConfig } from '@wvb/config';
import { localRemote } from '@wvb/remote-local';

const local = localRemote({ baseDir: '~/.wvb/local' }); // { uploader, deployer }

export default defineConfig({
  pack: { srcDir: './dist', outFile: 'app' },
  remote: {
    endpoint: 'http://localhost:4313',
    bundleName: 'app',
    uploader: local.uploader,
    deployer: local.deployer,
  },
});

2. Publish a version into the local store:

wvb pack
wvb upload app 1.1.0 --deploy   # writes app_1.1.0.wvb into ~/.wvb/local and deploys it

3. Start the local server:

wvb remote local                      # serves ~/.wvb/local on http://localhost:4313
# options: --base-dir ./.wvb/local  --port 4313  --allow-other-versions

4. Point your app's updater at it by setting its endpoint to http://localhost:4313 (updater: { remote: { endpoint: 'http://localhost:4313' } } in Electron, Remote::new(...) in Tauri, the remote URL in your Android/iOS updater). Now getUpdate / downloadUpdate hit your local server and install the bundle exactly as production would.

On the Android emulator, reach your host machine at http://10.0.2.2:4313 instead of localhost.

You can also verify the server directly with the CLI's client commands:

wvb remote list  --endpoint http://localhost:4313
wvb remote current app --endpoint http://localhost:4313
wvb download app --endpoint http://localhost:4313 --out /tmp/app.wvb

wvb serve — preview a single packed bundle

To just look at what's inside a .wvb (no update server, no manifest), serve its files over HTTP:

wvb serve ./app.wvb            # serves the bundle's files at http://localhost:4312
wvb serve ./app.wvb --port 8080

This is handy for confirming a pack produced the files and headers you expect, before you upload.

Provider packages

For real deployments, swap the local provider for a cloud one:

Each exposes a compatible uploader/deployer for wvb.config.ts and a server that implements the HTTP contract above. See their READMEs and the examples/ for provisioning with Pulumi.

On this page