despia-native 1.0.20 → 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 -1089
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,1326 +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
|
|
95
|
-
npm install despia-native
|
|
96
|
-
|
|
97
|
-
# pnpm
|
|
98
|
-
pnpm install despia-native
|
|
99
|
-
|
|
100
|
-
# yarn
|
|
101
|
-
yarn add despia-native
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
**This is NOT optional. Your app will NOT work without this package.**
|
|
105
|
-
|
|
106
|
-
**Package Manager Support:**
|
|
107
|
-
- ✅ **npm** - Fully supported
|
|
108
|
-
- ✅ **pnpm** - Fully supported (v1.0.19+)
|
|
109
|
-
- ✅ **yarn** - Fully supported
|
|
110
|
-
|
|
111
|
-
**Do not attempt to:**
|
|
112
|
-
- Write your own version
|
|
113
|
-
- Copy/paste a mock implementation
|
|
114
|
-
- Use window.despia directly in frameworks
|
|
115
|
-
- Create a "simplified" version
|
|
116
|
-
|
|
117
|
-
**The real SDK is required for ANY Despia functionality to work.**
|
|
118
|
-
|
|
119
|
-
## Important Notes
|
|
120
|
-
|
|
121
|
-
### Simple Function API
|
|
122
|
-
The SDK exports a single function - no initialization or setup required:
|
|
123
|
-
|
|
124
|
-
```javascript
|
|
125
|
-
import despia from 'despia-native';
|
|
126
|
-
|
|
127
|
-
// Just call it directly
|
|
128
|
-
despia('lighthaptic://');
|
|
129
|
-
despia('takescreenshot://');
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### Common Mistakes to Avoid
|
|
133
|
-
```javascript
|
|
134
|
-
// Don't create mock implementations
|
|
135
|
-
const despia = (command) => console.log(command); // Won't work on devices
|
|
136
|
-
|
|
137
|
-
// Don't use non-existent methods
|
|
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
|
-
```
|
|
142
|
-
|
|
143
|
-
## Getting Started
|
|
144
|
-
|
|
145
|
-
### Step 1: Import Despia SDK
|
|
146
|
-
|
|
147
|
-
**IMPORTANT: Always import as `despia` (default export), NOT as `{Commands}` or destructured imports!**
|
|
148
|
-
|
|
149
|
-
```javascript
|
|
150
|
-
// CORRECT - ES6/ES2015 modules (default import)
|
|
151
|
-
import despia from 'despia-native';
|
|
16
|
+
## MCP Server
|
|
152
17
|
|
|
153
|
-
|
|
154
|
-
const despia = require('despia-native');
|
|
18
|
+
Add the Despia MCP to give your AI assistant full knowledge of the `despia-native` API.
|
|
155
19
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
// despia is available globally
|
|
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)
|
|
159
22
|
|
|
160
|
-
// WRONG - Don't do this!
|
|
161
|
-
// import { Commands } from 'despia-native';
|
|
162
|
-
// import { despia } from 'despia-native';
|
|
163
23
|
```
|
|
164
|
-
|
|
165
|
-
**The SDK exports a single function called `despia` as the default export.**
|
|
166
|
-
|
|
167
|
-
### Step 2: Use Native Features (No Setup Required)
|
|
168
|
-
|
|
169
|
-
```javascript
|
|
170
|
-
// That's it! No initialization needed. Just call despia() directly:
|
|
171
|
-
|
|
172
|
-
// Simple commands (no response needed)
|
|
173
|
-
despia('lighthaptic://'); // Light haptic feedback
|
|
174
|
-
despia('takescreenshot://'); // Take screenshot
|
|
175
|
-
despia('spinneron://'); // Show loading spinner
|
|
176
|
-
|
|
177
|
-
// Commands that return data (use await)
|
|
178
|
-
const appInfo = await despia('getappversion://', ['versionNumber', 'bundleNumber']);
|
|
179
|
-
console.log(appInfo); // { versionNumber: '1.0.0', bundleNumber: '123' }
|
|
180
|
-
|
|
181
|
-
const contacts = await despia('readcontacts://', ['contacts']);
|
|
182
|
-
console.log(contacts); // { contacts: [...] }
|
|
24
|
+
https://setup.despia.com/mcp
|
|
183
25
|
```
|
|
184
26
|
|
|
185
|
-
|
|
27
|
+
Look for "Add MCP", "MCP Settings", or "Personal Connectors" in your builder. Requires Node.js v18+ for local tools.
|
|
186
28
|
|
|
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);
|
|
191
|
-
|
|
192
|
-
// For commands with no response, just call them
|
|
193
|
-
despia('lighthaptic://');
|
|
194
|
-
despia('takescreenshot://');
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Quick Examples
|
|
29
|
+
---
|
|
198
30
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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)
|
|
204
57
|
|
|
205
|
-
|
|
206
|
-
const appInfo = await despia('getappversion://', ['versionNumber', 'bundleNumber']);
|
|
207
|
-
const deviceId = await despia('get-uuid://', ['uuid']);
|
|
58
|
+
---
|
|
208
59
|
|
|
209
|
-
|
|
210
|
-
despia('spinneron://'); // Show loading
|
|
211
|
-
despia('spinneroff://'); // Hide loading
|
|
212
|
-
despia('hidebars://on'); // Hide status bar
|
|
60
|
+
## Installation
|
|
213
61
|
|
|
214
|
-
|
|
215
|
-
despia
|
|
216
|
-
|
|
62
|
+
```bash
|
|
63
|
+
npm install despia-native
|
|
64
|
+
# or
|
|
65
|
+
pnpm add despia-native
|
|
66
|
+
# or
|
|
67
|
+
yarn add despia-native
|
|
217
68
|
```
|
|
218
69
|
|
|
219
|
-
|
|
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.
|
|
220
71
|
|
|
221
|
-
|
|
72
|
+
---
|
|
222
73
|
|
|
223
|
-
|
|
224
|
-
// WRONG - This will cause errors
|
|
225
|
-
import { Commands } from 'despia-native';
|
|
226
|
-
import { despia } from 'despia-native';
|
|
74
|
+
## Quick Start
|
|
227
75
|
|
|
228
|
-
|
|
76
|
+
```js
|
|
229
77
|
import despia from 'despia-native';
|
|
230
78
|
|
|
231
|
-
//
|
|
79
|
+
// Fire-and-forget commands
|
|
232
80
|
despia('lighthaptic://');
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
**The package exports a single function as the default export, not named exports.**
|
|
236
|
-
|
|
237
|
-
## When to Use This SDK Package
|
|
238
|
-
|
|
239
|
-
### Vanilla JavaScript (works without this package)
|
|
240
|
-
|
|
241
|
-
```javascript
|
|
242
|
-
// This WORKS in vanilla JavaScript:
|
|
243
|
-
window.despia = 'lighthaptic://';
|
|
244
|
-
window.despia = 'getappversion://';
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
**Vanilla JS is fine for simple cases, but lacks:**
|
|
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
|
|
253
|
-
|
|
254
|
-
### Modern Frameworks Need This Package
|
|
255
81
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
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"
|
|
260
85
|
```
|
|
261
86
|
|
|
262
|
-
|
|
87
|
+
The SDK exports a single function as the default export. Always use a default import, not a named import.
|
|
263
88
|
|
|
264
|
-
```
|
|
265
|
-
//
|
|
89
|
+
```js
|
|
90
|
+
// Correct
|
|
266
91
|
import despia from 'despia-native';
|
|
267
92
|
|
|
268
|
-
|
|
269
|
-
|
|
93
|
+
// Wrong
|
|
94
|
+
import { despia } from 'despia-native';
|
|
95
|
+
import { Commands } from 'despia-native';
|
|
270
96
|
```
|
|
271
97
|
|
|
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
|
|
278
|
-
|
|
279
|
-
**This package is REQUIRED for TypeScript, React, Vue, Angular, and other modern frameworks.**
|
|
280
|
-
|
|
281
|
-
## Development Notes
|
|
282
|
-
|
|
283
|
-
### Environment Detection
|
|
284
|
-
The SDK only works within the Despia native runtime. For development and testing:
|
|
285
|
-
|
|
286
|
-
```javascript
|
|
287
|
-
import despia from 'despia-native';
|
|
98
|
+
---
|
|
288
99
|
|
|
289
|
-
|
|
290
|
-
// Use native features
|
|
291
|
-
despia('lighthaptic://');
|
|
292
|
-
} else {
|
|
293
|
-
// Handle non-Despia environment
|
|
294
|
-
console.log('Running outside Despia runtime');
|
|
295
|
-
}
|
|
296
|
-
```
|
|
100
|
+
## Environment Detection
|
|
297
101
|
|
|
298
|
-
|
|
299
|
-
- Use the real SDK in all environments (development, testing, production)
|
|
300
|
-
- Don't create mock implementations - they won't work on actual devices
|
|
301
|
-
- The SDK handles missing runtime gracefully
|
|
102
|
+
The SDK only activates inside the Despia native runtime. Gate native calls using these checks:
|
|
302
103
|
|
|
303
|
-
|
|
104
|
+
```js
|
|
105
|
+
const isDespia = navigator.userAgent.toLowerCase().includes('despia');
|
|
304
106
|
|
|
305
|
-
|
|
107
|
+
const isDespiaIOS = isDespia &&
|
|
108
|
+
(navigator.userAgent.toLowerCase().includes('iphone') ||
|
|
109
|
+
navigator.userAgent.toLowerCase().includes('ipad'));
|
|
306
110
|
|
|
307
|
-
|
|
308
|
-
|
|
111
|
+
const isDespiaAndroid = isDespia &&
|
|
112
|
+
navigator.userAgent.toLowerCase().includes('android');
|
|
309
113
|
|
|
310
|
-
|
|
311
|
-
if (navigator.userAgent.includes('despia')) {
|
|
312
|
-
// Use Despia native features
|
|
114
|
+
if (isDespia) {
|
|
313
115
|
despia('lighthaptic://');
|
|
314
|
-
const appInfo = await despia('getappversion://', ['versionNumber']);
|
|
315
116
|
} else {
|
|
316
|
-
//
|
|
317
|
-
console.log('Running outside Despia runtime - native features unavailable');
|
|
318
|
-
// Provide fallback behavior or show appropriate message
|
|
117
|
+
// Fallback for browser or development environment
|
|
319
118
|
}
|
|
320
119
|
```
|
|
321
120
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
## Usage
|
|
121
|
+
---
|
|
325
122
|
|
|
326
|
-
|
|
123
|
+
## AI Agent Rules
|
|
327
124
|
|
|
328
|
-
|
|
329
|
-
import despia from 'despia-native';
|
|
125
|
+
If you are an AI coding agent generating Despia app code, follow these rules:
|
|
330
126
|
|
|
331
|
-
|
|
332
|
-
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`
|
|
333
133
|
|
|
334
|
-
|
|
335
|
-
const result = await despia('getappversion://', ['versionNumber', 'bundleNumber']);
|
|
336
|
-
console.log(result); // { versionNumber: '1.0.0', bundleNumber: '123' }
|
|
337
|
-
```
|
|
134
|
+
---
|
|
338
135
|
|
|
339
|
-
|
|
136
|
+
## API Reference
|
|
340
137
|
|
|
341
|
-
|
|
342
|
-
// Native Widgets
|
|
343
|
-
despia('widget://${svg}?refresh=${refresh_time}');
|
|
138
|
+
### `despia(command, watch?)`
|
|
344
139
|
|
|
345
|
-
|
|
346
|
-
|
|
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. |
|
|
347
144
|
|
|
348
|
-
|
|
349
|
-
const purchaseData = await despia("getpurchasehistory://", ["restoredData"]);
|
|
145
|
+
Returns a `Promise` that resolves when all watched variables are set by the native runtime.
|
|
350
146
|
|
|
351
|
-
|
|
352
|
-
despia('revenuecat://launchPaywall?external_id=user_777&offering=default');
|
|
147
|
+
**Timeout behavior**
|
|
353
148
|
|
|
354
|
-
|
|
355
|
-
await despia(`setvault://?key=userId&value=user123&locked=false`);
|
|
356
|
-
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.
|
|
357
150
|
|
|
358
|
-
|
|
359
|
-
despia(`oauth://?url=${encodeURIComponent(oauthUrl)}`);
|
|
151
|
+
**Fresh-data behavior**
|
|
360
152
|
|
|
361
|
-
|
|
362
|
-
const encoded = encodeURIComponent(JSON.stringify(userData));
|
|
363
|
-
await despia(`writevalue://${encoded}`);
|
|
364
|
-
const data = await despia("readvalue://", ["storedValues"]);
|
|
153
|
+
Watched variables are cleared before each call to prevent resolving on stale values.
|
|
365
154
|
|
|
366
|
-
|
|
367
|
-
|
|
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.
|
|
368
157
|
|
|
369
|
-
|
|
370
|
-
despia("settingsapp://");
|
|
158
|
+
**Direct property access**
|
|
371
159
|
|
|
372
|
-
|
|
373
|
-
despia
|
|
374
|
-
|
|
160
|
+
```js
|
|
161
|
+
despia.variableName // Equivalent to window.variableName
|
|
162
|
+
```
|
|
375
163
|
|
|
376
|
-
|
|
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);
|
|
164
|
+
### Protocol format
|
|
386
165
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
// Set OneSignal external user ID (call on every app load)
|
|
391
|
-
despia(`setonesignalplayerid://?user_id=${YOUR_LOGGED_IN_USER_ID}`);
|
|
166
|
+
```
|
|
167
|
+
feature://action?param1=value1¶m2=value2
|
|
168
|
+
```
|
|
392
169
|
|
|
393
|
-
|
|
394
|
-
despia('lighthaptic://');
|
|
395
|
-
despia('heavyhaptic://');
|
|
396
|
-
despia('successhaptic://');
|
|
397
|
-
despia('warninghaptic://');
|
|
398
|
-
despia('errorhaptic://');
|
|
170
|
+
---
|
|
399
171
|
|
|
400
|
-
|
|
401
|
-
const appInfo = await despia('getappversion://', ['versionNumber', 'bundleNumber']);
|
|
402
|
-
const deviceInfo = await despia('get-uuid://', ['uuid']);
|
|
172
|
+
## Features
|
|
403
173
|
|
|
404
|
-
|
|
405
|
-
despia('takescreenshot://');
|
|
406
|
-
despia('scanningmode://auto');
|
|
407
|
-
despia('scanningmode://on');
|
|
408
|
-
despia('scanningmode://off');
|
|
174
|
+
### Haptic Feedback
|
|
409
175
|
|
|
410
|
-
|
|
411
|
-
|
|
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
|
+
```
|
|
412
183
|
|
|
413
|
-
|
|
414
|
-
despia('savethisimage://?url=${image_url}');
|
|
415
|
-
despia('file://${file_url}');
|
|
184
|
+
---
|
|
416
185
|
|
|
417
|
-
|
|
418
|
-
despia('reset://');
|
|
419
|
-
const trackingData = await despia('user-disable-tracking://', ['trackingDisabled']);
|
|
186
|
+
### Identity Vault
|
|
420
187
|
|
|
421
|
-
|
|
422
|
-
despia('spinneron://');
|
|
423
|
-
despia('spinneroff://');
|
|
424
|
-
despia('hidebars://on');
|
|
425
|
-
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.
|
|
426
189
|
|
|
427
|
-
|
|
428
|
-
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.
|
|
429
191
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
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');
|
|
433
195
|
|
|
434
|
-
//
|
|
435
|
-
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;
|
|
436
199
|
```
|
|
437
200
|
|
|
438
|
-
|
|
201
|
+
If the key does not exist, `readvault://` throws. Wrap in try/catch to handle first-time users.
|
|
439
202
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
```
|
|
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. |
|
|
446
208
|
|
|
447
|
-
|
|
209
|
+
**Store a value without biometric protection**
|
|
448
210
|
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
const appData = await despia('getappversion://', ['versionNumber', 'bundleNumber']);
|
|
211
|
+
```js
|
|
212
|
+
await despia('setvault://?key=userId&value=user123&locked=false');
|
|
452
213
|
|
|
453
|
-
|
|
454
|
-
despia('lighthaptic://');
|
|
455
|
-
const appData2 = await despia('getappversion://', ['versionNumber', 'bundleNumber']);
|
|
456
|
-
despia('successhaptic://');
|
|
214
|
+
const { userId } = await despia('readvault://?key=userId', ['userId']);
|
|
457
215
|
```
|
|
458
216
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
Background location tracking requires a two-step process:
|
|
462
|
-
|
|
463
|
-
```javascript
|
|
464
|
-
// Step 1: Enable native background location tracking via Despia
|
|
465
|
-
despia('backgroundlocationon://');
|
|
217
|
+
**Protect a sensitive action with Face ID**
|
|
466
218
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
(
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
console.error('Location error:', error);
|
|
479
|
-
},
|
|
480
|
-
{
|
|
481
|
-
enableHighAccuracy: true,
|
|
482
|
-
timeout: 10000,
|
|
483
|
-
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
|
|
484
230
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
// To stop tracking:
|
|
488
|
-
// Step 1: Disable native background tracking via Despia
|
|
489
|
-
despia('backgroundlocationoff://');
|
|
490
|
-
// Step 2: Clear browser geolocation watch (native API)
|
|
491
|
-
navigator.geolocation.clearWatch(watchId);
|
|
231
|
+
}
|
|
492
232
|
```
|
|
493
233
|
|
|
494
|
-
|
|
234
|
+
**Prevent free trial abuse**
|
|
495
235
|
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
+
}
|
|
503
247
|
```
|
|
504
248
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
Create a payment system that uses RevenueCat Paywalls by launching native paywall interfaces configured in your RevenueCat dashboard:
|
|
249
|
+
---
|
|
508
250
|
|
|
509
|
-
|
|
510
|
-
// First, install the package:
|
|
511
|
-
// npm install despia-native
|
|
251
|
+
### GPS Location
|
|
512
252
|
|
|
513
|
-
|
|
514
|
-
|
|
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
|
+
};
|
|
515
259
|
|
|
516
|
-
//
|
|
517
|
-
despia('
|
|
260
|
+
// Start tracking (buffer in seconds, movement threshold in centimetres)
|
|
261
|
+
despia('location://?buffer=60&movement=100');
|
|
518
262
|
|
|
519
|
-
//
|
|
520
|
-
|
|
521
|
-
// - "premium" - premium tier offering
|
|
522
|
-
// - "annual_sale" - special promotional offering
|
|
263
|
+
// Stop tracking and retrieve the session
|
|
264
|
+
const { locationSession } = await despia('stoplocation://', ['locationSession']);
|
|
523
265
|
```
|
|
524
266
|
|
|
525
|
-
**
|
|
267
|
+
**Server delivery**
|
|
526
268
|
|
|
527
|
-
|
|
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.
|
|
528
270
|
|
|
529
|
-
```
|
|
530
|
-
|
|
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
|
-
};
|
|
271
|
+
```js
|
|
272
|
+
despia('location://?server=https://api.example.com/track?user=USER_ID&buffer=30&movement=100');
|
|
538
273
|
```
|
|
539
274
|
|
|
540
|
-
|
|
275
|
+
Each POST body matches the location object shape returned by `stoplocation://`.
|
|
541
276
|
|
|
542
|
-
|
|
277
|
+
Full docs: https://setup.despia.com/native-features/gps-location
|
|
543
278
|
|
|
544
|
-
|
|
279
|
+
---
|
|
545
280
|
|
|
546
|
-
|
|
547
|
-
// First, install the package:
|
|
548
|
-
// npm install despia-native
|
|
281
|
+
### RevenueCat In-App Purchases
|
|
549
282
|
|
|
550
|
-
|
|
551
|
-
import despia from 'despia-native';
|
|
283
|
+
**Launch a paywall**
|
|
552
284
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
// Retrieve identity data
|
|
557
|
-
const data = await despia(`readvault://?key=${keyName}`, [keyName]);
|
|
558
|
-
const value = data[keyName];
|
|
285
|
+
```js
|
|
286
|
+
despia(`revenuecat://launchPaywall?external_id=${userId}&offering=default`);
|
|
559
287
|
```
|
|
560
288
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
- **locked** - Set to `'true'` to require Face ID/fingerprint, `'false'` for normal storage
|
|
566
|
-
|
|
567
|
-
**Features:**
|
|
568
|
-
|
|
569
|
-
- **Persistent storage** - Data survives app restarts, updates, and even uninstall/reinstall
|
|
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
|
|
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. |
|
|
574
293
|
|
|
575
|
-
**
|
|
294
|
+
**Direct purchase without paywall UI**
|
|
576
295
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
- Protecting sensitive actions with Face ID/Touch ID
|
|
581
|
-
- Saving user preferences and app settings
|
|
296
|
+
```js
|
|
297
|
+
// iOS
|
|
298
|
+
despia(`revenuecat://purchase?external_id=${userId}&product=monthly_premium_ios`);
|
|
582
299
|
|
|
583
|
-
|
|
300
|
+
// Android
|
|
301
|
+
despia(`revenuecat://purchase?external_id=${userId}&product=premium:monthly_premium_android`);
|
|
302
|
+
```
|
|
584
303
|
|
|
585
|
-
|
|
586
|
-
// Store user ID (normal storage)
|
|
587
|
-
await despia(`setvault://?key=userId&value=user123&locked=false`);
|
|
304
|
+
**Handle purchase success**
|
|
588
305
|
|
|
589
|
-
|
|
590
|
-
await despia(`setvault://?key=sessionToken&value=abc123xyz&locked=true`);
|
|
306
|
+
The native runtime calls `window.onRevenueCatPurchase()` after a successful purchase:
|
|
591
307
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
308
|
+
```js
|
|
309
|
+
window.onRevenueCatPurchase = async () => {
|
|
310
|
+
const { restoredData } = await despia('getpurchasehistory://', ['restoredData']);
|
|
311
|
+
const active = (restoredData ?? []).filter(p => p.isActive);
|
|
595
312
|
|
|
596
|
-
|
|
597
|
-
|
|
313
|
+
if (active.some(p => p.entitlementId === 'premium')) unlockPremium();
|
|
314
|
+
if (active.some(p => p.entitlementId === 'no_ads')) removeAds();
|
|
315
|
+
};
|
|
598
316
|
```
|
|
599
317
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
Launch OAuth authentication in a secure native browser session:
|
|
603
|
-
|
|
604
|
-
```javascript
|
|
605
|
-
// First, install the package:
|
|
606
|
-
// npm install despia-native
|
|
318
|
+
**Restore purchases**
|
|
607
319
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
despia(`oauth://?url=${encodeURIComponent(oauthUrl)}`);
|
|
320
|
+
```js
|
|
321
|
+
const { restoredData } = await despia('getpurchasehistory://', ['restoredData']);
|
|
322
|
+
const hasPremium = restoredData
|
|
323
|
+
.filter(p => p.isActive)
|
|
324
|
+
.some(p => p.entitlementId === 'premium');
|
|
614
325
|
```
|
|
615
326
|
|
|
616
|
-
|
|
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.
|
|
617
328
|
|
|
618
|
-
|
|
619
|
-
- **iOS**: ASWebAuthenticationSession (secure Safari sheet)
|
|
620
|
-
- **Android**: Chrome Custom Tabs (secure Chrome overlay)
|
|
329
|
+
---
|
|
621
330
|
|
|
622
|
-
|
|
331
|
+
### Push Notifications
|
|
623
332
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
const hash = window.location.hash.substring(1);
|
|
628
|
-
const hashParams = new URLSearchParams(hash);
|
|
629
|
-
const accessToken = hashParams.get('access_token');
|
|
630
|
-
const refreshToken = hashParams.get('refresh_token');
|
|
631
|
-
```
|
|
333
|
+
```js
|
|
334
|
+
// Register the device
|
|
335
|
+
despia('registerpush://');
|
|
632
336
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
// From your callback page (still in secure browser session)
|
|
636
|
-
window.location.href = `myapp://oauth/auth?access_token=${accessToken}&refresh_token=${refreshToken}`;
|
|
637
|
-
```
|
|
337
|
+
// Connect your user ID to this device registration (call on every app load)
|
|
338
|
+
despia(`setonesignalplayerid://?user_id=${userId}`);
|
|
638
339
|
|
|
639
|
-
|
|
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
|
+
```
|
|
640
343
|
|
|
641
|
-
**
|
|
344
|
+
**Send to a specific user from your backend**
|
|
642
345
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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
|
+
```
|
|
647
361
|
|
|
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`
|
|
362
|
+
When configuring OneSignal, select **Native iOS** and **Native Android** as the platforms, since Despia apps are native mobile applications.
|
|
652
363
|
|
|
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
|
|
364
|
+
---
|
|
657
365
|
|
|
658
|
-
###
|
|
366
|
+
### OAuth Authentication
|
|
659
367
|
|
|
660
|
-
|
|
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.
|
|
661
369
|
|
|
662
|
-
|
|
663
|
-
// First, install the package:
|
|
664
|
-
// 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.
|
|
665
371
|
|
|
666
|
-
|
|
667
|
-
|
|
372
|
+
```js
|
|
373
|
+
const isDespia = navigator.userAgent.toLowerCase().includes('despia');
|
|
668
374
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
375
|
+
const redirectUri = isDespia
|
|
376
|
+
? 'https://yourapp.com/native-callback.html'
|
|
377
|
+
: 'https://yourapp.com/auth/callback';
|
|
672
378
|
|
|
673
|
-
|
|
674
|
-
const userData = { refresh_token: "SSBMT1ZFIERFU1BJQSBOQVRJVkUgU08gTVVDSCBJIFdBTk5BIEtJU1MgSVQh" };
|
|
675
|
-
const encoded = encodeURIComponent(JSON.stringify(userData));
|
|
676
|
-
await despia(`writevalue://${encoded}`);
|
|
379
|
+
const oauthUrl = `https://provider.com/oauth/authorize?client_id=xxx&redirect_uri=${encodeURIComponent(redirectUri)}`;
|
|
677
380
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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
|
+
}
|
|
682
388
|
```
|
|
683
389
|
|
|
684
|
-
|
|
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:
|
|
685
391
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
- **Lifecycle**: Data persists across app restarts but is cleared on app uninstall
|
|
691
|
-
|
|
692
|
-
**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
|
+
```
|
|
693
396
|
|
|
694
|
-
|
|
695
|
-
- **Web fallback**: Use Local Storage as a fallback if not running in Despia Native Runtime
|
|
696
|
-
- **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`.
|
|
697
398
|
|
|
698
|
-
|
|
399
|
+
Deeplink format: `{yourscheme}://oauth/{path}?params`
|
|
699
400
|
|
|
700
|
-
|
|
701
|
-
- Caching temporary data
|
|
702
|
-
- Storing session tokens (non-sensitive)
|
|
703
|
-
- 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.
|
|
704
402
|
|
|
705
|
-
|
|
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 |
|
|
706
408
|
|
|
707
|
-
|
|
409
|
+
**Callback page**
|
|
708
410
|
|
|
709
|
-
|
|
710
|
-
// First, install the package:
|
|
711
|
-
// 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.
|
|
712
412
|
|
|
713
|
-
|
|
714
|
-
|
|
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
|
|
715
420
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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>
|
|
720
427
|
```
|
|
721
428
|
|
|
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
|
-
```
|
|
429
|
+
**Already-mounted `/auth` page**
|
|
789
430
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
```javascript
|
|
793
|
-
[
|
|
794
|
-
{
|
|
795
|
-
"transactionId": "GPA.3372-4150-9088-12345",
|
|
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
|
-
]
|
|
831
|
-
```
|
|
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:
|
|
832
432
|
|
|
833
|
-
|
|
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)`
|
|
834
436
|
|
|
835
|
-
|
|
836
|
-
const data = await despia("getpurchasehistory://", ["restoredData"]);
|
|
837
|
-
const purchases = data.restoredData;
|
|
437
|
+
Full docs: https://setup.despia.com/native-features/o-auth-2-0
|
|
838
438
|
|
|
839
|
-
|
|
840
|
-
const activePurchases = purchases.filter(p => p.isActive);
|
|
439
|
+
---
|
|
841
440
|
|
|
842
|
-
|
|
843
|
-
const hasPremium = activePurchases.some(p => p.entitlementId === "premium");
|
|
441
|
+
### Clipboard
|
|
844
442
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
}
|
|
443
|
+
```js
|
|
444
|
+
const { clipboarddata } = await despia('getclipboard://', ['clipboarddata']);
|
|
848
445
|
```
|
|
849
446
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
- Implementing "Restore Purchases" buttons required by App Store guidelines
|
|
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
|
|
857
|
-
|
|
858
|
-
**Important Notes:**
|
|
447
|
+
---
|
|
859
448
|
|
|
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
|
|
449
|
+
### Contacts
|
|
864
450
|
|
|
865
|
-
|
|
451
|
+
```js
|
|
452
|
+
const { contacts } = await despia('readcontacts://', ['contacts']);
|
|
453
|
+
```
|
|
866
454
|
|
|
867
|
-
|
|
455
|
+
---
|
|
868
456
|
|
|
869
|
-
|
|
870
|
-
// First, install the package:
|
|
871
|
-
// npm install despia-native
|
|
457
|
+
### App Information and Device Data
|
|
872
458
|
|
|
873
|
-
|
|
874
|
-
|
|
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
|
+
```
|
|
875
465
|
|
|
876
|
-
|
|
877
|
-
const clipboardData = await despia('getclipboard://', ['clipboarddata']);
|
|
466
|
+
---
|
|
878
467
|
|
|
879
|
-
|
|
880
|
-
const content = clipboardData.clipboarddata;
|
|
468
|
+
### UI Controls and Styling
|
|
881
469
|
|
|
882
|
-
|
|
883
|
-
|
|
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
|
|
884
479
|
```
|
|
885
480
|
|
|
886
|
-
|
|
481
|
+
---
|
|
887
482
|
|
|
888
|
-
|
|
889
|
-
- **Access content**: The clipboard content is available through the `.clipboarddata` property of the returned object
|
|
890
|
-
- **Native support**: Works on both iOS and Android platforms
|
|
483
|
+
### File and Media Operations
|
|
891
484
|
|
|
892
|
-
|
|
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
|
+
```
|
|
893
492
|
|
|
894
|
-
|
|
895
|
-
- Processing clipboard content in your app
|
|
896
|
-
- Implementing paste functionality
|
|
897
|
-
- Clipboard content validation
|
|
493
|
+
---
|
|
898
494
|
|
|
899
|
-
###
|
|
495
|
+
### Web Storage APIs
|
|
900
496
|
|
|
901
|
-
|
|
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:
|
|
902
498
|
|
|
903
|
-
```
|
|
904
|
-
//
|
|
905
|
-
|
|
499
|
+
```js
|
|
500
|
+
// localStorage works normally
|
|
501
|
+
localStorage.setItem('userId', 'user123');
|
|
502
|
+
const userId = localStorage.getItem('userId');
|
|
906
503
|
|
|
907
|
-
//
|
|
908
|
-
|
|
504
|
+
// IndexedDB works normally
|
|
505
|
+
const db = await indexedDB.open('myapp', 1);
|
|
909
506
|
|
|
910
|
-
//
|
|
911
|
-
|
|
507
|
+
// Web Crypto works normally
|
|
508
|
+
const key = await crypto.subtle.generateKey(
|
|
509
|
+
{ name: 'AES-GCM', length: 256 },
|
|
510
|
+
true,
|
|
511
|
+
['encrypt', 'decrypt']
|
|
512
|
+
);
|
|
912
513
|
```
|
|
913
514
|
|
|
914
|
-
|
|
515
|
+
For data that needs to survive uninstall and reinstall, or be locked behind Face ID, use [Identity Vault](#identity-vault) instead.
|
|
915
516
|
|
|
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
|
|
919
|
-
|
|
920
|
-
**Perfect for:**
|
|
517
|
+
---
|
|
921
518
|
|
|
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
|
|
519
|
+
### Local Server
|
|
926
520
|
|
|
927
|
-
|
|
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.
|
|
928
522
|
|
|
929
|
-
|
|
930
|
-
// Check if location permission is needed
|
|
931
|
-
if (!navigator.geolocation) {
|
|
932
|
-
// Open settings so user can enable location
|
|
933
|
-
despia("settingsapp://");
|
|
934
|
-
}
|
|
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.
|
|
935
524
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
function openNotificationSettings() {
|
|
939
|
-
despia("settingsapp://");
|
|
940
|
-
}
|
|
525
|
+
```bash
|
|
526
|
+
npm install --save-dev @despia/local
|
|
941
527
|
```
|
|
942
528
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
Set up OneSignal push notifications with external user IDs to connect your database user IDs with device registrations:
|
|
946
|
-
|
|
947
|
-
```javascript
|
|
948
|
-
// First, install the package:
|
|
949
|
-
// npm install despia-native
|
|
950
|
-
|
|
951
|
-
// Then import it:
|
|
952
|
-
import despia from 'despia-native';
|
|
529
|
+
Add the plugin to your build tool to generate the update manifest automatically:
|
|
953
530
|
|
|
954
|
-
|
|
955
|
-
//
|
|
956
|
-
|
|
957
|
-
|
|
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';
|
|
958
535
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
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 }
|
|
536
|
+
export default defineConfig({
|
|
537
|
+
plugins: [
|
|
538
|
+
despiaLocalPlugin({
|
|
539
|
+
outDir: 'dist',
|
|
540
|
+
entryHtml: 'index.html'
|
|
991
541
|
})
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
};
|
|
542
|
+
]
|
|
543
|
+
});
|
|
995
544
|
```
|
|
996
545
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
```javascript
|
|
1000
|
-
// In your app initialization or login flow
|
|
1001
|
-
function initializeApp(userId) {
|
|
1002
|
-
// Set external user ID on every app load
|
|
1003
|
-
despia(`setonesignalplayerid://?user_id=${userId}`);
|
|
1004
|
-
|
|
1005
|
-
// Register for push notifications
|
|
1006
|
-
despia('registerpush://');
|
|
1007
|
-
}
|
|
546
|
+
Or run via CLI after any build:
|
|
1008
547
|
|
|
1009
|
-
|
|
1010
|
-
despia
|
|
548
|
+
```bash
|
|
549
|
+
npx despia-local dist
|
|
1011
550
|
```
|
|
1012
551
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
- This feature requires native capabilities which are fully provided by the `despia-native` npm package
|
|
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
|
|
1020
|
-
|
|
1021
|
-
**Perfect for:**
|
|
1022
|
-
|
|
1023
|
-
- Sending targeted notifications to specific users
|
|
1024
|
-
- User-based notification management
|
|
1025
|
-
- Cross-device notification delivery
|
|
1026
|
-
- Personalized push notification campaigns
|
|
1027
|
-
|
|
1028
|
-
### Haptic 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.
|
|
1029
553
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
despia('errorhaptic://'); // Error haptic feedback - negative feedback
|
|
1041
|
-
|
|
1042
|
-
// Use cases:
|
|
1043
|
-
// - Button press feedback (light/heavy)
|
|
1044
|
-
// - Success notifications (successhaptic)
|
|
1045
|
-
// - Warning alerts (warninghaptic)
|
|
1046
|
-
// - Error feedback (errorhaptic)
|
|
1047
|
-
// - 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
|
+
}
|
|
1048
564
|
```
|
|
1049
565
|
|
|
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
|
-
}
|
|
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.
|
|
1073
567
|
|
|
1074
|
-
|
|
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
|
-
}
|
|
568
|
+
Full docs: https://setup.despia.com/local-server/introduction
|
|
1082
569
|
|
|
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
|
-
}
|
|
1090
570
|
|
|
1091
|
-
|
|
1092
|
-
if (navigator.userAgent.includes("despia")) {
|
|
1093
|
-
console.log("Biometric authentication unavailable");
|
|
1094
|
-
// Handle when biometric auth is not available
|
|
1095
|
-
// Fallback to alternative authentication method
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
571
|
+
---
|
|
1098
572
|
|
|
1099
|
-
|
|
1100
|
-
despia('bioauth://');
|
|
1101
|
-
```
|
|
573
|
+
### Local CDN
|
|
1102
574
|
|
|
1103
|
-
|
|
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.
|
|
1104
576
|
|
|
1105
|
-
```
|
|
1106
|
-
//
|
|
1107
|
-
|
|
1108
|
-
console.log('
|
|
1109
|
-
|
|
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
|
+
};
|
|
1110
583
|
|
|
1111
|
-
//
|
|
1112
|
-
|
|
1113
|
-
|
|
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
|
+
);
|
|
1114
588
|
|
|
1115
|
-
//
|
|
1116
|
-
const
|
|
1117
|
-
|
|
589
|
+
// Read cached items by ID
|
|
590
|
+
const { cdnItems } = await despia(
|
|
591
|
+
`localcdn://read?index=${encodeURIComponent(JSON.stringify(['clip_1']))}`,
|
|
592
|
+
['cdnItems']
|
|
593
|
+
);
|
|
1118
594
|
|
|
1119
|
-
//
|
|
1120
|
-
const
|
|
1121
|
-
console.log('Tracking Disabled:', trackingData.trackingDisabled);
|
|
595
|
+
// Or query everything in the cache
|
|
596
|
+
const { cdnItems: all } = await despia('localcdn://query', ['cdnItems']);
|
|
1122
597
|
```
|
|
1123
598
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
```javascript
|
|
1127
|
-
// Loading spinner controls
|
|
1128
|
-
await despia('spinneron://'); // Show loading spinner
|
|
1129
|
-
await despia('spinneroff://'); // Hide loading spinner
|
|
599
|
+
Use the `local_cdn` URL for playback:
|
|
1130
600
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
await despia('hidebars://off'); // Show status bar
|
|
1134
|
-
|
|
1135
|
-
// Status bar styling
|
|
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)
|
|
601
|
+
```html
|
|
602
|
+
<video src="http://localhost:7777/localcdn/videos/clip.mp4" controls></video>
|
|
1138
603
|
```
|
|
1139
604
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
```javascript
|
|
1143
|
-
// Take screenshot (saves to device)
|
|
1144
|
-
await despia('takescreenshot://');
|
|
605
|
+
**HTTP upload API** (Local Server only)
|
|
1145
606
|
|
|
1146
|
-
|
|
1147
|
-
|
|
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]);
|
|
1148
611
|
|
|
1149
|
-
|
|
1150
|
-
await
|
|
1151
|
-
|
|
1152
|
-
// Share app with message and URL
|
|
1153
|
-
await despia('shareapp://message?=Check%20out%20this%20app&url=https://myapp.com');
|
|
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" }
|
|
1154
615
|
```
|
|
1155
616
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
await despia('scanningmode://auto'); // Auto scanning mode
|
|
1161
|
-
await despia('scanningmode://on'); // Enable scanning
|
|
1162
|
-
await despia('scanningmode://off'); // Disable scanning
|
|
1163
|
-
```
|
|
617
|
+
| Method | Storage Path | URL Pattern |
|
|
618
|
+
|--------|-------------|-------------|
|
|
619
|
+
| `localcdn://write` | `/localcdn/` | `localhost:{PORT}/localcdn/{filepath}` |
|
|
620
|
+
| `/api/upload` | `/files/` | `localhost:{PORT}/files/{filename}` |
|
|
1164
621
|
|
|
1165
|
-
|
|
622
|
+
Full docs: https://setup.despia.com/local-cdn/introduction
|
|
1166
623
|
|
|
1167
|
-
|
|
1168
|
-
// Reset app (use with caution)
|
|
1169
|
-
await despia('reset://');
|
|
1170
|
-
```
|
|
624
|
+
---
|
|
1171
625
|
|
|
1172
|
-
|
|
626
|
+
## Safe Area
|
|
1173
627
|
|
|
1174
|
-
|
|
628
|
+
Despia exposes top and bottom safe area insets as CSS custom properties set by the native runtime.
|
|
1175
629
|
|
|
1176
630
|
```css
|
|
1177
|
-
|
|
1178
|
-
.my-element {
|
|
631
|
+
.header {
|
|
1179
632
|
padding-top: var(--safe-area-top);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
.footer {
|
|
1180
636
|
padding-bottom: var(--safe-area-bottom);
|
|
1181
637
|
}
|
|
1182
638
|
|
|
1183
|
-
/* Full height with safe area consideration */
|
|
1184
639
|
.full-height {
|
|
1185
640
|
height: calc(100vh - var(--safe-area-top) - var(--safe-area-bottom));
|
|
1186
641
|
}
|
|
1187
642
|
```
|
|
1188
643
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
These CSS variables are automatically provided by the Despia native runtime and represent the device's safe area insets (notches, home indicators, etc.).
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
## API Reference
|
|
644
|
+
Note: left and right safe area variables are not available.
|
|
1196
645
|
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
- **command** (string): The Despia protocol command (e.g., `'lighthaptic://'`)
|
|
1200
|
-
- **watch** (string[], optional): Array of variable names to watch for in the response
|
|
1201
|
-
|
|
1202
|
-
Returns a Promise that resolves when all watched variables are available:
|
|
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.
|
|
1205
|
-
|
|
1206
|
-
### Timeout behavior
|
|
1207
|
-
|
|
1208
|
-
When you call `despia(command, ['someVariable'])`, the SDK waits up to 30 seconds for
|
|
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.
|
|
1213
|
-
|
|
1214
|
-
### Fresh-data behavior
|
|
1215
|
-
|
|
1216
|
-
Before observing, watched variables are cleared to avoid resolving on stale values.
|
|
1217
|
-
The observer behavior differs by variable count:
|
|
1218
|
-
|
|
1219
|
-
- **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.
|
|
1220
|
-
|
|
1221
|
-
- **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.
|
|
1222
|
-
|
|
1223
|
-
This ensures each call waits for a fresh write from the native side.
|
|
1224
|
-
|
|
1225
|
-
### Direct Property Access
|
|
1226
|
-
|
|
1227
|
-
Access any window variable directly through the despia object:
|
|
1228
|
-
|
|
1229
|
-
```javascript
|
|
1230
|
-
despia.variableName // Equivalent to window.variableName
|
|
1231
|
-
```
|
|
1232
|
-
|
|
1233
|
-
## Despia Protocol Format
|
|
1234
|
-
|
|
1235
|
-
Despia uses a simple protocol format for all native integrations:
|
|
1236
|
-
|
|
1237
|
-
```
|
|
1238
|
-
feature://action?parameters
|
|
1239
|
-
```
|
|
1240
|
-
|
|
1241
|
-
Examples:
|
|
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';
|
|
1292
|
-
|
|
1293
|
-
// Type-safe usage with Despia commands
|
|
1294
|
-
const result: { versionNumber: string; bundleNumber: string } = await despia(
|
|
1295
|
-
'getappversion://',
|
|
1296
|
-
['versionNumber', 'bundleNumber']
|
|
1297
|
-
);
|
|
646
|
+
---
|
|
1298
647
|
|
|
1299
|
-
|
|
1300
|
-
const deviceInfo: any = despia.deviceInfo;
|
|
1301
|
-
```
|
|
648
|
+
## Web Apps vs React Native
|
|
1302
649
|
|
|
1303
|
-
|
|
650
|
+
This SDK is for web apps running inside the Despia runtime: React, Vue, Angular, Svelte, Next.js, Vite, Nuxt, and vanilla JavaScript.
|
|
1304
651
|
|
|
1305
|
-
|
|
652
|
+
It is not for React Native, Expo, or native mobile development.
|
|
1306
653
|
|
|
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
|
|
654
|
+
---
|
|
1311
655
|
|
|
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:
|
|
656
|
+
## Open Source
|
|
1314
657
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
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 |
|
|
1318
663
|
|
|
1319
|
-
|
|
1320
|
-
window.despia = 'lighthaptic://';
|
|
1321
|
-
```
|
|
664
|
+
Native capability implementations are written in Swift and Java and included in full on project export.
|
|
1322
665
|
|
|
1323
|
-
|
|
666
|
+
---
|
|
1324
667
|
|
|
1325
668
|
## License
|
|
1326
669
|
|