despia-native
v1.0.28
Published
Web-native iOS and Android SDK. Biometrics, GPS, in-app purchases, push notifications, and 50+ native APIs - no Swift, no Kotlin.
Maintainers
Readme
Despia Native
Add 50+ native iOS and Android capabilities to any web app (React, Vue, Angular, Svelte, Next.js, vanilla JS) through a single despia() function. Plus native offline support, a local SQLite database, and an on-device streaming CDN, solving 15 years of hybrid-app problems with modern runtime architecture.
Installation
npm install despia-native
# pnpm add despia-native
# yarn add despia-nativeimport despia from 'despia-native';Or via CDN:
<!-- UMD -->
<script src="https://cdn.jsdelivr.net/npm/despia-native/index.min.js"></script><!-- ESM -->
<script type="module">
import despia from 'https://cdn.jsdelivr.net/npm/despia-native/+esm';
</script>Quick Start
import despia from 'despia-native';
// Launch a RevenueCat paywall
despia(`revenuecat://launchPaywall?external_id=${userId}&offering=default`);
// Link the device to your user for OneSignal push
despia(`setonesignalplayerid://?user_id=${userId}`);
// Store a session token behind Face ID / Touch ID (survives reinstall)
await despia('setvault://?key=sessionToken&value=abc123&locked=true');
// Read it back later (prompts biometric)
const { sessionToken } = await despia('readvault://?key=sessionToken', ['sessionToken']);No initialization. Open your app in the Despia runtime and it works.
Why Despia
Despia is a hybrid app runtime built on the web platform. The web handles what it's good at (logic, UI, routing, state, storage); everything else (biometrics, push, in-app purchases, background GPS, system pickers) is handed to a native engine via a single despia() call.
Works with any web codebase. Bring an existing app or start fresh with any framework, any build tool, any host. No framework lock-in, no proprietary hosting, nothing to migrate.
Full native offline support. @despia/local serves your web build from http://localhost, @despia/powersync adds a native SQLite database that syncs with your backend, and the Local CDN streams cached media from device storage. File transfers stream between native storage and the web layer without loading into the JS heap.
Push, monetization, ads, and attribution are built in: OneSignal, RevenueCat, AdMob, AppsFlyer. Pre-wired into the runtime, configured from the Despia dashboard, called from web code. Builds, signing, and store submission run from the browser. No Xcode, no Android Studio.
Contents
- Why Despia
- Environment Detection
- For AI Coding
- API Reference
- Deployment Models
- Features
- Capability Reference
- Safe Area
- Extending the Runtime
- Web Native vs React Native
- Publishing
- MCP Server
- How It Works
- Support
Environment Detection
The SDK only activates inside the Despia runtime. Gate native calls:
const isDespia = navigator.userAgent.toLowerCase().includes('despia');
const isDespiaIOS = isDespia && (
navigator.userAgent.toLowerCase().includes('iphone') ||
navigator.userAgent.toLowerCase().includes('ipad')
);
const isDespiaAndroid = isDespia && navigator.userAgent.toLowerCase().includes('android');
if (isDespia) despia('lighthaptic://');For AI Coding
Full API context: llms.txt.
- Import as default:
import despia from 'despia-native' - Never write
window.despiadirectly; the SDK uses it internally - There is no
.init(),.ready(), or.setup(); the SDK exports a single function with no initialization - Do not mock or reimplement the SDK; mock implementations will not trigger the native runtime on real devices
- Gate native calls with the environment check above
- For Local Server apps, install via a package manager so the SDK ships with the build
- Do not
awaitlocalcdn://writewith a watch key; usewindow.contentServerChange - For the upload API, read
window.location.host; don't hardcode ports
API Reference
despia(command, watch?)
| Parameter | Type | Description |
| --------- | ---------- | ---------------------------------------------- |
| command | string | A Despia protocol URL, e.g. 'lighthaptic://' |
| watch | string[] | Variable names to wait for |
Returns a Promise that resolves when all watched variables are set, with a 30-second timeout. Long-running work (downloads, purchases, biometric prompts) reports back via global callbacks like window.onRevenueCatPurchase or window.contentServerChange instead. despia.variableName is equivalent to window.variableName.
Protocol format: feature://action?param1=value1¶m2=value2
Deployment Models
Remote hydration (default). Binary ships without web assets; Despia fetches the current build on launch. Web updates do not require store resubmission.
Local Server. Build is cached on-device and served from http://localhost. See Local Server.
Version gating. Use despia-version-guard to gate features by minimum runtime version:
import { VersionGuard } from 'despia-version-guard';
<VersionGuard min_version="21.0.3">
<NewFeature />
</VersionGuard>Features
RevenueCat In-App Purchases
// Paywall
despia(`revenuecat://launchPaywall?external_id=${userId}&offering=default`);
// Direct purchase
despia(`revenuecat://purchase?external_id=${userId}&product=monthly_premium_ios`);window.onRevenueCatPurchase() fires when the store confirms a transaction. Verify entitlement before unlocking (via your backend webhook, or by reading the store directly):
window.onRevenueCatPurchase = async () => {
const { restoredData } = await despia('getpurchasehistory://', ['restoredData']);
if (restoredData.some(p => p.isActive && p.entitlementId === 'premium')) unlockPremium();
};In the browser, fall back to a RevenueCat Web Purchase Link.
Push Notifications
Permission and OneSignal registration happen automatically at launch. Link the device to your user on every authenticated load:
despia(`setonesignalplayerid://?user_id=${userId}`);
// Check permission
const { nativePushEnabled } = await despia('checkNativePushPermissions://', ['nativePushEnabled']);
if (!nativePushEnabled) despia('settingsapp://');
// Local scheduled notification
despia('sendlocalpushmsg://push.send?s=60&msg=Hello&!#Title&!#https://myapp.com');Configure OneSignal with Native iOS and Native Android as platforms, then send remote pushes from your backend with the OneSignal REST API.
Identity Vault
Encrypted key-value storage backed by iCloud KV (iOS) and Android App Backup. Values survive uninstall and sync across a user's own devices. Set locked=true to require Face ID, Touch ID, or fingerprint on read.
// Write
await despia('setvault://?key=sessionToken&value=abc123&locked=true');
// Read (prompts biometric if locked)
const { sessionToken } = await despia('readvault://?key=sessionToken', ['sessionToken']);readvault:// throws if the key doesn't exist. Wrap in try/catch.
OAuth Authentication
Opens a secure browser session (ASWebAuthenticationSession / Chrome Custom Tabs). A deeplink with the oauth/ prefix closes the session and returns to your WebView.
const redirectUri = isDespia
? 'https://yourapp.com/native-callback.html'
: 'https://yourapp.com/auth/callback';
const authUrl = `https://provider.com/oauth/authorize?client_id=xxx&redirect_uri=${encodeURIComponent(redirectUri)}`;
if (isDespia) {
despia(`oauth://?url=${encodeURIComponent(authUrl)}`);
} else {
window.location.href = authUrl;
}In public/native-callback.html (plain HTML, not a framework route, so the hash isn't stripped on navigation):
<script>
var hash = new URLSearchParams(window.location.hash.substring(1));
var token = hash.get('access_token');
if (token) window.location.href = '{yourscheme}://oauth/auth?access_token=' + encodeURIComponent(token);
</script>Deeplink format: {yourscheme}://oauth/{path}?params. Without the oauth/ prefix the browser session stays open.
AppsFlyer Attribution
iOS today, Android soon. Enable in Despia > App > Settings > Integrations > AppsFlyer with your dev key.
// Attribution available on every launch
despia.appsFlyerAttribution
despia.appsFlyerReferrer
despia.appsFlyerUID
// After login
despia('appsflyer://set_user_id?customer_user_id=' + encodeURIComponent(userId));
// Log an event (af_-prefixed events map to Meta and TikTok automatically)
const values = { af_revenue: 9.99, af_currency: 'USD' };
despia('appsflyer://log_event?event_name=af_purchase&event_values=' + encodeURIComponent(JSON.stringify(values)));GPS Location
window.onLocationChange = (data) => {
if (data.active) console.log(data.latitude, data.longitude);
};
// buffer in seconds, movement threshold in centimetres
despia('location://?buffer=60&movement=100');
const { locationSession } = await despia('stoplocation://', ['locationSession']);Add &server=https://api.example.com/track to POST each point as it's recorded.
Apple Health (HealthKit)
iOS only. Gate behind isDespiaIOS. Permissions are requested on first call per identifier.
// Read
const { healthkitResponse } = await despia(
'readhealthkit://HKQuantityTypeIdentifierStepCount?days=7',
['healthkitResponse']
);
// Write
despia('writehealthkit://HKQuantityTypeIdentifierBodyMass//74.5');
// Observe and POST to your backend on change
despia('healthkit://observe?types=HKQuantityTypeIdentifierStepCount&frequency=hourly&server=https://api.example.com/webhook');Any HKQuantityTypeIdentifier, HKCategoryTypeIdentifier, HKWorkoutTypeIdentifier, or HKCharacteristicTypeIdentifier works.
File and Media Operations
Standard HTML file inputs route to native UI with events delivered back to the input:
<input type="file"> <!-- Action sheet -->
<input type="file" accept="image/*"> <!-- Image gallery -->
<input type="file" accept="video/*"> <!-- Video gallery -->
<input type="file" accept="image/*" capture="environment"><!-- Camera -->despia('takescreenshot://');
despia('savethisimage://?url=https://example.com/image.jpg');
despia('file://https://example.com/document.pdf');
despia('shareapp://message?=Check%20this%20out&url=https://myapp.com');AdMob Inline Ads
Ads render as real DOM elements via Google's WebView API for Ads. Enable in Despia > App > Settings > AdMob, then use standard AdSense, Google Publisher Tag, or IMA tags. No SDK calls from web.
Web Payment Request API
Apple Pay and Google Pay work through the standard Payment Request API with no Despia-specific calls.
const request = new PaymentRequest(
[{ supportedMethods: 'https://apple.com/apple-pay' }],
{ total: { label: 'Total', amount: { currency: 'USD', value: '9.99' } } }
);
await (await request.show()).complete('success');Local Server
Caches the web build on-device and serves it from http://localhost for offline launch. Updates are detected and swapped atomically on the next launch.
npm install --save-dev @despia/local// vite.config.js (also Webpack, Rollup, Nuxt, SvelteKit, Astro, Remix, esbuild)
import { despiaLocalPlugin } from '@despia/local/vite';
export default {
plugins: [despiaLocalPlugin({ outDir: 'dist', entryHtml: 'index.html' })],
};Or run npx despia-local dist after any build. Deploy as normal.
Local CDN
Cache remote files on-device for offline playback and background downloads. Transfers use NSURLSession / WorkManager and continue when the app is closed, with Live Activity (iOS) or system tray (Android) progress.
// Called on download complete
window.contentServerChange = (item) => {
// item.local_cdn, item.cdn, item.index, item.size, item.status, item.local_path
};
// Start a download. Do NOT await with a watch key.
despia(`localcdn://write?url=${remoteUrl}&filename=videos/clip.mp4&index=clip_1`);
// Query cache
const { cdnItems } = await despia('localcdn://query', ['cdnItems']);Play cached media from the local_cdn URL. For uploads, POST to http://${window.location.host}/api/upload (Local Server only).
Web Storage APIs
The app runs on a real origin, so localStorage, IndexedDB, and Web Crypto work normally. For data that must survive uninstall or be locked behind biometrics, use Identity Vault.
Haptic Feedback
despia('lighthaptic://'); // Subtle
despia('heavyhaptic://'); // Strong
despia('successhaptic://'); // Success
despia('warninghaptic://'); // Warning
despia('errorhaptic://'); // ErrorUI Controls and Styling
despia('spinneron://');
despia('spinneroff://');
despia('hidebars://on'); // Hide status bar
despia('hidebars://off');
despia('statusbarcolor://{255, 255, 255}'); // RGB
despia('statusbartextcolor://{black}'); // black | white
despia('settingsapp://'); // Open native app settings
despia('reset://');App Information and Device Data
const { versionNumber, bundleNumber } = await despia('getappversion://', ['versionNumber', 'bundleNumber']);
const { uuid } = await despia('get-uuid://', ['uuid']);
const { storeLocation } = await despia('getstorelocation://', ['storeLocation']);
const { trackingDisabled } = await despia('user-disable-tracking://', ['trackingDisabled']);Device indexing · Store location · App privacy
Clipboard
const { clipboarddata } = await despia('getclipboard://', ['clipboarddata']);Contacts
const { contacts } = await despia('readcontacts://', ['contacts']);Capability Reference
Core. Identity Vault, Face ID / Touch ID / fingerprint, background GPS, contacts, clipboard, haptics, native file system, image saving, background audio, local push, status bar controls, safe area CSS variables, device orientation, zoom lock, sleep lock, fullscreen, splash screen, iOS Home Widgets, Siri Shortcuts, native share, AirPrint, screen shield, PkPass for mobile wallets.
SDK bridges. RevenueCat, OneSignal, AppsFlyer, AdMob, HealthKit.
Infrastructure. ATT compliance, vendor ID, store location, jailbreak detection, App Clips, Share Extensions, Home Widget configuration.
Web interception. <input type="file"> routes to the native action sheet; capture opens the camera; accept filters to media gallery. Deeplinks and HTTPS deeplinks are handled natively.
Safe Area
Top and bottom safe area insets are exposed as CSS custom properties:
.header { padding-top: var(--safe-area-top); }
.footer { padding-bottom: var(--safe-area-bottom); }Left and right insets are not exposed.
Extending the Runtime
Intercept any custom scheme in WebViewController.swift (iOS) or MainActivity.java (Android), run native code, write the result back as a window variable. The SDK resolves it the same way as built-in schemes.
// iOS
if requestURL.absoluteString.hasPrefix("mycustom://") {
webView.evaluateJavaScript("window.myResult = '\(runMyNativeCode())';")
decisionHandler(.cancel)
return
}// Android
if (url.startsWith("mycustom://")) {
webView.evaluateJavascript("window.myResult = '" + runMyNativeCode() + "';", null);
return true;
}// Web
const { myResult } = await despia('mycustom://', ['myResult']);Full Xcode and Android Studio project export is available for custom native code.
Web Native vs React Native
This SDK is for web-native apps (React, Vue, Angular, Svelte, Next.js, Vite, Nuxt, vanilla JS) running inside the Despia runtime. It is not for React Native, Expo, or native mobile development.
Publishing
iOS and Android store deployment runs from the web editor. Cloud Mac Mini infrastructure handles code signing, provisioning, and submission.
MCP Server
Point your AI assistant at the Despia MCP for the full despia-native API.
For web builders (Lovable, Bolt, v0), paste: https://setup.despia.com/mcp.
How It Works
The SDK is a thin wrapper over a setter-based protocol bridge. Calling despia() assigns the scheme string to window.despia, which the native layer intercepts:
// When you call:
despia('lighthaptic://');
// the SDK does:
window.despia = 'lighthaptic://';iOS intercepts the assignment in WebViewController.swift; Android intercepts it in MainActivity.java. Results come back as named window variables, which the SDK resolves as a promise via the optional watch array. For long-running work (downloads, purchases, biometric prompts), the native layer calls back via global functions like window.onRevenueCatPurchase or window.contentServerChange instead.
No dependencies, no initialization, no lifecycle to manage.
