License setup — Capacitor
You added @dvai-bridge/capacitor plus a backend plugin like @dvai-bridge/capacitor-llama. You want to ship to the App Store and Play Store. Here's the licensing path.
TL;DR
Drop dvai-license.jwt into your www/ — or Vite's public/ — folder. It lands at /dvai-license.jwt inside the bundled webview assets.
The JS-side validator runs on DVAIBridge.start(...). It fetches the file via the Capacitor scheme, verifies the signature offline, and unlocks production behaviour. In Capacitor.DEBUG === true mode the SDK ignores license problems.
Where the file goes
On Capacitor, the JS-side validator does the verification. Same discovery rules as the web setup. The file ships inside the native app instead of from your origin.
- Vite / esbuild bundled webview — drop the file in
public/. Vite copies it todist/. Thennpx cap synccopiesdist/into the native projects. - Single-HTML / no bundler — drop it directly into
www/.
Verify it shipped:
- iOS — open the
.appbundle in Xcode, expandApp/public/, confirmdvai-license.jwtis there. - Android — open the APK with Android Studio's APK analyzer, look for
assets/public/dvai-license.jwt.
Alternative discovery — same as web:
- Inline JWT —
DVAIBridge.start({ licenseToken: "..." }). - Explicit URL —
DVAIBridge.start({ licenseKeyPath: "/path/in/webview.jwt" }).
Code: with vs. without
Default — license bundled in webview assets:
import { DVAIBridge } from "@dvai-bridge/capacitor";
const bound = await DVAIBridge.start({
backend: "llama",
modelPath: modelPath,
});
console.log(bound.baseUrl); // http://127.0.0.1:38883/v1
console.log(bound.licenseStatus); // { kind: "commercial", licensee: "Acme", ... }Inline JWT — downloaded into Capacitor Preferences:
import { Preferences } from "@capacitor/preferences";
const { value: token } = await Preferences.get({ key: "dvai_license_jwt" });
const bound = await DVAIBridge.start({
backend: "llama",
modelPath,
licenseToken: token!,
});What happens without a license
In release builds, start(...) rejects with a LicenseRequiredError relayed across the Capacitor bridge:
try {
await DVAIBridge.start(...);
} catch (err: any) {
if (err.code === "LICENSE_REQUIRED" || err.name === "LicenseRequiredError") {
// err.message is a multi-line string; err.status.kind is the kind.
console.error(err.message);
showAlert("License required");
}
}Testing locally without a license
The Capacitor JS validator detects dev mode when any of these are true:
- The webview hostname is
localhost— usually true under Capacitor's bundled-content scheme. Capacitor.DEBUG === true— set by Capacitor in debug builds.window.localStorage.DVAI_FORCE_DEV === "true".NODE_ENV=testorNODE_ENV=developmentat bundle time.
Rehearse production behaviour locally:
localStorage.setItem("DVAI_FORCE_PROD", "true");Audience binding on Capacitor
The runtime audience is the webview hostname. Capacitor reports localhost for the bundled-content origin. Your licenses need "localhost" as an aud entry for Capacitor activation — or "*". Native bundle-id binding lands with the v3.3 native-side validators — see the iOS / Android pages.
When validation fails
| Error reason fragment | What's wrong | Fix |
|---|---|---|
not a well-formed JWT | File missing from www/public after cap sync | Re-run cap sync after the file is in public/ |
signature did not verify | Wrong key or tampered | Re-download from your licensor |
does not authorise platform "capacitor" | License missing "capacitor" in platforms | Re-issue covering Capacitor |
audience entries ... do not match "localhost" | License doesn't include localhost (or *) | Re-issue with localhost in aud |
expired | Past exp | Renew |
See also
- License setup index
- Pre-init inspection — run
LicenseValidatorfrom@dvai-bridge/corein the webview before the native plugin boots — useful for setup wizards. - Web — the JS-side discovery rules apply identically.
- Native LLM (Capacitor) — the broader Capacitor quickstart.
