despia-native 1.0.20 → 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.
- package/README.md +663 -1010
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,1326 +1,979 @@
|
|
|
1
|
-
# Despia
|
|
1
|
+
# Despia Native
|
|
2
2
|
|
|
3
|
-
JavaScript SDK for [Despia](https://despia.com)
|
|
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
|
+
|
|
5
|
+
[npm](https://www.npmjs.com/package/despia-native)
|
|
6
|
+
[license](LICENSE)
|
|
7
|
+
|
|
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
|
+
|
|
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
|
+
|
|
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.
|
|
4
13
|
|
|
5
14
|
---
|
|
6
15
|
|
|
7
|
-
##
|
|
16
|
+
## MCP Server
|
|
17
|
+
|
|
18
|
+
Add the Despia MCP to give your AI assistant full knowledge of the `despia-native` API.
|
|
19
|
+
|
|
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
|
+
|
|
23
|
+
```
|
|
24
|
+
https://setup.despia.com/mcp
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Look for "Add MCP", "MCP Settings", or "Personal Connectors" in your builder. Requires Node.js v18+ for local tools.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Table of Contents
|
|
32
|
+
|
|
33
|
+
- [MCP Server](#mcp-server)
|
|
34
|
+
- [Installation](#installation)
|
|
35
|
+
- [Quick Start](#quick-start)
|
|
36
|
+
- [Environment Detection](#environment-detection)
|
|
37
|
+
- [AI Agent Rules](#ai-agent-rules)
|
|
38
|
+
- [API Reference](#api-reference)
|
|
39
|
+
- [Deployment Models](#deployment-models)
|
|
40
|
+
- [Features](#features)
|
|
41
|
+
- [Haptic Feedback](#haptic-feedback)
|
|
42
|
+
- [Identity Vault](#identity-vault)
|
|
43
|
+
- [GPS Location](#gps-location)
|
|
44
|
+
- [RevenueCat In-App Purchases](#revenuecat-in-app-purchases)
|
|
45
|
+
- [AppsFlyer Attribution](#appsflyer-attribution)
|
|
46
|
+
- [Push Notifications](#push-notifications)
|
|
47
|
+
- [OAuth Authentication](#oauth-authentication)
|
|
48
|
+
- [Clipboard](#clipboard)
|
|
49
|
+
- [Contacts](#contacts)
|
|
50
|
+
- [App Information and Device Data](#app-information-and-device-data)
|
|
51
|
+
- [UI Controls and Styling](#ui-controls-and-styling)
|
|
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)
|
|
56
|
+
- [Web Storage APIs](#web-storage-apis)
|
|
57
|
+
- [Local CDN](#local-cdn)
|
|
58
|
+
- [Local Server](#local-server)
|
|
59
|
+
- [Capability Reference](#capability-reference)
|
|
60
|
+
- [Safe Area](#safe-area)
|
|
61
|
+
- [Extending the Runtime](#extending-the-runtime)
|
|
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)
|
|
66
|
+
- [License](#license)
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
8
71
|
|
|
9
|
-
**Install the SDK:**
|
|
10
72
|
```bash
|
|
11
73
|
npm install despia-native
|
|
74
|
+
# pnpm add despia-native
|
|
75
|
+
# yarn add despia-native
|
|
12
76
|
```
|
|
13
77
|
|
|
14
|
-
|
|
15
|
-
```javascript
|
|
78
|
+
```js
|
|
16
79
|
import despia from 'despia-native';
|
|
17
|
-
|
|
18
|
-
// Simple commands
|
|
19
|
-
despia('lighthaptic://');
|
|
20
|
-
|
|
21
|
-
// Commands with responses
|
|
22
|
-
const appInfo = await despia('getappversion://', ['versionNumber']);
|
|
23
80
|
```
|
|
24
81
|
|
|
25
|
-
|
|
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)
|
|
26
83
|
|
|
27
84
|
---
|
|
28
85
|
|
|
29
|
-
|
|
86
|
+
## Quick Start
|
|
30
87
|
|
|
31
|
-
|
|
88
|
+
```js
|
|
89
|
+
import despia from 'despia-native'; // npm / pnpm / yarn only
|
|
32
90
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- Svelte web apps
|
|
38
|
-
- Any web framework or vanilla JavaScript
|
|
39
|
-
|
|
40
|
-
**This SDK is NOT for:**
|
|
41
|
-
- React Native apps
|
|
42
|
-
- Expo apps
|
|
43
|
-
- Native mobile development
|
|
44
|
-
|
|
45
|
-
**If you're building a React Native app, this SDK won't work for you.**
|
|
46
|
-
|
|
47
|
-
**Import:** `import despia from 'despia-native';` (default export, not destructured)
|
|
48
|
-
|
|
49
|
-
**IMPORTANT: This SDK package is REQUIRED for TypeScript, React, Vue, and other modern frameworks!** While `window.despia = ""` works in vanilla JavaScript, this package provides type safety, command queuing, and variable watching for professional development environments.
|
|
50
|
-
|
|
51
|
-
## About Despia
|
|
52
|
-
|
|
53
|
-
Despia bridges the gap between web and native mobile development. Build your React web app, Vue app, Angular app, or any web framework using the technologies you already know, then deploy it as a truly native application to the App Store and Google Play - complete with hardware acceleration, offline support, and deep OS integration.
|
|
54
|
-
|
|
55
|
-
Our visual editor allows you to configure native widgets, shortcuts, and dynamic app behaviors without touching Xcode or Android Studio. Ship to both app stores with one-click deployment, automatic CI/CD pipelines, and over-the-air updates. Export clean, human-readable Swift and Kotlin source code anytime - you own everything, no vendor lock-in.
|
|
56
|
-
|
|
57
|
-
**Go from web app to app store in a weekend - with full native capabilities.**
|
|
58
|
-
|
|
59
|
-
### Key Features:
|
|
60
|
-
- **Web Framework Support** - Works with React web apps, Vue web apps, Angular web apps, Svelte web apps, vanilla JS, or any web framework
|
|
61
|
-
- **NOT for React Native** - This is for web apps to add native features, not React Native apps
|
|
62
|
-
- **Visual Configuration** - Set up native features through an intuitive interface
|
|
63
|
-
- **Zero Native Coding** - Access device APIs without writing Swift or Kotlin
|
|
64
|
-
- **Source Code Export** - Get complete Xcode and Android Studio projects you can modify
|
|
65
|
-
- **True Ownership** - Full access to generated code, no restrictions
|
|
66
|
-
- **One-Click Publishing** - Direct deployment to App Store & Google Play
|
|
67
|
-
- **Automated Pipeline** - Built-in CI/CD with code signing and provisioning
|
|
68
|
-
- **OTA Updates** - Push updates instantly without app store review
|
|
69
|
-
- **Performance** - 60fps GPU-accelerated rendering
|
|
70
|
-
- **Device API Access** - NFC, HealthKit, Siri, RevenueCat, and 20+ more
|
|
71
|
-
- **Single Codebase** - Maintain one web project for iOS, Android, and web
|
|
72
|
-
|
|
73
|
-
### Native Device Features:
|
|
74
|
-
- **App Links & Deep Linking** - Universal links and app-to-app communication
|
|
75
|
-
- **Widgets & App Clips** - Home screen widgets and instant app experiences
|
|
76
|
-
- **In-App Purchases** - RevenueCat integration for subscriptions and payments
|
|
77
|
-
- **Push Notifications** - Local and remote notifications with rich media
|
|
78
|
-
- **Camera & Media** - Camera access, photo library, and file management
|
|
79
|
-
- **Biometric Authentication** - Face ID, Touch ID, and fingerprint
|
|
80
|
-
- **Location Services** - Foreground and background GPS tracking
|
|
81
|
-
- **Contacts** - Address book access with proper permissions
|
|
82
|
-
- **Haptic Feedback** - Tactile responses for user interactions
|
|
83
|
-
- **App Analytics** - Version tracking, bundle IDs, device identifiers
|
|
84
|
-
- **Screenshots** - Programmatic screen capture
|
|
85
|
-
- **Status Bar** - Dynamic styling and visibility control
|
|
86
|
-
- **Privacy Controls** - IDFA tracking and permission management
|
|
87
|
-
- **Share Sheets** - Native sharing UI
|
|
88
|
-
- **UI Controls** - Loading indicators, navigation bars, dynamic layouts
|
|
89
|
-
- **Plus 8+ additional native capabilities**
|
|
90
|
-
|
|
91
|
-
## Installation (REQUIRED - NO EXCEPTIONS)
|
|
91
|
+
despia('successhaptic://'); // haptic feedback
|
|
92
|
+
const device = await despia('get-uuid://', ['uuid']); // native device ID
|
|
93
|
+
console.log(device.uuid);
|
|
94
|
+
```
|
|
92
95
|
|
|
93
|
-
|
|
94
|
-
# npm
|
|
95
|
-
npm install despia-native
|
|
96
|
+
No initialization. No setup. Open your app in the Despia runtime and it works.
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
pnpm install despia-native
|
|
98
|
+
**How it works under the hood**
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
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.
|
|
101
|
+
|
|
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);
|
|
102
109
|
```
|
|
103
110
|
|
|
104
|
-
|
|
111
|
+
The SDK handles command queuing, promise-based variable watching with a 30-second timeout, and error handling.
|
|
105
112
|
|
|
106
|
-
|
|
107
|
-
- ✅ **npm** - Fully supported
|
|
108
|
-
- ✅ **pnpm** - Fully supported (v1.0.19+)
|
|
109
|
-
- ✅ **yarn** - Fully supported
|
|
113
|
+
---
|
|
110
114
|
|
|
111
|
-
|
|
112
|
-
- Write your own version
|
|
113
|
-
- Copy/paste a mock implementation
|
|
114
|
-
- Use window.despia directly in frameworks
|
|
115
|
-
- Create a "simplified" version
|
|
115
|
+
## Environment Detection
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
The SDK only activates inside the Despia native runtime. Gate native calls using these checks:
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
```js
|
|
120
|
+
const isDespia = navigator.userAgent.toLowerCase().includes('despia');
|
|
120
121
|
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
const isDespiaIOS = isDespia &&
|
|
123
|
+
(navigator.userAgent.toLowerCase().includes('iphone') ||
|
|
124
|
+
navigator.userAgent.toLowerCase().includes('ipad'));
|
|
123
125
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
const isDespiaAndroid = isDespia &&
|
|
127
|
+
navigator.userAgent.toLowerCase().includes('android');
|
|
126
128
|
|
|
127
|
-
|
|
128
|
-
despia('lighthaptic://');
|
|
129
|
-
|
|
129
|
+
if (isDespia) {
|
|
130
|
+
despia('lighthaptic://');
|
|
131
|
+
} else {
|
|
132
|
+
// Fallback for browser or development environment
|
|
133
|
+
}
|
|
130
134
|
```
|
|
131
135
|
|
|
132
|
-
|
|
133
|
-
```javascript
|
|
134
|
-
// Don't create mock implementations
|
|
135
|
-
const despia = (command) => console.log(command); // Won't work on devices
|
|
136
|
+
---
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
despia.ready(); // This method doesn't exist
|
|
139
|
-
despia.init(); // This method doesn't exist
|
|
140
|
-
despia.setup(); // This method doesn't exist
|
|
141
|
-
```
|
|
138
|
+
## AI Agent Rules
|
|
142
139
|
|
|
143
|
-
|
|
140
|
+
If you are an AI coding agent generating Despia app code, follow these rules:
|
|
144
141
|
|
|
145
|
-
|
|
142
|
+
- Import as a default: `import despia from 'despia-native'`
|
|
143
|
+
- Do not write to `window.despia` directly; it is used internally by the SDK
|
|
144
|
+
- Gate all native calls using the environment checks above
|
|
145
|
+
- For offline (Local Server) apps, bundle the SDK via a package manager so it is available when the app is served from `http://localhost`
|
|
146
|
+
- For Local CDN downloads, do not `await` `localcdn://write` with a watch key; use `window.contentServerChange(item)` or poll with `localcdn://read`
|
|
147
|
+
- For the HTTP upload API, do not hardcode localhost ports; use `const host = window.location.host`
|
|
146
148
|
|
|
147
|
-
|
|
149
|
+
---
|
|
148
150
|
|
|
149
|
-
|
|
150
|
-
// CORRECT - ES6/ES2015 modules (default import)
|
|
151
|
-
import despia from 'despia-native';
|
|
151
|
+
## API Reference
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
const despia = require('despia-native');
|
|
153
|
+
### `despia(command, watch?)`
|
|
155
154
|
|
|
156
|
-
// CORRECT - Browser (if using UMD build)
|
|
157
|
-
// <script src="despia-native.js"></script>
|
|
158
|
-
// despia is available globally
|
|
159
155
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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. |
|
|
164
160
|
|
|
165
|
-
**The SDK exports a single function called `despia` as the default export.**
|
|
166
161
|
|
|
167
|
-
|
|
162
|
+
Returns a `Promise` that resolves when all watched variables are set by the native runtime.
|
|
168
163
|
|
|
169
|
-
|
|
170
|
-
// That's it! No initialization needed. Just call despia() directly:
|
|
164
|
+
**Timeout behavior**
|
|
171
165
|
|
|
172
|
-
|
|
173
|
-
despia('lighthaptic://'); // Light haptic feedback
|
|
174
|
-
despia('takescreenshot://'); // Take screenshot
|
|
175
|
-
despia('spinneron://'); // Show loading spinner
|
|
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.
|
|
176
167
|
|
|
177
|
-
|
|
178
|
-
const appInfo = await despia('getappversion://', ['versionNumber', 'bundleNumber']);
|
|
179
|
-
console.log(appInfo); // { versionNumber: '1.0.0', bundleNumber: '123' }
|
|
168
|
+
**Fresh-data behavior**
|
|
180
169
|
|
|
181
|
-
|
|
182
|
-
console.log(contacts); // { contacts: [...] }
|
|
183
|
-
```
|
|
170
|
+
Watched variables are cleared before each call to prevent resolving on stale values.
|
|
184
171
|
|
|
185
|
-
|
|
172
|
+
- Single variable: `null` is treated as a valid resolved value. Other empty placeholders (`undefined`, `"n/a"`, `{}`, `[]`) are ignored.
|
|
173
|
+
- Multiple variables: all variables must be non-null before the Promise resolves.
|
|
186
174
|
|
|
187
|
-
|
|
188
|
-
// For commands that set variables, watch for them
|
|
189
|
-
const result = await despia('get-uuid://', ['uuid']);
|
|
190
|
-
console.log('Device UUID:', result.uuid);
|
|
175
|
+
**Direct property access**
|
|
191
176
|
|
|
192
|
-
|
|
193
|
-
despia
|
|
194
|
-
despia('takescreenshot://');
|
|
177
|
+
```js
|
|
178
|
+
despia.variableName // Equivalent to window.variableName
|
|
195
179
|
```
|
|
196
180
|
|
|
197
|
-
###
|
|
181
|
+
### Protocol format
|
|
198
182
|
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
despia('heavyhaptic://'); // Heavy vibration
|
|
203
|
-
despia('successhaptic://'); // Success vibration
|
|
183
|
+
```
|
|
184
|
+
feature://action?param1=value1¶m2=value2
|
|
185
|
+
```
|
|
204
186
|
|
|
205
|
-
|
|
206
|
-
const appInfo = await despia('getappversion://', ['versionNumber', 'bundleNumber']);
|
|
207
|
-
const deviceId = await despia('get-uuid://', ['uuid']);
|
|
187
|
+
---
|
|
208
188
|
|
|
209
|
-
|
|
210
|
-
despia('spinneron://'); // Show loading
|
|
211
|
-
despia('spinneroff://'); // Hide loading
|
|
212
|
-
despia('hidebars://on'); // Hide status bar
|
|
189
|
+
## Deployment Models
|
|
213
190
|
|
|
214
|
-
|
|
215
|
-
despia('takescreenshot://');
|
|
216
|
-
despia('shareapp://message?=Hello&url=https://myapp.com');
|
|
217
|
-
```
|
|
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.
|
|
218
192
|
|
|
219
|
-
|
|
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).
|
|
220
194
|
|
|
221
|
-
**
|
|
195
|
+
**OTA version gating:** Gate features by minimum runtime version using `despia-version-guard`.
|
|
222
196
|
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
import { despia } from 'despia-native';
|
|
197
|
+
```bash
|
|
198
|
+
npm install despia-version-guard
|
|
199
|
+
```
|
|
227
200
|
|
|
228
|
-
|
|
229
|
-
import
|
|
201
|
+
```jsx
|
|
202
|
+
import { VersionGuard } from 'despia-version-guard';
|
|
230
203
|
|
|
231
|
-
|
|
232
|
-
|
|
204
|
+
const MyApp = () => (
|
|
205
|
+
<div>
|
|
206
|
+
<StableFeature />
|
|
207
|
+
<VersionGuard min_version="21.0.3">
|
|
208
|
+
<NewFeature />
|
|
209
|
+
</VersionGuard>
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
233
212
|
```
|
|
234
213
|
|
|
235
|
-
|
|
214
|
+
---
|
|
236
215
|
|
|
237
|
-
##
|
|
216
|
+
## Features
|
|
238
217
|
|
|
239
|
-
###
|
|
218
|
+
### Haptic Feedback
|
|
240
219
|
|
|
241
|
-
```
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
220
|
+
```js
|
|
221
|
+
despia('lighthaptic://'); // Subtle vibration
|
|
222
|
+
despia('heavyhaptic://'); // Strong vibration
|
|
223
|
+
despia('successhaptic://'); // Positive confirmation
|
|
224
|
+
despia('warninghaptic://'); // Attention alert
|
|
225
|
+
despia('errorhaptic://'); // Negative feedback
|
|
245
226
|
```
|
|
246
227
|
|
|
247
|
-
|
|
248
|
-
- **TypeScript Support** - No type definitions or autocomplete
|
|
249
|
-
- **Command Queuing** - Commands may be lost or executed out of order
|
|
250
|
-
- **Variable Watching** - Can't wait for responses from native commands
|
|
251
|
-
- **Error Handling** - No timeout or error management
|
|
252
|
-
- **Type Safety** - No validation or IntelliSense
|
|
228
|
+
---
|
|
253
229
|
|
|
254
|
-
###
|
|
230
|
+
### Identity Vault
|
|
255
231
|
|
|
256
|
-
|
|
257
|
-
// This WON'T work in TypeScript, React, Vue, etc.:
|
|
258
|
-
window.despia = 'lighthaptic://'; // TypeScript errors, no type safety
|
|
259
|
-
window.despia = 'getappversion://'; // No command queuing, no variable watching
|
|
260
|
-
```
|
|
232
|
+
Encrypted key-value storage backed by iCloud KV on iOS and Android App Backup on Android. Persists across uninstalls and reinstalls. Data syncs automatically across all devices sharing the same Apple ID or Google account.
|
|
261
233
|
|
|
262
|
-
|
|
234
|
+
Set `locked=true` on any key to require Face ID or Touch ID before the value can be read back. Because the vault stores the actual value server-side and only returns it after biometric success, you can store real JWT tokens, session cookies, and API keys behind biometrics. This is a hardware-enforced security guarantee, not a client-side check that can be bypassed.
|
|
263
235
|
|
|
264
|
-
```
|
|
265
|
-
//
|
|
266
|
-
|
|
236
|
+
```js
|
|
237
|
+
// Store a JWT token (reading it back requires Face ID / Touch ID)
|
|
238
|
+
await despia('setvault://?key=sessionToken&value=abc123&locked=true');
|
|
267
239
|
|
|
268
|
-
|
|
269
|
-
const
|
|
240
|
+
// Read triggers the biometric prompt; token is only returned on success
|
|
241
|
+
const data = await despia('readvault://?key=sessionToken', ['sessionToken']);
|
|
242
|
+
const token = data.sessionToken;
|
|
270
243
|
```
|
|
271
244
|
|
|
272
|
-
|
|
273
|
-
- **TypeScript Support** - Full type definitions and autocomplete
|
|
274
|
-
- **Command Queuing** - Sequential execution, no lost commands
|
|
275
|
-
- **Variable Watching** - Automatic waiting for native responses
|
|
276
|
-
- **Error Handling** - Timeouts, error management, debugging
|
|
277
|
-
- **Type Safety** - Validated commands, autocomplete, IntelliSense
|
|
245
|
+
If the key does not exist, `readvault://` throws. Wrap in try/catch to handle first-time users.
|
|
278
246
|
|
|
279
|
-
**This package is REQUIRED for TypeScript, React, Vue, Angular, and other modern frameworks.**
|
|
280
247
|
|
|
281
|
-
|
|
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. |
|
|
282
253
|
|
|
283
|
-
### Environment Detection
|
|
284
|
-
The SDK only works within the Despia native runtime. For development and testing:
|
|
285
254
|
|
|
286
|
-
|
|
287
|
-
import despia from 'despia-native';
|
|
255
|
+
**Store a value without biometric protection**
|
|
288
256
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
despia('lighthaptic://');
|
|
292
|
-
} else {
|
|
293
|
-
// Handle non-Despia environment
|
|
294
|
-
console.log('Running outside Despia runtime');
|
|
295
|
-
}
|
|
296
|
-
```
|
|
257
|
+
```js
|
|
258
|
+
await despia('setvault://?key=userId&value=user123&locked=false');
|
|
297
259
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
- Don't create mock implementations - they won't work on actual devices
|
|
301
|
-
- The SDK handles missing runtime gracefully
|
|
260
|
+
const { userId } = await despia('readvault://?key=userId', ['userId']);
|
|
261
|
+
```
|
|
302
262
|
|
|
303
|
-
|
|
263
|
+
**Protect a sensitive action with Face ID**
|
|
304
264
|
|
|
305
|
-
|
|
265
|
+
```js
|
|
266
|
+
async function confirmWithBiometrics() {
|
|
267
|
+
await despia('setvault://?key=confirm&value=yes&locked=true');
|
|
268
|
+
try {
|
|
269
|
+
const data = await despia('readvault://?key=confirm', ['confirm']);
|
|
270
|
+
if (data.confirm === 'yes') {
|
|
271
|
+
await performSensitiveAction();
|
|
272
|
+
await despia('setvault://?key=confirm&value=&locked=false');
|
|
273
|
+
}
|
|
274
|
+
} catch {
|
|
275
|
+
// User cancelled or biometric failed
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
306
279
|
|
|
307
|
-
|
|
308
|
-
import despia from 'despia-native';
|
|
280
|
+
**Prevent free trial abuse**
|
|
309
281
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
282
|
+
```js
|
|
283
|
+
async function checkTrialEligibility() {
|
|
284
|
+
try {
|
|
285
|
+
const data = await despia('readvault://?key=hasUsedTrial', ['hasUsedTrial']);
|
|
286
|
+
return data.hasUsedTrial !== 'yes';
|
|
287
|
+
} catch {
|
|
288
|
+
// Key not found, first-time user
|
|
289
|
+
await despia('setvault://?key=hasUsedTrial&value=yes&locked=false');
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
319
292
|
}
|
|
320
293
|
```
|
|
321
294
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
## Usage
|
|
295
|
+
---
|
|
325
296
|
|
|
326
|
-
###
|
|
297
|
+
### GPS Location
|
|
327
298
|
|
|
328
|
-
```
|
|
329
|
-
|
|
299
|
+
```js
|
|
300
|
+
// Set up the live update callback
|
|
301
|
+
window.onLocationChange = (data) => {
|
|
302
|
+
if (!data.active) return;
|
|
303
|
+
console.log(data.latitude, data.longitude, data.horizontalAccuracy);
|
|
304
|
+
};
|
|
330
305
|
|
|
331
|
-
//
|
|
332
|
-
despia('
|
|
306
|
+
// Start tracking (buffer in seconds, movement threshold in centimetres)
|
|
307
|
+
despia('location://?buffer=60&movement=100');
|
|
333
308
|
|
|
334
|
-
//
|
|
335
|
-
const
|
|
336
|
-
console.log(result); // { versionNumber: '1.0.0', bundleNumber: '123' }
|
|
309
|
+
// Stop tracking and retrieve the session
|
|
310
|
+
const { locationSession } = await despia('stoplocation://', ['locationSession']);
|
|
337
311
|
```
|
|
338
312
|
|
|
339
|
-
|
|
313
|
+
**Server delivery**
|
|
340
314
|
|
|
341
|
-
|
|
342
|
-
// Native Widgets
|
|
343
|
-
despia('widget://${svg}?refresh=${refresh_time}');
|
|
315
|
+
When `server` is set, each GPS point is POSTed to your endpoint as it is recorded. Server delivery, `window.onLocationChange`, and local session storage all run simultaneously and independently. Loss of network does not affect local storage or frontend callbacks.
|
|
344
316
|
|
|
345
|
-
|
|
346
|
-
despia('
|
|
317
|
+
```js
|
|
318
|
+
despia('location://?server=https://api.example.com/track?user=USER_ID&buffer=30&movement=100');
|
|
319
|
+
```
|
|
347
320
|
|
|
348
|
-
|
|
349
|
-
const purchaseData = await despia("getpurchasehistory://", ["restoredData"]);
|
|
321
|
+
Each POST body matches the location object shape returned by `stoplocation://`.
|
|
350
322
|
|
|
351
|
-
|
|
352
|
-
despia('revenuecat://launchPaywall?external_id=user_777&offering=default');
|
|
323
|
+
Full docs: [https://setup.despia.com/native-features/gps-location](https://setup.despia.com/native-features/gps-location)
|
|
353
324
|
|
|
354
|
-
|
|
355
|
-
await despia(`setvault://?key=userId&value=user123&locked=false`);
|
|
356
|
-
const vaultData = await despia(`readvault://?key=userId`, ['userId']);
|
|
325
|
+
---
|
|
357
326
|
|
|
358
|
-
|
|
359
|
-
despia(`oauth://?url=${encodeURIComponent(oauthUrl)}`);
|
|
327
|
+
### RevenueCat In-App Purchases
|
|
360
328
|
|
|
361
|
-
|
|
362
|
-
const encoded = encodeURIComponent(JSON.stringify(userData));
|
|
363
|
-
await despia(`writevalue://${encoded}`);
|
|
364
|
-
const data = await despia("readvalue://", ["storedValues"]);
|
|
329
|
+
**Launch a paywall**
|
|
365
330
|
|
|
366
|
-
|
|
367
|
-
|
|
331
|
+
```js
|
|
332
|
+
despia(`revenuecat://launchPaywall?external_id=${userId}&offering=default`);
|
|
333
|
+
```
|
|
368
334
|
|
|
369
|
-
// Open App Settings
|
|
370
|
-
despia("settingsapp://");
|
|
371
335
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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. |
|
|
375
340
|
|
|
376
|
-
// Background Location
|
|
377
|
-
despia('backgroundlocationon://');
|
|
378
|
-
// Use native browser geolocation API (not despia native runtime)
|
|
379
|
-
const watchId = navigator.geolocation.watchPosition(
|
|
380
|
-
(position) => console.log('Location:', position),
|
|
381
|
-
(error) => console.error('Location error:', error)
|
|
382
|
-
);
|
|
383
|
-
// To stop
|
|
384
|
-
despia('backgroundlocationoff://');
|
|
385
|
-
navigator.geolocation.clearWatch(watchId);
|
|
386
341
|
|
|
387
|
-
|
|
388
|
-
despia('registerpush://');
|
|
389
|
-
despia('sendlocalpushmsg://push.send?s=60&msg=Hello&!#New Message&!#https://myapp.com');
|
|
390
|
-
// Set OneSignal external user ID (call on every app load)
|
|
391
|
-
despia(`setonesignalplayerid://?user_id=${YOUR_LOGGED_IN_USER_ID}`);
|
|
342
|
+
**Direct purchase without paywall UI**
|
|
392
343
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
despia(
|
|
396
|
-
despia('successhaptic://');
|
|
397
|
-
despia('warninghaptic://');
|
|
398
|
-
despia('errorhaptic://');
|
|
344
|
+
```js
|
|
345
|
+
// iOS
|
|
346
|
+
despia(`revenuecat://purchase?external_id=${userId}&product=monthly_premium_ios`);
|
|
399
347
|
|
|
400
|
-
//
|
|
401
|
-
|
|
402
|
-
|
|
348
|
+
// Android
|
|
349
|
+
despia(`revenuecat://purchase?external_id=${userId}&product=premium:monthly_premium_android`);
|
|
350
|
+
```
|
|
403
351
|
|
|
404
|
-
|
|
405
|
-
despia('takescreenshot://');
|
|
406
|
-
despia('scanningmode://auto');
|
|
407
|
-
despia('scanningmode://on');
|
|
408
|
-
despia('scanningmode://off');
|
|
352
|
+
**Handle purchase success**
|
|
409
353
|
|
|
410
|
-
|
|
411
|
-
const storeData = await despia('getstorelocation://', ['storeLocation']);
|
|
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.
|
|
412
355
|
|
|
413
|
-
|
|
414
|
-
despia('savethisimage://?url=${image_url}');
|
|
415
|
-
despia('file://${file_url}');
|
|
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.
|
|
416
357
|
|
|
417
|
-
|
|
418
|
-
despia('reset://');
|
|
419
|
-
const trackingData = await despia('user-disable-tracking://', ['trackingDisabled']);
|
|
358
|
+
If you have no backend, use `getpurchasehistory://` inside the callback to check entitlements directly from the store:
|
|
420
359
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
despia('
|
|
424
|
-
|
|
425
|
-
despia('hidebars://off');
|
|
360
|
+
```js
|
|
361
|
+
window.onRevenueCatPurchase = async () => {
|
|
362
|
+
const { restoredData } = await despia('getpurchasehistory://', ['restoredData']);
|
|
363
|
+
const active = (restoredData ?? []).filter(p => p.isActive);
|
|
426
364
|
|
|
427
|
-
|
|
428
|
-
|
|
365
|
+
if (active.some(p => p.entitlementId === 'premium')) unlockPremium();
|
|
366
|
+
if (active.some(p => p.entitlementId === 'no_ads')) removeAds();
|
|
367
|
+
};
|
|
368
|
+
```
|
|
429
369
|
|
|
430
|
-
|
|
431
|
-
despia('statusbarcolor://{255, 255, 255}');
|
|
432
|
-
despia('statusbartextcolor://{black}');
|
|
370
|
+
**Restore purchases**
|
|
433
371
|
|
|
434
|
-
|
|
435
|
-
despia('
|
|
372
|
+
```js
|
|
373
|
+
const { restoredData } = await despia('getpurchasehistory://', ['restoredData']);
|
|
374
|
+
const hasPremium = restoredData
|
|
375
|
+
.filter(p => p.isActive)
|
|
376
|
+
.some(p => p.entitlementId === 'premium');
|
|
436
377
|
```
|
|
437
378
|
|
|
438
|
-
|
|
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.
|
|
380
|
+
|
|
381
|
+
**Web fallback**
|
|
439
382
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
+
}
|
|
445
391
|
```
|
|
446
392
|
|
|
447
|
-
|
|
393
|
+
Full RevenueCat docs: [https://setup.despia.com/native-features/revenuecat/reference](https://setup.despia.com/native-features/revenuecat/reference)
|
|
448
394
|
|
|
449
|
-
|
|
450
|
-
// Watch multiple response variables
|
|
451
|
-
const appData = await despia('getappversion://', ['versionNumber', 'bundleNumber']);
|
|
395
|
+
---
|
|
452
396
|
|
|
453
|
-
|
|
454
|
-
despia('lighthaptic://');
|
|
455
|
-
const appData2 = await despia('getappversion://', ['versionNumber', 'bundleNumber']);
|
|
456
|
-
despia('successhaptic://');
|
|
457
|
-
```
|
|
397
|
+
### AppsFlyer Attribution
|
|
458
398
|
|
|
459
|
-
|
|
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.
|
|
460
400
|
|
|
461
|
-
|
|
401
|
+
AppsFlyer must be enabled in **Despia > App > Settings > Integrations > AppsFlyer** with your dev key configured.
|
|
462
402
|
|
|
463
|
-
|
|
464
|
-
// Step 1: Enable native background location tracking via Despia
|
|
465
|
-
despia('backgroundlocationon://');
|
|
403
|
+
**Attribution variables (injected on every page load)**
|
|
466
404
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
timestamp: position.timestamp
|
|
475
|
-
});
|
|
476
|
-
},
|
|
477
|
-
(error) => {
|
|
478
|
-
console.error('Location error:', error);
|
|
479
|
-
},
|
|
480
|
-
{
|
|
481
|
-
enableHighAccuracy: true,
|
|
482
|
-
timeout: 10000,
|
|
483
|
-
maximumAge: 60000
|
|
484
|
-
}
|
|
485
|
-
);
|
|
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)**
|
|
486
412
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
despia('
|
|
490
|
-
|
|
491
|
-
navigator.geolocation.clearWatch(watchId);
|
|
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
|
|
492
417
|
```
|
|
493
418
|
|
|
494
|
-
|
|
419
|
+
**GDPR consent**
|
|
495
420
|
|
|
496
|
-
```
|
|
497
|
-
//
|
|
498
|
-
despia('
|
|
421
|
+
```js
|
|
422
|
+
// User accepted
|
|
423
|
+
despia('appsflyer://set_consent?is_gdpr=true&has_consent=true');
|
|
499
424
|
|
|
500
|
-
//
|
|
501
|
-
|
|
502
|
-
console.log('Contacts:', contactData.contacts);
|
|
425
|
+
// User declined
|
|
426
|
+
despia('appsflyer://set_consent?is_gdpr=true&has_consent=false');
|
|
503
427
|
```
|
|
504
428
|
|
|
505
|
-
|
|
429
|
+
**Log in-app events**
|
|
506
430
|
|
|
507
|
-
|
|
431
|
+
Standard `af_` prefixed events map automatically to Meta and TikTok conversion events. Custom events appear in AppsFlyer for funnel and retention analysis.
|
|
508
432
|
|
|
509
|
-
```
|
|
510
|
-
//
|
|
511
|
-
|
|
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)));
|
|
512
437
|
|
|
513
|
-
//
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
// Launch a paywall for a specific offering
|
|
517
|
-
despia('revenuecat://launchPaywall?external_id=USER_ID&offering=default');
|
|
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)));
|
|
518
441
|
|
|
519
|
-
//
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
// - "annual_sale" - special promotional offering
|
|
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)));
|
|
523
445
|
```
|
|
524
446
|
|
|
525
|
-
|
|
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.
|
|
526
448
|
|
|
527
|
-
|
|
449
|
+
Full attribution docs: [https://setup.despia.com/analytics/appsflyer/introduction](https://setup.despia.com/analytics/appsflyer/introduction)
|
|
528
450
|
|
|
529
|
-
|
|
530
|
-
// Define the global callback function
|
|
531
|
-
window.onRevenueCatPurchase = function() {
|
|
532
|
-
console.log('Purchase successful! Polling backend for status update...');
|
|
533
|
-
|
|
534
|
-
// Start polling your backend to check if RevenueCat webhook
|
|
535
|
-
// has updated the user's status or plan permissions
|
|
536
|
-
// This ensures you don't grant access before the webhook processes
|
|
537
|
-
};
|
|
538
|
-
```
|
|
451
|
+
---
|
|
539
452
|
|
|
540
|
-
|
|
453
|
+
### Push Notifications
|
|
541
454
|
|
|
542
|
-
|
|
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.
|
|
543
456
|
|
|
544
|
-
|
|
457
|
+
```js
|
|
458
|
+
// Link device to your user (call on every authenticated app load)
|
|
459
|
+
despia(`setonesignalplayerid://?user_id=${userId}`);
|
|
545
460
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
// npm install despia-native
|
|
461
|
+
// Send a local scheduled notification (fires after 60 seconds)
|
|
462
|
+
despia('sendlocalpushmsg://push.send?s=60&msg=Hello&!#New Message&!#https://myapp.com');
|
|
549
463
|
|
|
550
|
-
//
|
|
551
|
-
|
|
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
|
+
}
|
|
470
|
+
```
|
|
552
471
|
|
|
553
|
-
|
|
554
|
-
await despia(`setvault://?key=${keyName}&value=${value}&locked=${isLocked}`);
|
|
472
|
+
**Send to a specific user from your backend**
|
|
555
473
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
474
|
+
```js
|
|
475
|
+
await fetch('https://onesignal.com/api/v1/notifications', {
|
|
476
|
+
method: 'POST',
|
|
477
|
+
headers: {
|
|
478
|
+
'Content-Type': 'application/json',
|
|
479
|
+
'Authorization': 'Basic YOUR_REST_API_KEY',
|
|
480
|
+
},
|
|
481
|
+
body: JSON.stringify({
|
|
482
|
+
app_id: 'ONESIGNAL-APP-ID',
|
|
483
|
+
include_external_user_ids: [externalUserId],
|
|
484
|
+
headings: { en: title },
|
|
485
|
+
contents: { en: message },
|
|
486
|
+
data: { url: 'https://yourapp.com/messages/123' } // navigate WebView on tap
|
|
487
|
+
}),
|
|
488
|
+
});
|
|
559
489
|
```
|
|
560
490
|
|
|
561
|
-
**
|
|
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)
|
|
562
492
|
|
|
563
|
-
|
|
564
|
-
- **value** - The data to store (text/string)
|
|
565
|
-
- **locked** - Set to `'true'` to require Face ID/fingerprint, `'false'` for normal storage
|
|
493
|
+
---
|
|
566
494
|
|
|
567
|
-
|
|
495
|
+
### OAuth Authentication
|
|
568
496
|
|
|
569
|
-
|
|
570
|
-
- **Cross-device sync** - Works across all user's devices with the same Apple ID or Google account
|
|
571
|
-
- **User tracking** - Identify the same user even after they uninstall and reinstall your app
|
|
572
|
-
- **Face ID protection** - Optional biometric lock for sensitive actions
|
|
573
|
-
- **Automatic timeout** - 30-second timeout prevents app freezing
|
|
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.
|
|
574
498
|
|
|
575
|
-
|
|
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.
|
|
576
500
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
- Storing login session tokens
|
|
580
|
-
- Protecting sensitive actions with Face ID/Touch ID
|
|
581
|
-
- Saving user preferences and app settings
|
|
501
|
+
```js
|
|
502
|
+
const isDespia = navigator.userAgent.toLowerCase().includes('despia');
|
|
582
503
|
|
|
583
|
-
|
|
504
|
+
const redirectUri = isDespia
|
|
505
|
+
? 'https://yourapp.com/native-callback.html'
|
|
506
|
+
: 'https://yourapp.com/auth/callback';
|
|
584
507
|
|
|
585
|
-
|
|
586
|
-
// Store user ID (normal storage)
|
|
587
|
-
await despia(`setvault://?key=userId&value=user123&locked=false`);
|
|
508
|
+
const oauthUrl = `https://provider.com/oauth/authorize?client_id=xxx&redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
588
509
|
|
|
589
|
-
|
|
590
|
-
|
|
510
|
+
if (isDespia) {
|
|
511
|
+
// Step 1: open a secure native browser session
|
|
512
|
+
despia(`oauth://?url=${encodeURIComponent(oauthUrl)}`);
|
|
513
|
+
} else {
|
|
514
|
+
// Regular web flow
|
|
515
|
+
window.location.href = oauthUrl;
|
|
516
|
+
}
|
|
517
|
+
```
|
|
591
518
|
|
|
592
|
-
|
|
593
|
-
const userData = await despia(`readvault://?key=userId`, ['userId']);
|
|
594
|
-
console.log('User ID:', userData.userId);
|
|
519
|
+
`/native-callback.html` runs inside the secure browser session. It receives the tokens or authorization code from the provider, handles the exchange if needed, then fires the deeplink to close the session and return to the app:
|
|
595
520
|
|
|
596
|
-
|
|
597
|
-
|
|
521
|
+
```js
|
|
522
|
+
// Step 2: from inside the callback page, fire the deeplink to return to your app
|
|
523
|
+
window.location.href = `{yourscheme}://oauth/auth?access_token=${token}`;
|
|
598
524
|
```
|
|
599
525
|
|
|
600
|
-
|
|
526
|
+
Despia intercepts the deeplink, closes the browser session, and navigates your WebView to `/auth?access_token=xxx`.
|
|
601
527
|
|
|
602
|
-
|
|
528
|
+
Deeplink format: `{yourscheme}://oauth/{path}?params`
|
|
603
529
|
|
|
604
|
-
|
|
605
|
-
// First, install the package:
|
|
606
|
-
// npm install despia-native
|
|
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.
|
|
607
531
|
|
|
608
|
-
// Then import it:
|
|
609
|
-
import despia from 'despia-native';
|
|
610
532
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
533
|
+
| Deeplink | Result |
|
|
534
|
+
| -------------------------------------------- | ------------------------------------------------------------- |
|
|
535
|
+
| `{yourscheme}://oauth/auth?access_token=xxx` | Browser closes, WebView navigates to `/auth?access_token=xxx` |
|
|
536
|
+
| `{yourscheme}://oauth/home` | Browser closes, WebView navigates to `/home` |
|
|
537
|
+
| `{yourscheme}://auth?access_token=xxx` | Browser stays open, user is stuck |
|
|
615
538
|
|
|
616
|
-
**How it works:**
|
|
617
539
|
|
|
618
|
-
|
|
619
|
-
- **iOS**: ASWebAuthenticationSession (secure Safari sheet)
|
|
620
|
-
- **Android**: Chrome Custom Tabs (secure Chrome overlay)
|
|
540
|
+
**Callback page**
|
|
621
541
|
|
|
622
|
-
|
|
542
|
+
Use a plain HTML file at `public/native-callback.html` rather than a React or Vue route. React Router can strip the `#access_token` hash fragment during a route change, causing tokens to disappear before your callback logic runs. A plain HTML file bypasses the router entirely and reads the hash directly from the browser.
|
|
623
543
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
```
|
|
544
|
+
```html
|
|
545
|
+
<!-- public/native-callback.html -->
|
|
546
|
+
<script>
|
|
547
|
+
var params = new URLSearchParams(window.location.search);
|
|
548
|
+
var hash = new URLSearchParams(window.location.hash.substring(1));
|
|
549
|
+
var code = params.get('code'); // authorization code flow
|
|
550
|
+
var accessToken = hash.get('access_token'); // implicit flow
|
|
632
551
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
552
|
+
if (code) {
|
|
553
|
+
// exchange code via your backend, then fire deeplink with tokens
|
|
554
|
+
} else if (accessToken) {
|
|
555
|
+
window.location.href = '{yourscheme}://oauth/auth?access_token=' + encodeURIComponent(accessToken);
|
|
556
|
+
}
|
|
557
|
+
</script>
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
**Already-mounted `/auth` page**
|
|
638
561
|
|
|
639
|
-
|
|
562
|
+
When Despia navigates the WebView to `/auth`, if that route is already active your framework does not remount the component. Token-reading logic that only runs on mount will not fire again. Fix per framework:
|
|
640
563
|
|
|
641
|
-
|
|
564
|
+
- React: include `searchParams` in your `useEffect` dependency array
|
|
565
|
+
- Vue: use `watch: { '$route.query': { immediate: true, handler } }` instead of reading params in `mounted()`
|
|
566
|
+
- Vanilla JS: call your handler on load and add `window.addEventListener('popstate', handler)`
|
|
642
567
|
|
|
643
|
-
|
|
644
|
-
- `oauth/` - Required prefix that tells native code to close the browser session
|
|
645
|
-
- `{path}` - Where to navigate in your app (e.g., `auth`, `home`, `profile`)
|
|
646
|
-
- `?params` - Query parameters passed to that page
|
|
568
|
+
Full docs: [https://setup.despia.com/native-features/o-auth-2-0](https://setup.despia.com/native-features/o-auth-2-0)
|
|
647
569
|
|
|
648
|
-
|
|
649
|
-
- `myapp://oauth/auth?access_token=xxx` - Closes browser, opens `/auth?access_token=xxx`
|
|
650
|
-
- `myapp://oauth/home` - Closes browser, opens `/home`
|
|
651
|
-
- `myapp://oauth/profile?tab=settings` - Closes browser, opens `/profile?tab=settings`
|
|
570
|
+
---
|
|
652
571
|
|
|
653
|
-
|
|
654
|
-
- Google, Facebook, Apple, GitHub, and other OAuth providers
|
|
655
|
-
- Secure authentication flows without leaving your app
|
|
656
|
-
- Native browser sessions with automatic cleanup
|
|
572
|
+
### Clipboard
|
|
657
573
|
|
|
658
|
-
|
|
574
|
+
```js
|
|
575
|
+
const { clipboarddata } = await despia('getclipboard://', ['clipboarddata']);
|
|
576
|
+
```
|
|
659
577
|
|
|
660
|
-
|
|
578
|
+
---
|
|
661
579
|
|
|
662
|
-
|
|
663
|
-
// First, install the package:
|
|
664
|
-
// npm install despia-native
|
|
580
|
+
### Contacts
|
|
665
581
|
|
|
666
|
-
|
|
667
|
-
|
|
582
|
+
```js
|
|
583
|
+
const { contacts } = await despia('readcontacts://', ['contacts']);
|
|
584
|
+
```
|
|
668
585
|
|
|
669
|
-
|
|
670
|
-
// Ensure that the User Agent string includes "despia" before running this code.
|
|
671
|
-
// If the User Agent string doesn't include "despia" you can use Local Storage as a web fallback.
|
|
586
|
+
---
|
|
672
587
|
|
|
673
|
-
|
|
674
|
-
const userData = { refresh_token: "SSBMT1ZFIERFU1BJQSBOQVRJVkUgU08gTVVDSCBJIFdBTk5BIEtJU1MgSVQh" };
|
|
675
|
-
const encoded = encodeURIComponent(JSON.stringify(userData));
|
|
676
|
-
await despia(`writevalue://${encoded}`);
|
|
588
|
+
### App Information and Device Data
|
|
677
589
|
|
|
678
|
-
|
|
679
|
-
const
|
|
680
|
-
const
|
|
681
|
-
|
|
590
|
+
```js
|
|
591
|
+
const { versionNumber, bundleNumber } = await despia('getappversion://', ['versionNumber', 'bundleNumber']);
|
|
592
|
+
const { uuid } = await despia('get-uuid://', ['uuid']);
|
|
593
|
+
const { storeLocation } = await despia('getstorelocation://', ['storeLocation']);
|
|
594
|
+
const { trackingDisabled } = await despia('user-disable-tracking://', ['trackingDisabled']);
|
|
682
595
|
```
|
|
683
596
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
- **Save data**: Use `writevalue://` with a JSON-encoded string to store data on the device
|
|
687
|
-
- **Retrieve data**: Use `readvalue://` with `["storedValues"]` to get the stored data
|
|
688
|
-
- **Data format**: Data is stored as a single string and returned in the response object
|
|
689
|
-
- **Cross-platform**: Works on both iOS and Android
|
|
690
|
-
- **Lifecycle**: Data persists across app restarts but is cleared on app uninstall
|
|
597
|
+
---
|
|
691
598
|
|
|
692
|
-
|
|
599
|
+
### UI Controls and Styling
|
|
693
600
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
601
|
+
```js
|
|
602
|
+
despia('spinneron://'); // Show loading spinner
|
|
603
|
+
despia('spinneroff://'); // Hide loading spinner
|
|
604
|
+
despia('hidebars://on'); // Hide status bar (full screen)
|
|
605
|
+
despia('hidebars://off'); // Restore status bar
|
|
606
|
+
despia('statusbarcolor://{255, 255, 255}'); // Set status bar background (RGB)
|
|
607
|
+
despia('statusbartextcolor://{black}'); // Set status bar text color: black | white
|
|
608
|
+
despia('settingsapp://'); // Open native app settings
|
|
609
|
+
despia('reset://'); // Reset the app
|
|
610
|
+
```
|
|
697
611
|
|
|
698
|
-
|
|
612
|
+
---
|
|
699
613
|
|
|
700
|
-
|
|
701
|
-
- Caching temporary data
|
|
702
|
-
- Storing session tokens (non-sensitive)
|
|
703
|
-
- App configuration data
|
|
614
|
+
### File and Media Operations
|
|
704
615
|
|
|
705
|
-
|
|
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.
|
|
706
617
|
|
|
707
|
-
|
|
618
|
+
```html
|
|
619
|
+
<!-- Opens a native action sheet with camera, document scanner, and media library -->
|
|
620
|
+
<input type="file">
|
|
708
621
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
// npm install despia-native
|
|
622
|
+
<!-- Opens native media gallery for images -->
|
|
623
|
+
<input type="file" accept="image/*">
|
|
712
624
|
|
|
713
|
-
|
|
714
|
-
|
|
625
|
+
<!-- Opens native media gallery for video -->
|
|
626
|
+
<input type="file" accept="video/*">
|
|
715
627
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
const purchases = data.restoredData;
|
|
719
|
-
console.log(purchases);
|
|
628
|
+
<!-- Opens native camera directly -->
|
|
629
|
+
<input type="file" accept="image/*" capture="environment">
|
|
720
630
|
```
|
|
721
631
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
Despia queries the native platform's billing system to retrieve all purchases associated with the current user's App Store or Google Play account. This includes active subscriptions, expired subscriptions, consumables, and non-consumable (lifetime) purchases. The data is normalized into a consistent format across both iOS and Android platforms.
|
|
725
|
-
|
|
726
|
-
**Response Structure:**
|
|
727
|
-
|
|
728
|
-
Each purchase object in the response array includes:
|
|
729
|
-
|
|
730
|
-
- **transactionId** - Unique identifier for this specific transaction
|
|
731
|
-
- **originalTransactionId** - Identifier linking to the original purchase (useful for subscription renewals)
|
|
732
|
-
- **productId** - The product identifier configured in App Store Connect / Google Play Console
|
|
733
|
-
- **type** - Either `"subscription"` or `"product"` (one-time purchase)
|
|
734
|
-
- **entitlementId** - The entitlement/access level this purchase grants
|
|
735
|
-
- **externalUserId** - External user identifier if configured
|
|
736
|
-
- **isAnonymous** - Boolean indicating if the purchase is anonymous
|
|
737
|
-
- **isActive** - Boolean indicating if the purchase currently grants access
|
|
738
|
-
- **willRenew** - Boolean indicating if a subscription will auto-renew
|
|
739
|
-
- **purchaseDate** - ISO timestamp of the most recent transaction
|
|
740
|
-
- **originalPurchaseDate** - ISO timestamp of the initial purchase
|
|
741
|
-
- **expirationDate** - ISO timestamp when access expires (`null` for lifetime purchases)
|
|
742
|
-
- **store** - Either `"app_store"` or `"play_store"`
|
|
743
|
-
- **country** - User's country code
|
|
744
|
-
- **environment** - `"production"` or `"sandbox"`
|
|
745
|
-
- **receipt** - The raw receipt data for server-side validation
|
|
746
|
-
|
|
747
|
-
**Example Response (iOS):**
|
|
748
|
-
|
|
749
|
-
```javascript
|
|
750
|
-
[
|
|
751
|
-
{
|
|
752
|
-
"transactionId": "1000000987654321",
|
|
753
|
-
"originalTransactionId": "1000000123456789",
|
|
754
|
-
"productId": "com.app.premium.monthly",
|
|
755
|
-
"type": "subscription",
|
|
756
|
-
"entitlementId": "premium",
|
|
757
|
-
"externalUserId": "abc123",
|
|
758
|
-
"isAnonymous": false,
|
|
759
|
-
"isActive": true,
|
|
760
|
-
"willRenew": true,
|
|
761
|
-
"purchaseDate": "2024-01-15T14:32:05Z",
|
|
762
|
-
"originalPurchaseDate": "2023-06-20T09:15:33Z",
|
|
763
|
-
"expirationDate": "2024-02-15T14:32:05Z",
|
|
764
|
-
"store": "app_store",
|
|
765
|
-
"country": "USA",
|
|
766
|
-
"receipt": "MIIbngYJKoZIhvcNAQcCoIIbajCCG2YCAQExDzAN...",
|
|
767
|
-
"environment": "production"
|
|
768
|
-
},
|
|
769
|
-
{
|
|
770
|
-
"transactionId": "1000000555555555",
|
|
771
|
-
"originalTransactionId": "1000000555555555",
|
|
772
|
-
"productId": "com.app.removeads",
|
|
773
|
-
"type": "product",
|
|
774
|
-
"entitlementId": "no_ads",
|
|
775
|
-
"externalUserId": "abc123",
|
|
776
|
-
"isAnonymous": false,
|
|
777
|
-
"isActive": true,
|
|
778
|
-
"willRenew": false,
|
|
779
|
-
"purchaseDate": "2023-12-01T08:00:00Z",
|
|
780
|
-
"originalPurchaseDate": "2023-12-01T08:00:00Z",
|
|
781
|
-
"expirationDate": null,
|
|
782
|
-
"store": "app_store",
|
|
783
|
-
"country": "USA",
|
|
784
|
-
"receipt": "MIIbngYJKoZIhvcNAQcCoIIbajCCG2YCAQExDzAN...",
|
|
785
|
-
"environment": "production"
|
|
786
|
-
}
|
|
787
|
-
]
|
|
788
|
-
```
|
|
632
|
+
Additional file and media commands:
|
|
789
633
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
"originalTransactionId": "GPA.3372-4150-9088-12345",
|
|
797
|
-
"productId": "com.app.premium.monthly",
|
|
798
|
-
"type": "subscription",
|
|
799
|
-
"entitlementId": "premium",
|
|
800
|
-
"externalUserId": "abc123",
|
|
801
|
-
"isAnonymous": false,
|
|
802
|
-
"isActive": true,
|
|
803
|
-
"willRenew": true,
|
|
804
|
-
"purchaseDate": "2024-01-15T14:32:05Z",
|
|
805
|
-
"originalPurchaseDate": "2023-06-20T09:15:33Z",
|
|
806
|
-
"expirationDate": "2024-02-15T14:32:05Z",
|
|
807
|
-
"store": "play_store",
|
|
808
|
-
"country": "US",
|
|
809
|
-
"receipt": "kefhajglhaljhfajkfajk.AO-J1OxBnT3hAjkl5FjpKc9...",
|
|
810
|
-
"environment": "production"
|
|
811
|
-
},
|
|
812
|
-
{
|
|
813
|
-
"transactionId": "GPA.3372-4150-9088-67890",
|
|
814
|
-
"originalTransactionId": "GPA.3372-4150-9088-67890",
|
|
815
|
-
"productId": "com.app.removeads",
|
|
816
|
-
"type": "product",
|
|
817
|
-
"entitlementId": "no_ads",
|
|
818
|
-
"externalUserId": "abc123",
|
|
819
|
-
"isAnonymous": false,
|
|
820
|
-
"isActive": true,
|
|
821
|
-
"willRenew": false,
|
|
822
|
-
"purchaseDate": "2023-12-01T08:00:00Z",
|
|
823
|
-
"originalPurchaseDate": "2023-12-01T08:00:00Z",
|
|
824
|
-
"expirationDate": null,
|
|
825
|
-
"store": "play_store",
|
|
826
|
-
"country": "US",
|
|
827
|
-
"receipt": "minodkpfokbofclncmaa.AO-J1Oy2fXpTml7rKxE3vNc9...",
|
|
828
|
-
"environment": "production"
|
|
829
|
-
}
|
|
830
|
-
]
|
|
634
|
+
```js
|
|
635
|
+
despia('takescreenshot://');
|
|
636
|
+
despia('savethisimage://?url=https://example.com/image.jpg');
|
|
637
|
+
despia('file://https://example.com/document.pdf');
|
|
638
|
+
despia('shareapp://message?=Check%20out%20this%20app&url=https://myapp.com');
|
|
639
|
+
despia('scanningmode://auto'); // auto | on | off
|
|
831
640
|
```
|
|
832
641
|
|
|
833
|
-
|
|
642
|
+
---
|
|
834
643
|
|
|
835
|
-
|
|
836
|
-
const data = await despia("getpurchasehistory://", ["restoredData"]);
|
|
837
|
-
const purchases = data.restoredData;
|
|
644
|
+
### Apple Health (HealthKit)
|
|
838
645
|
|
|
839
|
-
|
|
840
|
-
const activePurchases = purchases.filter(p => p.isActive);
|
|
646
|
+
iOS only. Always gate behind `isDespiaIOS`. Despia requests permissions on the first call for each identifier.
|
|
841
647
|
|
|
842
|
-
|
|
843
|
-
|
|
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" }, ...]
|
|
844
653
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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');
|
|
848
659
|
```
|
|
849
660
|
|
|
850
|
-
|
|
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)
|
|
851
662
|
|
|
852
|
-
|
|
853
|
-
- Verifying user entitlements on app launch
|
|
854
|
-
- Checking subscription status and renewal information
|
|
855
|
-
- Server-side receipt validation using raw receipt data
|
|
856
|
-
- Cross-platform purchase history retrieval
|
|
663
|
+
---
|
|
857
664
|
|
|
858
|
-
|
|
665
|
+
### AdMob Inline Ads
|
|
859
666
|
|
|
860
|
-
|
|
861
|
-
- No additional native libraries are needed
|
|
862
|
-
- Please follow the installation instructions for the `despia-native` npm package closely
|
|
863
|
-
- Implementation as mentioned is critical for App Store compliance
|
|
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.
|
|
864
668
|
|
|
865
|
-
|
|
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.
|
|
866
670
|
|
|
867
|
-
|
|
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
|
+
```
|
|
868
683
|
|
|
869
|
-
|
|
870
|
-
// First, install the package:
|
|
871
|
-
// npm install despia-native
|
|
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.
|
|
872
685
|
|
|
873
|
-
|
|
874
|
-
import despia from 'despia-native';
|
|
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)
|
|
875
687
|
|
|
876
|
-
|
|
877
|
-
const clipboardData = await despia('getclipboard://', ['clipboarddata']);
|
|
688
|
+
---
|
|
878
689
|
|
|
879
|
-
|
|
880
|
-
const content = clipboardData.clipboarddata;
|
|
690
|
+
### Web Payment Request API
|
|
881
691
|
|
|
882
|
-
|
|
883
|
-
console.log('Clipboard content:', content);
|
|
884
|
-
```
|
|
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.
|
|
885
693
|
|
|
886
|
-
|
|
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
|
+
);
|
|
887
702
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
703
|
+
const result = await request.show();
|
|
704
|
+
await result.complete('success');
|
|
705
|
+
```
|
|
891
706
|
|
|
892
|
-
|
|
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)
|
|
893
708
|
|
|
894
|
-
|
|
895
|
-
- Processing clipboard content in your app
|
|
896
|
-
- Implementing paste functionality
|
|
897
|
-
- Clipboard content validation
|
|
709
|
+
---
|
|
898
710
|
|
|
899
|
-
###
|
|
711
|
+
### Web Storage APIs
|
|
900
712
|
|
|
901
|
-
|
|
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:
|
|
902
714
|
|
|
903
|
-
```
|
|
904
|
-
//
|
|
905
|
-
|
|
715
|
+
```js
|
|
716
|
+
// localStorage works normally
|
|
717
|
+
localStorage.setItem('userId', 'user123');
|
|
718
|
+
const userId = localStorage.getItem('userId');
|
|
906
719
|
|
|
907
|
-
//
|
|
908
|
-
|
|
720
|
+
// IndexedDB works normally
|
|
721
|
+
const db = await indexedDB.open('myapp', 1);
|
|
909
722
|
|
|
910
|
-
//
|
|
911
|
-
|
|
723
|
+
// Web Crypto works normally
|
|
724
|
+
const key = await crypto.subtle.generateKey(
|
|
725
|
+
{ name: 'AES-GCM', length: 256 },
|
|
726
|
+
true,
|
|
727
|
+
['encrypt', 'decrypt']
|
|
728
|
+
);
|
|
912
729
|
```
|
|
913
730
|
|
|
914
|
-
|
|
731
|
+
For data that needs to survive uninstall and reinstall, or be locked behind Face ID, use [Identity Vault](#identity-vault) instead.
|
|
915
732
|
|
|
916
|
-
|
|
917
|
-
- **Permission management**: Users can manage permissions like notifications, location, camera, microphone, and more
|
|
918
|
-
- **One-time prompts**: Great for directing users to activate features that you can only ask once, like location or push notifications
|
|
733
|
+
---
|
|
919
734
|
|
|
920
|
-
|
|
735
|
+
### Local Server
|
|
921
736
|
|
|
922
|
-
-
|
|
923
|
-
- Helping users enable push notifications if they initially declined
|
|
924
|
-
- Managing camera, microphone, or other permission settings
|
|
925
|
-
- Providing a way to access app settings when permission prompts can't be shown again
|
|
737
|
+
> **Built-in OTA updates. True offline support. Host with your existing web hosting. No proprietary infrastructure required.**
|
|
926
738
|
|
|
927
|
-
|
|
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.
|
|
928
740
|
|
|
929
|
-
|
|
930
|
-
// Check if location permission is needed
|
|
931
|
-
if (!navigator.geolocation) {
|
|
932
|
-
// Open settings so user can enable location
|
|
933
|
-
despia("settingsapp://");
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
// After user denies push notification permission
|
|
937
|
-
// Provide a button to open settings
|
|
938
|
-
function openNotificationSettings() {
|
|
939
|
-
despia("settingsapp://");
|
|
940
|
-
}
|
|
941
|
-
```
|
|
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.
|
|
942
742
|
|
|
943
|
-
|
|
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.
|
|
944
744
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
```
|
|
948
|
-
// First, install the package:
|
|
949
|
-
// npm install despia-native
|
|
745
|
+
```bash
|
|
746
|
+
npm install --save-dev @despia/local
|
|
747
|
+
```
|
|
950
748
|
|
|
951
|
-
|
|
952
|
-
|
|
749
|
+
```js
|
|
750
|
+
// vite.config.js (also available for Webpack, Rollup, Nuxt, SvelteKit, Astro, Remix, esbuild)
|
|
751
|
+
import { defineConfig } from 'vite';
|
|
752
|
+
import { despiaLocalPlugin } from '@despia/local/vite';
|
|
953
753
|
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
754
|
+
export default defineConfig({
|
|
755
|
+
plugins: [
|
|
756
|
+
despiaLocalPlugin({
|
|
757
|
+
outDir: 'dist',
|
|
758
|
+
entryHtml: 'index.html'
|
|
759
|
+
})
|
|
760
|
+
]
|
|
761
|
+
});
|
|
957
762
|
```
|
|
958
763
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
3. **Important**: When configuring OneSignal, select **"Native iOS"** and **"Native Android"** platforms since Despia apps are native mobile applications
|
|
964
|
-
4. **Add your OneSignal App ID** to your Despia project settings
|
|
965
|
-
|
|
966
|
-
**How it works:**
|
|
967
|
-
|
|
968
|
-
- **External User IDs**: External IDs (your database user IDs) are now the default and recommended approach
|
|
969
|
-
- **Player IDs**: Player IDs still work but are no longer suggested
|
|
970
|
-
- **Device Registration**: Devices are automatically registered in OneSignal when the app is installed
|
|
971
|
-
- **User Connection**: Calling `setonesignalplayerid://` on every app load connects your user ID with the device registration
|
|
972
|
-
|
|
973
|
-
**Backend Integration Required:**
|
|
974
|
-
|
|
975
|
-
You'll need to create a backend endpoint to send notifications using OneSignal's REST API with external user IDs:
|
|
976
|
-
|
|
977
|
-
```javascript
|
|
978
|
-
// Backend API endpoint - send to specific user
|
|
979
|
-
const sendNotification = async (externalUserId, title, message) => {
|
|
980
|
-
const response = await fetch('https://onesignal.com/api/v1/notifications', {
|
|
981
|
-
method: 'POST',
|
|
982
|
-
headers: {
|
|
983
|
-
'Content-Type': 'application/json',
|
|
984
|
-
'Authorization': 'Basic YOUR_REST_API_KEY'
|
|
985
|
-
},
|
|
986
|
-
body: JSON.stringify({
|
|
987
|
-
app_id: 'ONESIGNAL-APP-ID',
|
|
988
|
-
include_external_user_ids: [externalUserId], // Array with single ID
|
|
989
|
-
headings: { en: title },
|
|
990
|
-
contents: { en: message }
|
|
991
|
-
})
|
|
992
|
-
});
|
|
993
|
-
return response.json();
|
|
994
|
-
};
|
|
764
|
+
Or run via CLI after any build:
|
|
765
|
+
|
|
766
|
+
```bash
|
|
767
|
+
npx despia-local dist
|
|
995
768
|
```
|
|
996
769
|
|
|
997
|
-
|
|
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.
|
|
998
771
|
|
|
999
|
-
```
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
772
|
+
```json
|
|
773
|
+
{
|
|
774
|
+
"entry": "/index.html",
|
|
775
|
+
"deployed_at": "1737225600000",
|
|
776
|
+
"assets": [
|
|
777
|
+
"/index.html",
|
|
778
|
+
"/assets/app.abc123.css",
|
|
779
|
+
"/assets/app.def456.js"
|
|
780
|
+
]
|
|
1007
781
|
}
|
|
1008
|
-
|
|
1009
|
-
// Send local push notification
|
|
1010
|
-
despia('sendlocalpushmsg://push.send?s=60&msg=Hello&!#New Message&!#https://myapp.com');
|
|
1011
782
|
```
|
|
1012
783
|
|
|
1013
|
-
|
|
784
|
+
Full docs: [https://setup.despia.com/local-server/introduction](https://setup.despia.com/local-server/introduction)
|
|
1014
785
|
|
|
1015
|
-
|
|
1016
|
-
- No additional native libraries are needed
|
|
1017
|
-
- Please follow the installation instructions for the `despia-native` npm package closely
|
|
1018
|
-
- Implementation as mentioned is critical for proper push notification delivery
|
|
1019
|
-
- Call `setonesignalplayerid://` on every app load to ensure the user ID is always connected
|
|
786
|
+
---
|
|
1020
787
|
|
|
1021
|
-
|
|
788
|
+
### Local CDN
|
|
1022
789
|
|
|
1023
|
-
-
|
|
1024
|
-
- User-based notification management
|
|
1025
|
-
- Cross-device notification delivery
|
|
1026
|
-
- Personalized push notification campaigns
|
|
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.
|
|
1027
791
|
|
|
1028
|
-
|
|
792
|
+
```js
|
|
793
|
+
// Called by the native runtime when a download completes.
|
|
794
|
+
// This is a global function, not an event listener.
|
|
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
|
|
1029
802
|
|
|
1030
|
-
|
|
803
|
+
console.log('Cached:', item.index, item.local_cdn);
|
|
804
|
+
addToDownloadsList(item);
|
|
805
|
+
};
|
|
1031
806
|
|
|
1032
|
-
|
|
1033
|
-
//
|
|
1034
|
-
despia(
|
|
1035
|
-
|
|
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.
|
|
809
|
+
despia(
|
|
810
|
+
`localcdn://write?url=${remoteUrl}&filename=videos/clip.mp4&index=clip_1&push=true&pushmessage="Download complete"`
|
|
811
|
+
);
|
|
1036
812
|
|
|
1037
|
-
//
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
813
|
+
// Read cached items by ID
|
|
814
|
+
const { cdnItems } = await despia(
|
|
815
|
+
`localcdn://read?index=${encodeURIComponent(JSON.stringify(['clip_1']))}`,
|
|
816
|
+
['cdnItems']
|
|
817
|
+
);
|
|
1041
818
|
|
|
1042
|
-
//
|
|
1043
|
-
|
|
1044
|
-
// - Success notifications (successhaptic)
|
|
1045
|
-
// - Warning alerts (warninghaptic)
|
|
1046
|
-
// - Error feedback (errorhaptic)
|
|
1047
|
-
// - UI interaction confirmation
|
|
819
|
+
// Or query everything in the cache
|
|
820
|
+
const { cdnItems: all } = await despia('localcdn://query', ['cdnItems']);
|
|
1048
821
|
```
|
|
1049
822
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
Biometric authentication requires setting up callback functions before running the command:
|
|
1053
|
-
|
|
1054
|
-
```javascript
|
|
1055
|
-
// Step 1: Set up the biometric authentication SDK
|
|
1056
|
-
if (!document.getElementById("bioauth-sdk")) {
|
|
1057
|
-
const script = document.createElement("script")
|
|
1058
|
-
script.id = "bioauth-sdk"
|
|
1059
|
-
script.type = "text/javascript"
|
|
1060
|
-
script.textContent = `
|
|
1061
|
-
function onBioAuthSuccess() {
|
|
1062
|
-
window.bioauthSuccess()
|
|
1063
|
-
}
|
|
1064
|
-
function onBioAuthFailure(errorCode, errorMessage) {
|
|
1065
|
-
window.bioauthFailure(errorCode, errorMessage)
|
|
1066
|
-
}
|
|
1067
|
-
function onBioAuthUnavailable() {
|
|
1068
|
-
window.bioauthUnavailable()
|
|
1069
|
-
}
|
|
1070
|
-
`
|
|
1071
|
-
document.head.appendChild(script)
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
// Step 2: Define your callback functions
|
|
1075
|
-
window.bioauthSuccess = function() {
|
|
1076
|
-
if (navigator.userAgent.includes("despia")) {
|
|
1077
|
-
console.log("Biometric authentication successful");
|
|
1078
|
-
// Handle successful authentication
|
|
1079
|
-
// Redirect user, unlock features, etc.
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
window.bioauthFailure = function(errorCode, errorMessage) {
|
|
1084
|
-
if (navigator.userAgent.includes("despia")) {
|
|
1085
|
-
console.log("Biometric authentication failed:", errorCode, errorMessage);
|
|
1086
|
-
// Handle authentication failure
|
|
1087
|
-
// Show error message, fallback to password, etc.
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
823
|
+
Use the `local_cdn` URL for playback:
|
|
1090
824
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
console.log("Biometric authentication unavailable");
|
|
1094
|
-
// Handle when biometric auth is not available
|
|
1095
|
-
// Fallback to alternative authentication method
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
// Step 3: Trigger biometric authentication
|
|
1100
|
-
despia('bioauth://');
|
|
825
|
+
```html
|
|
826
|
+
<video src="http://localhost:7777/localcdn/videos/clip.mp4" controls></video>
|
|
1101
827
|
```
|
|
1102
828
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
```javascript
|
|
1106
|
-
// Get app version information
|
|
1107
|
-
const appInfo = await despia('getappversion://', ['versionNumber', 'bundleNumber']);
|
|
1108
|
-
console.log('App Version:', appInfo.versionNumber);
|
|
1109
|
-
console.log('Bundle Number:', appInfo.bundleNumber);
|
|
829
|
+
**HTTP upload API** (Local Server only)
|
|
1110
830
|
|
|
1111
|
-
|
|
1112
|
-
const
|
|
1113
|
-
|
|
831
|
+
```js
|
|
832
|
+
const host = window.location.host; // Do not hardcode the port; it rotates per session
|
|
833
|
+
const fd = new FormData();
|
|
834
|
+
fd.append('file', fileInput.files[0]);
|
|
1114
835
|
|
|
1115
|
-
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
// Check tracking permission
|
|
1120
|
-
const trackingData = await despia('user-disable-tracking://', ['trackingDisabled']);
|
|
1121
|
-
console.log('Tracking Disabled:', trackingData.trackingDisabled);
|
|
836
|
+
const res = await fetch(`http://${host}/api/upload`, { method: 'POST', body: fd });
|
|
837
|
+
const data = await res.json();
|
|
838
|
+
// { success: true, fileName: "video.mp4", url: "http://localhost:7777/files/video.mp4" }
|
|
1122
839
|
```
|
|
1123
840
|
|
|
1124
|
-
### UI Controls & Styling
|
|
1125
841
|
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
842
|
+
| Method | Storage Path | URL Pattern |
|
|
843
|
+
| ------------------ | ------------ | -------------------------------------- |
|
|
844
|
+
| `localcdn://write` | `/localcdn/` | `localhost:{PORT}/localcdn/{filepath}` |
|
|
845
|
+
| `/api/upload` | `/files/` | `localhost:{PORT}/files/{filename}` |
|
|
1130
846
|
|
|
1131
|
-
// Full screen mode
|
|
1132
|
-
await despia('hidebars://on'); // Hide status bar (full screen)
|
|
1133
|
-
await despia('hidebars://off'); // Show status bar
|
|
1134
847
|
|
|
1135
|
-
|
|
1136
|
-
await despia('statusbarcolor://{255, 255, 255}'); // Set status bar background color (RGB)
|
|
1137
|
-
await despia('statusbartextcolor://{black}'); // Set status bar text color (black/white)
|
|
1138
|
-
```
|
|
848
|
+
Full docs: [https://setup.despia.com/local-cdn/introduction](https://setup.despia.com/local-cdn/introduction)
|
|
1139
849
|
|
|
1140
|
-
|
|
850
|
+
---
|
|
1141
851
|
|
|
1142
|
-
|
|
1143
|
-
// Take screenshot (saves to device)
|
|
1144
|
-
await despia('takescreenshot://');
|
|
852
|
+
## Capability Reference
|
|
1145
853
|
|
|
1146
|
-
|
|
1147
|
-
await despia('savethisimage://?url=https://example.com/image.jpg');
|
|
854
|
+
A full index of native capabilities built into Despia.
|
|
1148
855
|
|
|
1149
|
-
|
|
1150
|
-
await despia('file://https://example.com/document.pdf');
|
|
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.
|
|
1151
857
|
|
|
1152
|
-
|
|
1153
|
-
await despia('shareapp://message?=Check%20out%20this%20app&url=https://myapp.com');
|
|
1154
|
-
```
|
|
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).
|
|
1155
859
|
|
|
1156
|
-
|
|
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.
|
|
1157
861
|
|
|
1158
|
-
|
|
1159
|
-
// Control scanning mode
|
|
1160
|
-
await despia('scanningmode://auto'); // Auto scanning mode
|
|
1161
|
-
await despia('scanningmode://on'); // Enable scanning
|
|
1162
|
-
await despia('scanningmode://off'); // Disable scanning
|
|
1163
|
-
```
|
|
1164
|
-
|
|
1165
|
-
### App Reset
|
|
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.
|
|
1166
863
|
|
|
1167
|
-
|
|
1168
|
-
// Reset app (use with caution)
|
|
1169
|
-
await despia('reset://');
|
|
1170
|
-
```
|
|
864
|
+
---
|
|
1171
865
|
|
|
1172
|
-
|
|
866
|
+
## Safe Area
|
|
1173
867
|
|
|
1174
|
-
|
|
868
|
+
Despia exposes top and bottom safe area insets as CSS custom properties set by the native runtime.
|
|
1175
869
|
|
|
1176
870
|
```css
|
|
1177
|
-
|
|
1178
|
-
.my-element {
|
|
871
|
+
.header {
|
|
1179
872
|
padding-top: var(--safe-area-top);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
.footer {
|
|
1180
876
|
padding-bottom: var(--safe-area-bottom);
|
|
1181
877
|
}
|
|
1182
878
|
|
|
1183
|
-
/* Full height with safe area consideration */
|
|
1184
879
|
.full-height {
|
|
1185
880
|
height: calc(100vh - var(--safe-area-top) - var(--safe-area-bottom));
|
|
1186
881
|
}
|
|
1187
882
|
```
|
|
1188
883
|
|
|
1189
|
-
|
|
884
|
+
Note: left and right safe area variables are not available.
|
|
1190
885
|
|
|
1191
|
-
|
|
886
|
+
---
|
|
1192
887
|
|
|
888
|
+
## Extending the Runtime
|
|
1193
889
|
|
|
890
|
+
A common concern when adopting any hybrid framework is lock-in: can you escape the custom protocol API later?
|
|
1194
891
|
|
|
1195
|
-
|
|
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.
|
|
1196
893
|
|
|
1197
|
-
|
|
894
|
+
**On the bridge architecture**
|
|
1198
895
|
|
|
1199
|
-
|
|
1200
|
-
- **watch** (string[], optional): Array of variable names to watch for in the response
|
|
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.
|
|
1201
897
|
|
|
1202
|
-
|
|
1203
|
-
- **Single variable**: 30-second timeout with observation. `null` values resolve immediately (useful for error/not-found signals). Other empty placeholders (`undefined`, `"n/a"`, `{}`, `[]`) are ignored. Promise always resolves; on timeout it resolves with `undefined`.
|
|
1204
|
-
- **Multiple variables**: Uses VariableTracker with 5-minute auto-cleanup. All variables must have non-null values. Any `null` value blocks resolution until all variables have real values.
|
|
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.
|
|
1205
899
|
|
|
1206
|
-
|
|
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.
|
|
1207
901
|
|
|
1208
|
-
|
|
1209
|
-
`window.someVariable` to be set by the native runtime. If it appears earlier, the
|
|
1210
|
-
Promise resolves with that value. If it is never set, the Promise still resolves
|
|
1211
|
-
after 30 seconds with `undefined` and a timeout is logged to the console. This
|
|
1212
|
-
prevents hanging Promises for long-running or failing native operations.
|
|
902
|
+
**iOS: WebViewController.swift**
|
|
1213
903
|
|
|
1214
|
-
|
|
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
|
+
```
|
|
1215
912
|
|
|
1216
|
-
|
|
1217
|
-
The observer behavior differs by variable count:
|
|
913
|
+
**Android: MainActivity.java**
|
|
1218
914
|
|
|
1219
|
-
|
|
915
|
+
```java
|
|
916
|
+
if (url.startsWith("mycustom://")) {
|
|
917
|
+
String result = runMyNativeCode();
|
|
918
|
+
webView.evaluateJavascript("window.myResult = '" + result + "';", null);
|
|
919
|
+
return true;
|
|
920
|
+
}
|
|
921
|
+
```
|
|
1220
922
|
|
|
1221
|
-
|
|
923
|
+
**Call it from your web app**
|
|
1222
924
|
|
|
1223
|
-
|
|
925
|
+
```js
|
|
926
|
+
import despia from 'despia-native';
|
|
1224
927
|
|
|
1225
|
-
|
|
928
|
+
const data = await despia('mycustom://', ['myResult']);
|
|
929
|
+
console.log(data.myResult);
|
|
930
|
+
```
|
|
1226
931
|
|
|
1227
|
-
|
|
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.
|
|
1228
933
|
|
|
1229
|
-
|
|
1230
|
-
despia.variableName // Equivalent to window.variableName
|
|
1231
|
-
```
|
|
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.
|
|
1232
935
|
|
|
1233
|
-
|
|
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.
|
|
1234
937
|
|
|
1235
|
-
Despia
|
|
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.
|
|
1236
939
|
|
|
1237
|
-
|
|
1238
|
-
feature://action?parameters
|
|
1239
|
-
```
|
|
940
|
+
---
|
|
1240
941
|
|
|
1241
|
-
|
|
1242
|
-
- `lighthaptic://`
|
|
1243
|
-
- `getappversion://`
|
|
1244
|
-
- `revenuecat://purchase?external_id=user_777&product=monthly_premium`
|
|
1245
|
-
- `revenuecat://launchPaywall?external_id=user_777&offering=default`
|
|
1246
|
-
- `getpurchasehistory://`
|
|
1247
|
-
- `getclipboard://`
|
|
1248
|
-
- `settingsapp://`
|
|
1249
|
-
- `setonesignalplayerid://?user_id=user123`
|
|
1250
|
-
- `registerpush://`
|
|
1251
|
-
- `setvault://?key=userId&value=user123&locked=false`
|
|
1252
|
-
- `readvault://?key=userId`
|
|
1253
|
-
- `writevalue://{JSON-ENCODED-STRING}`
|
|
1254
|
-
- `readvalue://`
|
|
1255
|
-
- `oauth://?url=https://provider.com/oauth/authorize`
|
|
1256
|
-
- `requestcontactpermission://`
|
|
1257
|
-
- `savethisimage://?url=https://example.com/image.jpg`
|
|
1258
|
-
|
|
1259
|
-
## Available Despia Features
|
|
1260
|
-
|
|
1261
|
-
Your app can access these native features:
|
|
1262
|
-
|
|
1263
|
-
- **Native Widgets** - Create widgets with SVG and refresh time
|
|
1264
|
-
- **In-App Purchases** - RevenueCat integration with external user IDs
|
|
1265
|
-
- **Restore Purchases** - Retrieve purchase history from App Store and Google Play
|
|
1266
|
-
- **Contact Access** - Request permissions and read contacts
|
|
1267
|
-
- **Background Location** - Native tracking with browser geolocation API
|
|
1268
|
-
- **Push Notifications** - OneSignal integration with external user IDs and local push messages
|
|
1269
|
-
- **Haptic Feedback** - Light, heavy, success, warning, and error feedback
|
|
1270
|
-
- **App Information** - Version numbers, bundle numbers, device UUID
|
|
1271
|
-
- **Clipboard Access** - Read clipboard content from device
|
|
1272
|
-
- **Screenshots** - Take device screenshots
|
|
1273
|
-
- **Scanning Mode** - Auto, on, and off scanning controls
|
|
1274
|
-
- **Store Location** - Get store location data
|
|
1275
|
-
- **File Operations** - Save images and download files
|
|
1276
|
-
- **Identity Vault** - Persistent cross-device storage with optional biometric protection
|
|
1277
|
-
- **Local Storage** - Cross-platform device storage (cleared on uninstall)
|
|
1278
|
-
- **OAuth Authentication** - Secure OAuth flows with native browser sessions
|
|
1279
|
-
- **App Control** - Reset app and disable tracking
|
|
1280
|
-
- **App Settings** - Open native app settings for permission management
|
|
1281
|
-
- **UI Controls** - Loading spinners and full screen mode
|
|
1282
|
-
- **Sharing** - Share app with custom messages and URLs
|
|
1283
|
-
- **Status Bar** - Control colors and text colors
|
|
1284
|
-
- **Biometric Authentication** - Native biometric auth with callbacks
|
|
1285
|
-
|
|
1286
|
-
## TypeScript Support
|
|
1287
|
-
|
|
1288
|
-
Full TypeScript definitions are included:
|
|
1289
|
-
|
|
1290
|
-
```typescript
|
|
1291
|
-
import despia from 'despia-native';
|
|
942
|
+
## Web Apps vs React Native
|
|
1292
943
|
|
|
1293
|
-
|
|
1294
|
-
const result: { versionNumber: string; bundleNumber: string } = await despia(
|
|
1295
|
-
'getappversion://',
|
|
1296
|
-
['versionNumber', 'bundleNumber']
|
|
1297
|
-
);
|
|
944
|
+
This SDK is for web apps running inside the Despia runtime: React, Vue, Angular, Svelte, Next.js, Vite, Nuxt, and vanilla JavaScript.
|
|
1298
945
|
|
|
1299
|
-
|
|
1300
|
-
const deviceInfo: any = despia.deviceInfo;
|
|
1301
|
-
```
|
|
946
|
+
It is not for React Native, Expo, or native mobile development.
|
|
1302
947
|
|
|
1303
|
-
|
|
948
|
+
---
|
|
1304
949
|
|
|
1305
|
-
|
|
950
|
+
## Publishing to the App Store and Google Play
|
|
1306
951
|
|
|
1307
|
-
|
|
1308
|
-
- **Variable Watching** - Async monitoring of response variables
|
|
1309
|
-
- **Hybrid Framework Compatible** - Works with Despia's hybrid app framework
|
|
1310
|
-
- **Direct Access** - Proxy-based access to window variables
|
|
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.
|
|
1311
953
|
|
|
1312
|
-
|
|
1313
|
-
Despia's protocol handler system eliminates the need for complex libraries or dependencies, making it compatible across various frameworks and platforms. The SDK uses the setter pattern to execute commands:
|
|
954
|
+
Full deployment docs: [https://setup.despia.com/deployment/apple-ios/automatic](https://setup.despia.com/deployment/apple-ios/automatic)
|
|
1314
955
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
956
|
+
---
|
|
957
|
+
|
|
958
|
+
## Open Source
|
|
959
|
+
|
|
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 |
|
|
1318
966
|
|
|
1319
|
-
// It internally executes:
|
|
1320
|
-
window.despia = 'lighthaptic://';
|
|
1321
|
-
```
|
|
1322
967
|
|
|
1323
|
-
|
|
968
|
+
Native capability implementations are written in Swift and Java and included in full on project export.
|
|
969
|
+
|
|
970
|
+
---
|
|
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
|
+
---
|
|
1324
977
|
|
|
1325
978
|
## License
|
|
1326
979
|
|