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