despia-native 1.0.21 → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +382 -72
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  JavaScript SDK for [Despia](https://despia.com). Build with any web framework, access 50+ native device capabilities through a single JavaScript function, and publish to iOS and Android from a browser. No Swift, no Kotlin, no terminal.
4
4
 
5
- [![npm](https://img.shields.io/npm/v/despia-native)](https://www.npmjs.com/package/despia-native)
6
- [![license](https://img.shields.io/npm/l/despia-native)](LICENSE)
5
+ [npm](https://www.npmjs.com/package/despia-native)
6
+ [license](LICENSE)
7
7
 
8
8
  **[Documentation](https://setup.despia.com)** | **[AI Agent Index](https://setup.despia.com/llms.txt)** | **[iOS Deployment](https://setup.despia.com/deployment/apple-ios/automatic)** | **[Android Deployment](https://setup.despia.com/deployment/google-android/automatic)**
9
9
 
10
10
  Despia is the next generation of web-native development. Unlike earlier hybrid frameworks that routed files through JavaScript, forced Base64 encoding, imposed storage quotas, and ran on insecure `file://` origins, Despia runs your app on a real secure origin (`http://localhost` via Local Server, or your remote URL). Standard web APIs work without restrictions. File operations bypass JavaScript entirely and go directly to the native file system. A 500MB video file uses roughly 100 bytes of JS heap rather than 1.6GB.
11
11
 
12
- The runtime has been in production since 2011, powering over 7,500 apps on the foundation that became Despia in 2023. Native capabilities are implemented in Swift and Java and called from JavaScript through a single typed function.
12
+ The runtime runs on WKWebView (iOS) and the Chromium-based WebView (Android) with hardware-accelerated compositing, JIT-compiled JavaScript, automatic DOM optimization, smart caching, and GPU-accelerated WebGL and Canvas 2D. CSS animations run on the compositor thread independently of JavaScript. The runtime has been actively maintained by the same core team since 2011, when it shipped as Advanced WebView. It has powered over 7,500 apps and became Despia in 2023. Native capabilities are implemented in Swift and Java and called from JavaScript through a single typed function. See the [changelog](https://setup.despia.com/changelog) for the full history.
13
13
 
14
14
  ---
15
15
 
@@ -17,8 +17,8 @@ The runtime has been in production since 2011, powering over 7,500 apps on the f
17
17
 
18
18
  Add the Despia MCP to give your AI assistant full knowledge of the `despia-native` API.
19
19
 
20
- [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=Despia&config=eyJ0eXBlIjoiaHR0cCIsInVybCI6Imh0dHBzOi8vc2V0dXAuZGVzcGlhLmNvbS9tY3AifQ==)
21
- [![Install in VS Code](https://img.shields.io/badge/Install_in_VS_Code-007ACC?logo=visualstudiocode&logoColor=white)](vscode:mcp/install?%7B%22name%22%3A%22Despia%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fsetup.despia.com%2Fmcp%22%7D)
20
+ [Install in Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=Despia&config=eyJ0eXBlIjoiaHR0cCIsInVybCI6Imh0dHBzOi8vc2V0dXAuZGVzcGlhLmNvbS9tY3AifQ==)
21
+ [Install in VS Code](vscode:mcp/install?%7B%22name%22%3A%22Despia%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fsetup.despia.com%2Fmcp%22%7D)
22
22
 
23
23
  ```
24
24
  https://setup.despia.com/mcp
@@ -36,11 +36,13 @@ Look for "Add MCP", "MCP Settings", or "Personal Connectors" in your builder. Re
36
36
  - [Environment Detection](#environment-detection)
37
37
  - [AI Agent Rules](#ai-agent-rules)
38
38
  - [API Reference](#api-reference)
39
+ - [Deployment Models](#deployment-models)
39
40
  - [Features](#features)
40
41
  - [Haptic Feedback](#haptic-feedback)
41
42
  - [Identity Vault](#identity-vault)
42
43
  - [GPS Location](#gps-location)
43
44
  - [RevenueCat In-App Purchases](#revenuecat-in-app-purchases)
45
+ - [AppsFlyer Attribution](#appsflyer-attribution)
44
46
  - [Push Notifications](#push-notifications)
45
47
  - [OAuth Authentication](#oauth-authentication)
46
48
  - [Clipboard](#clipboard)
@@ -48,11 +50,19 @@ Look for "Add MCP", "MCP Settings", or "Personal Connectors" in your builder. Re
48
50
  - [App Information and Device Data](#app-information-and-device-data)
49
51
  - [UI Controls and Styling](#ui-controls-and-styling)
50
52
  - [File and Media Operations](#file-and-media-operations)
53
+ - [Apple Health (HealthKit)](#apple-health-healthkit)
54
+ - [AdMob Inline Ads](#admob-inline-ads)
55
+ - [Web Payment Request API](#web-payment-request-api)
51
56
  - [Web Storage APIs](#web-storage-apis)
52
57
  - [Local CDN](#local-cdn)
53
58
  - [Local Server](#local-server)
59
+ - [Capability Reference](#capability-reference)
54
60
  - [Safe Area](#safe-area)
61
+ - [Extending the Runtime](#extending-the-runtime)
55
62
  - [Web Apps vs React Native](#web-apps-vs-react-native)
63
+ - [Publishing to the App Store and Google Play](#publishing-to-the-app-store-and-google-play)
64
+ - [Open Source](#open-source)
65
+ - [Support](#support)
56
66
  - [License](#license)
57
67
 
58
68
  ---
@@ -61,40 +71,45 @@ Look for "Add MCP", "MCP Settings", or "Personal Connectors" in your builder. Re
61
71
 
62
72
  ```bash
63
73
  npm install despia-native
64
- # or
65
- pnpm add despia-native
66
- # or
67
- yarn add despia-native
74
+ # pnpm add despia-native
75
+ # yarn add despia-native
68
76
  ```
69
77
 
70
- Do not write mock implementations or use `window.despia` directly in modern frameworks. The real SDK is required for any Despia functionality to work.
78
+ ```js
79
+ import despia from 'despia-native';
80
+ ```
81
+
82
+ > CDN alternative: `https://cdn.jsdelivr.net/npm/despia-native/+esm` (ESM) or `https://cdn.jsdelivr.net/npm/despia-native/index.min.js` (UMD, global)
71
83
 
72
84
  ---
73
85
 
74
86
  ## Quick Start
75
87
 
76
88
  ```js
77
- import despia from 'despia-native';
78
-
79
- // Fire-and-forget commands
80
- despia('lighthaptic://');
89
+ import despia from 'despia-native'; // npm / pnpm / yarn only
81
90
 
82
- // Commands that return data
83
- const appInfo = await despia('getappversion://', ['versionNumber', 'bundleNumber']);
84
- console.log(appInfo.versionNumber); // "1.0.0"
91
+ despia('successhaptic://'); // haptic feedback
92
+ const device = await despia('get-uuid://', ['uuid']); // native device ID
93
+ console.log(device.uuid);
85
94
  ```
86
95
 
87
- The SDK exports a single function as the default export. Always use a default import, not a named import.
96
+ No initialization. No setup. Open your app in the Despia runtime and it works.
88
97
 
89
- ```js
90
- // Correct
91
- import despia from 'despia-native';
98
+ **How it works under the hood**
99
+
100
+ Calling `despia()` sets `window.despia` to the scheme string. On iOS this is intercepted in `WebViewController.swift` via `decidePolicyFor navigationAction`. On Android it is intercepted in `MainActivity.java` via `shouldOverrideUrlLoading`. Results are written back to the WebView as named window variables, which the SDK resolves as a promise.
92
101
 
93
- // Wrong
94
- import { despia } from 'despia-native';
95
- import { Commands } from 'despia-native';
102
+ ```js
103
+ // despia('get-uuid://', ['uuid'])
104
+ // 1. window.despia = 'get-uuid://' set by SDK
105
+ // 2. window.location.href = 'get-uuid://' intercepted by native layer
106
+ // 3. window.uuid = '<device-uuid>' written back to WebView
107
+ const device = await despia('get-uuid://', ['uuid']);
108
+ console.log(device.uuid);
96
109
  ```
97
110
 
111
+ The SDK handles command queuing, promise-based variable watching with a 30-second timeout, and error handling.
112
+
98
113
  ---
99
114
 
100
115
  ## Environment Detection
@@ -137,16 +152,18 @@ If you are an AI coding agent generating Despia app code, follow these rules:
137
152
 
138
153
  ### `despia(command, watch?)`
139
154
 
140
- | Parameter | Type | Description |
141
- |-----------|------|-------------|
142
- | `command` | `string` | A Despia protocol URL, e.g. `'lighthaptic://'` |
143
- | `watch` | `string[]` | Optional. Array of variable names to wait for in the response. |
155
+
156
+ | Parameter | Type | Description |
157
+ | --------- | ---------- | -------------------------------------------------------------- |
158
+ | `command` | `string` | A Despia protocol URL, e.g. `'lighthaptic://'` |
159
+ | `watch` | `string[]` | Optional. Array of variable names to wait for in the response. |
160
+
144
161
 
145
162
  Returns a `Promise` that resolves when all watched variables are set by the native runtime.
146
163
 
147
164
  **Timeout behavior**
148
165
 
149
- The SDK waits up to 30 seconds for a single watched variable. If the native runtime never sets it, the Promise resolves with `undefined` and logs a timeout to the console.
166
+ The SDK watches for a single variable for up to 30 seconds. This window applies to the variable watch only, not to the underlying native operation. Long-running operations — file downloads, purchases, biometric prompts — complete in native and deliver their result via a global callback function (e.g. `window.onRevenueCatPurchase`, `window.contentServerChange`) rather than a watched variable, so they are never constrained by this window. If a watched variable is never set, the Promise resolves with `undefined` and logs a timeout to the console.
150
167
 
151
168
  **Fresh-data behavior**
152
169
 
@@ -169,6 +186,33 @@ feature://action?param1=value1&param2=value2
169
186
 
170
187
  ---
171
188
 
189
+ ## Deployment Models
190
+
191
+ **Remote hydration (default):** The binary ships without embedded web assets. On each launch, Despia fetches the current build from your configured hosting URL. Web content updates do not require App Store or Google Play resubmission.
192
+
193
+ **Local Server:** For offline-first apps. Your web build is cached on-device and served from `http://localhost`. After initial hydration the app launches without a network request. See [Local Server](#local-server).
194
+
195
+ **OTA version gating:** Gate features by minimum runtime version using `despia-version-guard`.
196
+
197
+ ```bash
198
+ npm install despia-version-guard
199
+ ```
200
+
201
+ ```jsx
202
+ import { VersionGuard } from 'despia-version-guard';
203
+
204
+ const MyApp = () => (
205
+ <div>
206
+ <StableFeature />
207
+ <VersionGuard min_version="21.0.3">
208
+ <NewFeature />
209
+ </VersionGuard>
210
+ </div>
211
+ );
212
+ ```
213
+
214
+ ---
215
+
172
216
  ## Features
173
217
 
174
218
  ### Haptic Feedback
@@ -200,11 +244,13 @@ const token = data.sessionToken;
200
244
 
201
245
  If the key does not exist, `readvault://` throws. Wrap in try/catch to handle first-time users.
202
246
 
203
- | Parameter | Description |
204
- |-----------|-------------|
205
- | `key` | Storage key, e.g. `"userId"` or `"sessionToken"` |
206
- | `value` | String value to store |
207
- | `locked` | `"true"` requires biometrics on read. `"false"` for open access. |
247
+
248
+ | Parameter | Description |
249
+ | --------- | ---------------------------------------------------------------- |
250
+ | `key` | Storage key, e.g. `"userId"` or `"sessionToken"` |
251
+ | `value` | String value to store |
252
+ | `locked` | `"true"` requires biometrics on read. `"false"` for open access. |
253
+
208
254
 
209
255
  **Store a value without biometric protection**
210
256
 
@@ -274,7 +320,7 @@ despia('location://?server=https://api.example.com/track?user=USER_ID&buffer=30&
274
320
 
275
321
  Each POST body matches the location object shape returned by `stoplocation://`.
276
322
 
277
- Full docs: https://setup.despia.com/native-features/gps-location
323
+ Full docs: [https://setup.despia.com/native-features/gps-location](https://setup.despia.com/native-features/gps-location)
278
324
 
279
325
  ---
280
326
 
@@ -286,10 +332,12 @@ Full docs: https://setup.despia.com/native-features/gps-location
286
332
  despia(`revenuecat://launchPaywall?external_id=${userId}&offering=default`);
287
333
  ```
288
334
 
289
- | Parameter | Required | Description |
290
- |-----------|----------|-------------|
291
- | `external_id` | Yes | Your user ID in RevenueCat |
292
- | `offering` | Yes | RevenueCat offering ID. Use `"default"` for your default offering. |
335
+
336
+ | Parameter | Required | Description |
337
+ | ------------- | -------- | ------------------------------------------------------------------ |
338
+ | `external_id` | Yes | Your user ID in RevenueCat |
339
+ | `offering` | Yes | RevenueCat offering ID. Use `"default"` for your default offering. |
340
+
293
341
 
294
342
  **Direct purchase without paywall UI**
295
343
 
@@ -303,7 +351,11 @@ despia(`revenuecat://purchase?external_id=${userId}&product=premium:monthly_prem
303
351
 
304
352
  **Handle purchase success**
305
353
 
306
- The native runtime calls `window.onRevenueCatPurchase()` after a successful purchase:
354
+ The Despia runtime calls `window.onRevenueCatPurchase()` immediately when the store confirms a transaction. This is a global function, not an event listener — part of how the bridge is designed to stay lightweight and offload heavy operations to the native layer.
355
+
356
+ This callback is a signal that a transaction occurred, not confirmation that access should be granted. If you have a backend, the callback fires before your server has received and processed the RevenueCat webhook. Always wait for your backend to confirm before unlocking features.
357
+
358
+ If you have no backend, use `getpurchasehistory://` inside the callback to check entitlements directly from the store:
307
359
 
308
360
  ```js
309
361
  window.onRevenueCatPurchase = async () => {
@@ -326,19 +378,95 @@ const hasPremium = restoredData
326
378
 
327
379
  Each purchase object includes `transactionId`, `productId`, `type`, `entitlementId`, `isActive`, `willRenew`, `purchaseDate`, `expirationDate`, `store`, `receipt`, and more. The response shape is normalized across iOS and Android.
328
380
 
381
+ **Web fallback**
382
+
383
+ Neither `launchPaywall` nor `purchase` work in a standard browser. Gate behind the environment check and fall back to a RevenueCat Web Purchase Link:
384
+
385
+ ```js
386
+ if (isDespia) {
387
+ despia(`revenuecat://launchPaywall?external_id=${userId}&offering=default`);
388
+ } else {
389
+ window.location.href = `https://pay.rev.cat/<your_token>/${encodeURIComponent(userId)}`;
390
+ }
391
+ ```
392
+
393
+ Full RevenueCat docs: [https://setup.despia.com/native-features/revenuecat/reference](https://setup.despia.com/native-features/revenuecat/reference)
394
+
329
395
  ---
330
396
 
331
- ### Push Notifications
397
+ ### AppsFlyer Attribution
398
+
399
+ Attribution data is recorded at install time by the native AppsFlyer SDK, cached on-device, and injected into the web layer on every page load. No requests, no waiting. Currently available on iOS, with Android coming soon.
400
+
401
+ AppsFlyer must be enabled in **Despia > App > Settings > Integrations > AppsFlyer** with your dev key configured.
402
+
403
+ **Attribution variables (injected on every page load)**
404
+
405
+ ```js
406
+ despia.appsFlyerAttribution // full attribution object (campaign, ad set, UTM params, etc.)
407
+ despia.appsFlyerReferrer // normalized source: "tiktok_ad", "facebook_organic", "organic", etc.
408
+ despia.appsFlyerUID // unique AppsFlyer user ID
409
+ ```
410
+
411
+ **User identification (call after login)**
412
+
413
+ ```js
414
+ despia('appsflyer://set_user_id?customer_user_id=' + encodeURIComponent(userId));
415
+ despia('appsflyer://set_email?email=' + encodeURIComponent(email));
416
+ despia('appsflyer://set_phone?phone=' + encodeURIComponent(phone)); // hashed with SHA256 automatically
417
+ ```
418
+
419
+ **GDPR consent**
332
420
 
333
421
  ```js
334
- // Register the device
335
- despia('registerpush://');
422
+ // User accepted
423
+ despia('appsflyer://set_consent?is_gdpr=true&has_consent=true');
424
+
425
+ // User declined
426
+ despia('appsflyer://set_consent?is_gdpr=true&has_consent=false');
427
+ ```
428
+
429
+ **Log in-app events**
430
+
431
+ Standard `af_` prefixed events map automatically to Meta and TikTok conversion events. Custom events appear in AppsFlyer for funnel and retention analysis.
432
+
433
+ ```js
434
+ // af_purchase maps to Meta Purchase and TikTok CompletePayment
435
+ const purchase = { af_revenue: 9.99, af_currency: 'USD', af_content_id: 'pro_plan' };
436
+ despia('appsflyer://log_event?event_name=af_purchase&event_values=' + encodeURIComponent(JSON.stringify(purchase)));
437
+
438
+ // af_complete_registration maps to Meta CompleteRegistration
439
+ const reg = { af_registration_method: 'email' };
440
+ despia('appsflyer://log_event?event_name=af_complete_registration&event_values=' + encodeURIComponent(JSON.stringify(reg)));
441
+
442
+ // Custom event (AppsFlyer only)
443
+ const onboarding = { step: 1, name: 'select_interests' };
444
+ despia('appsflyer://log_event?event_name=onboarding_step&event_values=' + encodeURIComponent(JSON.stringify(onboarding)));
445
+ ```
446
+
447
+ `event_values` must always be `JSON.stringify()`ed and `encodeURIComponent()`ed. Event names are case-sensitive, lowercase alphanumeric and underscores only, max 300 unique names per day.
336
448
 
337
- // Connect your user ID to this device registration (call on every app load)
449
+ Full attribution docs: [https://setup.despia.com/analytics/appsflyer/introduction](https://setup.despia.com/analytics/appsflyer/introduction)
450
+
451
+ ---
452
+
453
+ ### Push Notifications
454
+
455
+ Despia requests push permission and registers the device with OneSignal automatically at app launch. Call `setonesignalplayerid://` on every authenticated load to link the device to your user.
456
+
457
+ ```js
458
+ // Link device to your user (call on every authenticated app load)
338
459
  despia(`setonesignalplayerid://?user_id=${userId}`);
339
460
 
340
461
  // Send a local scheduled notification (fires after 60 seconds)
341
462
  despia('sendlocalpushmsg://push.send?s=60&msg=Hello&!#New Message&!#https://myapp.com');
463
+
464
+ // Check push permission status
465
+ const result = await despia('checkNativePushPermissions://', ['nativePushEnabled']);
466
+ if (!result.nativePushEnabled) {
467
+ // Direct user to settings to enable notifications
468
+ despia('settingsapp://');
469
+ }
342
470
  ```
343
471
 
344
472
  **Send to a specific user from your backend**
@@ -355,11 +483,12 @@ await fetch('https://onesignal.com/api/v1/notifications', {
355
483
  include_external_user_ids: [externalUserId],
356
484
  headings: { en: title },
357
485
  contents: { en: message },
486
+ data: { url: 'https://yourapp.com/messages/123' } // navigate WebView on tap
358
487
  }),
359
488
  });
360
489
  ```
361
490
 
362
- When configuring OneSignal, select **Native iOS** and **Native Android** as the platforms, since Despia apps are native mobile applications.
491
+ When configuring OneSignal, select **Native iOS** and **Native Android** as the platforms. For critical alerts that bypass Do Not Disturb, see the full push docs: [https://setup.despia.com](https://setup.despia.com)
363
492
 
364
493
  ---
365
494
 
@@ -367,7 +496,7 @@ When configuring OneSignal, select **Native iOS** and **Native Android** as the
367
496
 
368
497
  The flow uses two Despia URL protocols. `oauth://` opens a secure browser session (ASWebAuthenticationSession on iOS, Chrome Custom Tabs on Android). The `{scheme}://oauth/` prefix on the return deeplink tells Despia to close that session and navigate your WebView to the path that follows.
369
498
 
370
- When running in Despia, use a native-specific redirect URI pointing to `/native-callback.html` rather than your regular web auth callback. This is a different redirect URI from your web flow register both with your OAuth provider.
499
+ When running in Despia, use a native-specific redirect URI pointing to `/native-callback.html` rather than your regular web auth callback. This is a different redirect URI from your web flow. Register both with your OAuth provider.
371
500
 
372
501
  ```js
373
502
  const isDespia = navigator.userAgent.toLowerCase().includes('despia');
@@ -400,11 +529,13 @@ Deeplink format: `{yourscheme}://oauth/{path}?params`
400
529
 
401
530
  Your deeplink scheme is your app name in lowercase with no spaces (e.g. `myapp://`), or a custom Despialink set in the Despia editor.
402
531
 
403
- | Deeplink | Result |
404
- |----------|--------|
532
+
533
+ | Deeplink | Result |
534
+ | -------------------------------------------- | ------------------------------------------------------------- |
405
535
  | `{yourscheme}://oauth/auth?access_token=xxx` | Browser closes, WebView navigates to `/auth?access_token=xxx` |
406
- | `{yourscheme}://oauth/home` | Browser closes, WebView navigates to `/home` |
407
- | `{yourscheme}://auth?access_token=xxx` | Browser stays open, user is stuck |
536
+ | `{yourscheme}://oauth/home` | Browser closes, WebView navigates to `/home` |
537
+ | `{yourscheme}://auth?access_token=xxx` | Browser stays open, user is stuck |
538
+
408
539
 
409
540
  **Callback page**
410
541
 
@@ -434,7 +565,7 @@ When Despia navigates the WebView to `/auth`, if that route is already active yo
434
565
  - Vue: use `watch: { '$route.query': { immediate: true, handler } }` instead of reading params in `mounted()`
435
566
  - Vanilla JS: call your handler on load and add `window.addEventListener('popstate', handler)`
436
567
 
437
- Full docs: https://setup.despia.com/native-features/o-auth-2-0
568
+ Full docs: [https://setup.despia.com/native-features/o-auth-2-0](https://setup.despia.com/native-features/o-auth-2-0)
438
569
 
439
570
  ---
440
571
 
@@ -482,6 +613,24 @@ despia('reset://'); // Reset the app
482
613
 
483
614
  ### File and Media Operations
484
615
 
616
+ Despia automatically intercepts standard HTML file inputs and routes them to native UI. No Base64 blobs, no memory issues. File events are injected back into the standard HTML file input.
617
+
618
+ ```html
619
+ <!-- Opens a native action sheet with camera, document scanner, and media library -->
620
+ <input type="file">
621
+
622
+ <!-- Opens native media gallery for images -->
623
+ <input type="file" accept="image/*">
624
+
625
+ <!-- Opens native media gallery for video -->
626
+ <input type="file" accept="video/*">
627
+
628
+ <!-- Opens native camera directly -->
629
+ <input type="file" accept="image/*" capture="environment">
630
+ ```
631
+
632
+ Additional file and media commands:
633
+
485
634
  ```js
486
635
  despia('takescreenshot://');
487
636
  despia('savethisimage://?url=https://example.com/image.jpg');
@@ -492,6 +641,73 @@ despia('scanningmode://auto'); // auto | on | off
492
641
 
493
642
  ---
494
643
 
644
+ ### Apple Health (HealthKit)
645
+
646
+ iOS only. Always gate behind `isDespiaIOS`. Despia requests permissions on the first call for each identifier.
647
+
648
+ ```js
649
+ // Read historical data
650
+ const data = await despia('readhealthkit://HKQuantityTypeIdentifierStepCount?days=7', ['healthkitResponse']);
651
+ const steps = data.healthkitResponse.HKQuantityTypeIdentifierStepCount;
652
+ // [{ date: "2025-11-17", value: 9820, unit: "count" }, ...]
653
+
654
+ // Write a value back to HealthKit
655
+ despia('writehealthkit://HKQuantityTypeIdentifierBodyMass//74.5');
656
+
657
+ // Observe changes and POST to your server whenever HealthKit updates
658
+ despia('healthkit://observe?types=HKQuantityTypeIdentifierStepCount&frequency=hourly&server=https://api.example.com/webhook?user=USER_ID');
659
+ ```
660
+
661
+ Pass any valid `HKQuantityTypeIdentifier`, `HKCategoryTypeIdentifier`, `HKWorkoutTypeIdentifier`, or `HKCharacteristicTypeIdentifier` directly. Sleep, workouts, heart rate, body mass, and all other HealthKit types are supported. Full docs: [https://setup.despia.com](https://setup.despia.com)
662
+
663
+ ---
664
+
665
+ ### AdMob Inline Ads
666
+
667
+ Despia implements Google's WebView API for Ads on both platforms, connecting the Mobile Ads SDK directly to the WebView so AdMob ads render as real DOM elements inside your web UI, not as native overlays. Enabled from **Despia > App > Settings > AdMob**. Requires a rebuild after enabling.
668
+
669
+ Once enabled, serve ads using standard AdSense, Google Publisher Tag, or IMA for HTML5 tags. No Despia SDK calls needed on the web side.
670
+
671
+ ```html
672
+ <!-- AdSense banner -->
673
+ <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"
674
+ data-ad-client="ca-pub-XXXXXXXXXXXXXXXX"></script>
675
+ <ins class="adsbygoogle"
676
+ style="display:block"
677
+ data-ad-client="ca-pub-XXXXXXXXXXXXXXXX"
678
+ data-ad-slot="YYYYYYYYYY"
679
+ data-ad-format="auto"
680
+ data-full-width-responsive="true"></ins>
681
+ <script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
682
+ ```
683
+
684
+ Because ads are real DOM elements, placements that are impossible with native overlay ads — banners between feed cards, mid-article units, rewarded content gates, pre-roll video — are straightforward CSS and JavaScript.
685
+
686
+ Google WebView API for Ads references: [iOS](https://developers.google.com/admob/ios/browser/webview/api-for-ads) | [Android](https://developers.google.com/admob/android/browser/webview/api-for-ads) | Full Despia docs: [https://setup.despia.com](https://setup.despia.com)
687
+
688
+ ---
689
+
690
+ ### Web Payment Request API
691
+
692
+ Despia polyfills support for the Web Payment Request API, enabling native Apple Pay on iOS and Google Pay on Android directly from your web UI. This is a known pain point with standard WebViews — Despia handles it by importing WebKit's Payment Request support on Android (`androidx.webkit:webkit:1.14.0` with `WebSettingsCompat.setPaymentRequestEnabled`) and the required Google Pay intent queries, and on iOS via WKWebView's built-in Apple Pay support.
693
+
694
+ ```js
695
+ // Standard Web Payment Request API — works in Despia without any native code changes
696
+ const request = new PaymentRequest(
697
+ [{ supportedMethods: 'https://apple.com/apple-pay' }], // or Google Pay method
698
+ {
699
+ total: { label: 'Total', amount: { currency: 'USD', value: '9.99' } }
700
+ }
701
+ );
702
+
703
+ const result = await request.show();
704
+ await result.complete('success');
705
+ ```
706
+
707
+ Apple Pay on iOS and Google Pay on Android both work via the standard Web Payment Request API. No Despia-specific calls required. Reference: [Google Pay in Android WebView](https://developers.google.com/pay/api/android/guides/recipes/using-android-webview)
708
+
709
+ ---
710
+
495
711
  ### Web Storage APIs
496
712
 
497
713
  Despia runs your app on a secure origin (`http://localhost` via Local Server, or your remote URL). This means all standard web storage APIs work without any restrictions or workarounds, unlike other hybrid frameworks that require hacks or fall back to `file://` origins:
@@ -518,16 +734,18 @@ For data that needs to survive uninstall and reinstall, or be locked behind Face
518
734
 
519
735
  ### Local Server
520
736
 
521
- Most hybrid frameworks approximate offline support with service workers. Service workers are a browser-level cache that intercepts network requests, they are fragile, complex to configure, and cannot truly boot an app without any network activity. Despia takes a different approach entirely.
737
+ > **Built-in OTA updates. True offline support. Host with your existing web hosting. No proprietary infrastructure required.**
738
+
739
+ No service workers. No workarounds. The Local Server downloads your complete web build to the device and serves it from a native on-device HTTP server at `http://localhost`. The app loads from device storage at native speed and works completely offline from the first launch after hydration.
522
740
 
523
- The Local Server downloads your complete web build to the device and serves it from a native on-device HTTP server at `http://localhost`. There are no service workers involved. The app loads from device storage at native speed, works completely offline from the first launch after hydration, and every web API works because it is running on a real secure origin.
741
+ When the user has a connection, Despia checks for updates in the background by comparing the `deployed_at` timestamp in your manifest. If a new build is available it downloads silently while the user continues using the app. The update applies on the next launch. If the connection is not stable, the locally cached version is served without interruption.
742
+
743
+ You host the manifest and assets on your own infrastructure — Netlify, Vercel, AWS, your own server, anywhere. No proprietary hosting, no per-MAU fees, no vendor lock-in on your delivery pipeline.
524
744
 
525
745
  ```bash
526
746
  npm install --save-dev @despia/local
527
747
  ```
528
748
 
529
- Add the plugin to your build tool to generate the update manifest automatically:
530
-
531
749
  ```js
532
750
  // vite.config.js (also available for Webpack, Rollup, Nuxt, SvelteKit, Astro, Remix, esbuild)
533
751
  import { defineConfig } from 'vite';
@@ -549,7 +767,7 @@ Or run via CLI after any build:
549
767
  npx despia-local dist
550
768
  ```
551
769
 
552
- This generates `despia/local.json` in your output directory. Despia reads this manifest on startup, compares the `deployed_at` timestamp with the cached value, and downloads a new build in the background only when something has actually changed. The running app is never interrupted. Updates apply on the next launch.
770
+ The plugin generates `despia/local.json` in your output directory alongside your assets. Deploy both to your existing web host. That is the entire setup.
553
771
 
554
772
  ```json
555
773
  {
@@ -563,10 +781,7 @@ This generates `despia/local.json` in your output directory. Despia reads this m
563
781
  }
564
782
  ```
565
783
 
566
- What this means in practice: your app boots in milliseconds from local storage, works indefinitely without any connectivity, and receives UI updates silently in the background with no app store submission required for HTML, CSS, JavaScript, image, or font changes.
567
-
568
- Full docs: https://setup.despia.com/local-server/introduction
569
-
784
+ Full docs: [https://setup.despia.com/local-server/introduction](https://setup.despia.com/local-server/introduction)
570
785
 
571
786
  ---
572
787
 
@@ -575,13 +790,22 @@ Full docs: https://setup.despia.com/local-server/introduction
575
790
  Cache individual remote files on-device for offline playback and background downloads. Downloads use native OS transfer APIs (NSURLSession on iOS, WorkManager on Android) and continue when the app is closed, with automatic retry on network failure. On iOS a Live Activity shows real-time download progress. On Android a native notification appears in the system tray. Both require no setup.
576
791
 
577
792
  ```js
578
- // Set up the completion callback before triggering a download
793
+ // Called by the native runtime when a download completes.
794
+ // This is a global function, not an event listener.
579
795
  window.contentServerChange = (item) => {
796
+ // item.local_cdn - localhost URL to use for playback
797
+ // item.cdn - original remote URL
798
+ // item.index - the uniqueId you passed to the write call
799
+ // item.size - file size in bytes
800
+ // item.status - "cached" when complete
801
+ // item.local_path - absolute path on the device
802
+
580
803
  console.log('Cached:', item.index, item.local_cdn);
581
- // item.local_cdn is the localhost URL to use for playback
804
+ addToDownloadsList(item);
582
805
  };
583
806
 
584
- // Fire and forget. Do not await with a watch key; large files outlive the 30s bridge timeout.
807
+ // Fire and forget. Do not await with a watch key; large downloads take longer than the SDK's
808
+ // 30-second variable-watch window. Use window.contentServerChange to receive the result instead.
585
809
  despia(
586
810
  `localcdn://write?url=${remoteUrl}&filename=videos/clip.mp4&index=clip_1&push=true&pushmessage="Download complete"`
587
811
  );
@@ -614,12 +838,28 @@ const data = await res.json();
614
838
  // { success: true, fileName: "video.mp4", url: "http://localhost:7777/files/video.mp4" }
615
839
  ```
616
840
 
617
- | Method | Storage Path | URL Pattern |
618
- |--------|-------------|-------------|
841
+
842
+ | Method | Storage Path | URL Pattern |
843
+ | ------------------ | ------------ | -------------------------------------- |
619
844
  | `localcdn://write` | `/localcdn/` | `localhost:{PORT}/localcdn/{filepath}` |
620
- | `/api/upload` | `/files/` | `localhost:{PORT}/files/{filename}` |
845
+ | `/api/upload` | `/files/` | `localhost:{PORT}/files/{filename}` |
846
+
847
+
848
+ Full docs: [https://setup.despia.com/local-cdn/introduction](https://setup.despia.com/local-cdn/introduction)
849
+
850
+ ---
851
+
852
+ ## Capability Reference
853
+
854
+ A full index of native capabilities built into Despia.
621
855
 
622
- Full docs: https://setup.despia.com/local-cdn/introduction
856
+ **Core runtime:** Hardware-backed Identity Vault, Face ID / Touch ID / fingerprint biometrics, background GPS (including Samsung, Huawei, Xiaomi, and OnePlus), contacts, clipboard, haptics (5 types), native file system access, image saving, background audio, local push notifications, status bar controls, safe area CSS variables, device orientation per device class, prevent zoom, prevent sleep, fullscreen mode, splash screen, iOS Home Widgets, Siri Shortcuts, native share dialog, AirPrint, screen shield, PkPass for iOS and Android mobile wallets.
857
+
858
+ **Integrated SDK bridges:** RevenueCat (purchases, subscriptions, restore, paywalls), OneSignal (remote push with external user ID mapping), AppsFlyer (attribution, deep linking, event tracking), AdMob (advertising), HealthKit (all major health identifiers).
859
+
860
+ **Infrastructure:** ATT compliance, vendor ID tracking, device ID tracking via iCloud KV and Android App Backup, store location access, jailbreak detection with configurable blocking, App Clips, Share Extensions, and Home Widget management from the Despia editor.
861
+
862
+ **Native web interception:** `<input type="file">` routes to a native action sheet. The `capture` attribute opens the native camera. `accept="image/*"` or `accept="video/*"` opens the native media gallery. Deeplinks and HTTPS deeplinks are handled natively.
623
863
 
624
864
  ---
625
865
 
@@ -645,6 +885,60 @@ Note: left and right safe area variables are not available.
645
885
 
646
886
  ---
647
887
 
888
+ ## Extending the Runtime
889
+
890
+ A common concern when adopting any hybrid framework is lock-in: can you escape the custom protocol API later?
891
+
892
+ The bridge pattern is consistent across both platforms and fully open. You can intercept any custom scheme in `WebViewController.swift` on iOS or `MainActivity.java` on Android, run native code, and write the result back to the WebView. The SDK resolves custom bridges identically to built-in capabilities. No special registration or plugin system required.
893
+
894
+ **On the bridge architecture**
895
+
896
+ The scheme bridge is simple by design and has been running in production since 2011. A command goes in as a plain string, the native layer acts on it, and the result is written back as a named window variable. No serialization, no abstraction layers. Input is sanitized on the native side.
897
+
898
+ For file uploads, streaming, and binary data, Despia uses the on-device HTTP server at `http://localhost` rather than the scheme bridge. The two channels handle what each is suited for.
899
+
900
+ A typed plugin system for community contributions and custom native development is in progress, alongside the open source Extension system planned for mid/late 2026.
901
+
902
+ **iOS: WebViewController.swift**
903
+
904
+ ```swift
905
+ if requestURL.absoluteString.hasPrefix("mycustom://") {
906
+ let result = runMyNativeCode()
907
+ webView.evaluateJavaScript("window.myResult = '\(result)';")
908
+ decisionHandler(.cancel)
909
+ return
910
+ }
911
+ ```
912
+
913
+ **Android: MainActivity.java**
914
+
915
+ ```java
916
+ if (url.startsWith("mycustom://")) {
917
+ String result = runMyNativeCode();
918
+ webView.evaluateJavascript("window.myResult = '" + result + "';", null);
919
+ return true;
920
+ }
921
+ ```
922
+
923
+ **Call it from your web app**
924
+
925
+ ```js
926
+ import despia from 'despia-native';
927
+
928
+ const data = await despia('mycustom://', ['myResult']);
929
+ console.log(data.myResult);
930
+ ```
931
+
932
+ Adding custom native code requires exporting the project and deploying outside of Despia's hosted CI/CD pipeline. Despia offers full Xcode and Android Studio project export for developers who need it. You own the code and can build and ship entirely independently at any point.
933
+
934
+ Each lifetime Despia license applies to one specific Despia project bound to one bundle ID, and grants export, editing, self-hosting, and custom CI/CD rights for that project only. Associated repositories must remain private. Reuse beyond that scope requires separate licensing or white-label terms.
935
+
936
+ Projects and their licensing rights can be transferred between Despia accounts. Contact [license@despia.com](mailto:license@despia.com) for transfers or licensing questions, and [whitelabel@despia.com](mailto:whitelabel@despia.com) for white-label use cases.
937
+
938
+ > **Coming mid/late 2026:** An official open source Despia Extension system is currently in active development. It will allow developers to build and distribute custom native capabilities that work directly within the Despia editor, with no Xcode or Android Studio required.
939
+
940
+ ---
941
+
648
942
  ## Web Apps vs React Native
649
943
 
650
944
  This SDK is for web apps running inside the Despia runtime: React, Vue, Angular, Svelte, Next.js, Vite, Nuxt, and vanilla JavaScript.
@@ -653,18 +947,34 @@ It is not for React Native, Expo, or native mobile development.
653
947
 
654
948
  ---
655
949
 
950
+ ## Publishing to the App Store and Google Play
951
+
952
+ Despia provides fully automated iOS and Android store deployment from the web editor. No Mac required. One-click deployment spins up Mac Mini build infrastructure in the cloud, handles code signing, provisioning, and submits to both the App Store and Google Play — entirely from a browser.
953
+
954
+ Full deployment docs: [https://setup.despia.com/deployment/apple-ios/automatic](https://setup.despia.com/deployment/apple-ios/automatic)
955
+
956
+ ---
957
+
656
958
  ## Open Source
657
959
 
658
- | Package | Description | License |
659
- |---------|-------------|---------|
660
- | [despia-native](https://www.npmjs.com/package/despia-native) | JavaScript SDK | MIT |
661
- | [@despia/local](https://www.npmjs.com/package/@despia/local) | Offline asset bundler | MIT |
662
- | [despia-version-guard](https://www.npmjs.com/package/despia-version-guard) | OTA version gating | MIT |
960
+
961
+ | Package | Description | License |
962
+ | -------------------------------------------------------------------------- | --------------------- | ------- |
963
+ | [despia-native](https://www.npmjs.com/package/despia-native) | JavaScript SDK | MIT |
964
+ | [@despia/local](https://www.npmjs.com/package/@despia/local) | Offline asset bundler | MIT |
965
+ | [despia-version-guard](https://www.npmjs.com/package/despia-version-guard) | OTA version gating | MIT |
966
+
663
967
 
664
968
  Native capability implementations are written in Swift and Java and included in full on project export.
665
969
 
666
970
  ---
667
971
 
972
+ ## Support
973
+
974
+ For questions or concerns regarding this package, contact the Despia team at [npm@despia.com](mailto:npm@despia.com).
975
+
976
+ ---
977
+
668
978
  ## License
669
979
 
670
980
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "despia-native",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
4
4
  "description": "Web-native iOS and Android SDK. Biometrics, GPS, in-app purchases, push notifications, and 50+ native APIs - no Swift, no Kotlin.",
5
5
  "main": "index.js",
6
6
  "module": "index.js",