@sparkvault/sdk 1.0.0 → 1.1.5
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 +536 -502
- package/dist/identity/api.d.ts +4 -3
- package/dist/identity/renderer.d.ts +9 -2
- package/dist/identity/types.d.ts +2 -0
- package/dist/identity/views/icons.d.ts +8 -0
- package/dist/identity/views/sparklink-waiting.d.ts +7 -0
- package/dist/identity/views/totp-verify.d.ts +1 -1
- package/dist/sparkvault.cjs.js +304 -73
- package/dist/sparkvault.cjs.js.map +1 -1
- package/dist/sparkvault.esm.js +304 -73
- package/dist/sparkvault.esm.js.map +1 -1
- package/dist/sparkvault.js +1 -1
- package/dist/sparkvault.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SparkVault JavaScript SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The official SparkVault JavaScript SDK for browser applications. Add passwordless authentication to your app in minutes with zero-config integration or full programmatic control.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@sparkvault/sdk)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -8,627 +8,699 @@ A unified JavaScript SDK for SparkVault services: Identity verification, encrypt
|
|
|
8
8
|
## Table of Contents
|
|
9
9
|
|
|
10
10
|
- [Installation](#installation)
|
|
11
|
-
- [
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- [Vaults](#vaults)
|
|
17
|
-
- [RNG](#rng)
|
|
18
|
-
- [Configuration](#configuration)
|
|
11
|
+
- [Zero-Config Integration](#zero-config-integration)
|
|
12
|
+
- [Manual Initialization](#manual-initialization)
|
|
13
|
+
- [Identity Verification](#identity-verification)
|
|
14
|
+
- [Backend Token Verification](#backend-token-verification)
|
|
15
|
+
- [React Integration](#react-integration)
|
|
19
16
|
- [Error Handling](#error-handling)
|
|
20
|
-
- [TypeScript](#typescript)
|
|
21
|
-
- [
|
|
17
|
+
- [TypeScript Support](#typescript-support)
|
|
18
|
+
- [Browser Support](#browser-support)
|
|
22
19
|
- [Development](#development)
|
|
23
20
|
- [License](#license)
|
|
24
21
|
|
|
25
22
|
## Installation
|
|
26
23
|
|
|
27
|
-
### CDN (
|
|
24
|
+
### CDN (Recommended)
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
```html
|
|
27
|
+
<script src="https://cdn.sparkvault.com/sdk/v1/sparkvault.js"></script>
|
|
28
|
+
```
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
### npm / yarn
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
```bash
|
|
33
|
+
npm install @sparkvault/sdk
|
|
34
|
+
# or
|
|
35
|
+
yarn add @sparkvault/sdk
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Bundle Formats:**
|
|
39
|
+
- `sparkvault.js` — UMD bundle for browsers (recommended)
|
|
40
|
+
- `sparkvault.esm.js` — ES modules for modern bundlers
|
|
41
|
+
- `sparkvault.cjs.js` — CommonJS for Node.js
|
|
42
|
+
|
|
43
|
+
## Zero-Config Integration
|
|
44
|
+
|
|
45
|
+
Add SparkVault authentication to your site with a single script tag — no JavaScript required!
|
|
34
46
|
|
|
35
47
|
```html
|
|
48
|
+
<!-- Add this single script tag -->
|
|
36
49
|
<script
|
|
37
50
|
async
|
|
38
51
|
src="https://cdn.sparkvault.com/sdk/v1/sparkvault.js"
|
|
39
|
-
data-account-id="
|
|
40
|
-
data-attach-selector=".
|
|
41
|
-
data-success-url="
|
|
42
|
-
data-debug="true"
|
|
52
|
+
data-account-id="acc_YOUR_ACCOUNT_ID"
|
|
53
|
+
data-attach-selector=".login-btn"
|
|
54
|
+
data-success-url="/auth/verify-token"
|
|
43
55
|
></script>
|
|
44
56
|
|
|
45
|
-
|
|
57
|
+
<!-- Any element matching the selector becomes a login button -->
|
|
58
|
+
<button class="login-btn">Sign In</button>
|
|
59
|
+
<a href="#" class="login-btn">Create Account</a>
|
|
46
60
|
```
|
|
47
61
|
|
|
48
62
|
That's it! The SDK automatically:
|
|
49
63
|
- Initializes with your account ID
|
|
50
|
-
- Attaches click handlers to matching elements
|
|
51
|
-
- Opens the verification modal
|
|
64
|
+
- Attaches click handlers to all matching elements
|
|
65
|
+
- Opens the verification modal when clicked
|
|
52
66
|
- POSTs the result to your success URL
|
|
67
|
+
- Watches for dynamically added elements (MutationObserver)
|
|
53
68
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
| Attribute | Description |
|
|
57
|
-
|-----------|-------------|
|
|
58
|
-
| `data-account-id` | Your SparkVault account ID (required for auto-init) |
|
|
59
|
-
| `data-attach-selector` | CSS selector for elements to attach click handlers |
|
|
60
|
-
| `data-success-url` | URL to POST `{ token, identity, identityType }` on success |
|
|
61
|
-
| `data-success-function` | Global function name to call on success (e.g., `"handleAuth"` or `"MyApp.auth.onSuccess"`) |
|
|
62
|
-
| `data-error-url` | URL to redirect to on error (appends `?error=message`) |
|
|
63
|
-
| `data-error-function` | Global function name to call on error |
|
|
64
|
-
| `data-debug` | Set to `"true"` for verbose console logging |
|
|
69
|
+
### Data Attributes
|
|
65
70
|
|
|
66
|
-
|
|
71
|
+
| Attribute | Required | Description |
|
|
72
|
+
|-----------|----------|-------------|
|
|
73
|
+
| `data-account-id` | Yes | Your SparkVault account ID (required for auto-init) |
|
|
74
|
+
| `data-attach-selector` | No | CSS selector for elements to attach click handlers |
|
|
75
|
+
| `data-success-url` | No | URL to POST `{ token, identity, identityType }` on success |
|
|
76
|
+
| `data-success-function` | No | Global function name to call on success (e.g., `"MyApp.auth.onSuccess"`) |
|
|
77
|
+
| `data-error-url` | No | URL to redirect on error (appends `?error=message`) |
|
|
78
|
+
| `data-error-function` | No | Global function name to call on error |
|
|
79
|
+
| `data-preload-config` | No | Set to `"false"` to defer config loading. Default: true |
|
|
80
|
+
| `data-timeout` | No | Request timeout in milliseconds. Default: 30000 |
|
|
81
|
+
| `data-debug` | No | Set to `"true"` for verbose console logging |
|
|
67
82
|
|
|
68
|
-
|
|
83
|
+
### Complete Example
|
|
69
84
|
|
|
70
85
|
```html
|
|
71
|
-
|
|
86
|
+
<script
|
|
87
|
+
async
|
|
88
|
+
src="https://cdn.sparkvault.com/sdk/v1/sparkvault.js"
|
|
89
|
+
data-account-id="acc_YOUR_ACCOUNT_ID"
|
|
90
|
+
data-attach-selector=".js-sparkvault-auth"
|
|
91
|
+
data-success-url="https://example.com/auth/verify-token"
|
|
92
|
+
data-success-function="onSparkVaultSuccess"
|
|
93
|
+
data-error-function="onSparkVaultError"
|
|
94
|
+
data-debug="true"
|
|
95
|
+
></script>
|
|
96
|
+
|
|
72
97
|
<script>
|
|
73
|
-
|
|
98
|
+
// Optional: Define callback functions
|
|
99
|
+
function onSparkVaultSuccess(result) {
|
|
74
100
|
console.log('Verified:', result.identity);
|
|
75
101
|
console.log('Token:', result.token);
|
|
76
|
-
//
|
|
102
|
+
// The SDK will also POST to data-success-url
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function onSparkVaultError(error) {
|
|
106
|
+
console.error('Error:', error.message);
|
|
107
|
+
alert('Authentication failed. Please try again.');
|
|
77
108
|
}
|
|
78
109
|
</script>
|
|
79
|
-
<script
|
|
80
|
-
src="https://cdn.sparkvault.com/sdk/v1/sparkvault.js"
|
|
81
|
-
data-account-id="acc_your_account"
|
|
82
|
-
data-attach-selector=".login-btn"
|
|
83
|
-
data-success-function="handleSparkVaultAuth"
|
|
84
|
-
></script>
|
|
85
110
|
|
|
86
|
-
<!--
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
data-account-id="acc_your_account"
|
|
90
|
-
data-attach-selector=".login-btn"
|
|
91
|
-
data-success-url="/auth/verify-sparkvault-token"
|
|
92
|
-
></script>
|
|
111
|
+
<!-- Multiple elements can use the same selector -->
|
|
112
|
+
<button class="js-sparkvault-auth">Sign In</button>
|
|
113
|
+
<a href="#" class="js-sparkvault-auth">Login with SparkVault</a>
|
|
93
114
|
```
|
|
94
115
|
|
|
95
|
-
|
|
116
|
+
### Success URL Response
|
|
117
|
+
|
|
118
|
+
When you specify `data-success-url`, the SDK POSTs the verification result:
|
|
119
|
+
|
|
96
120
|
```json
|
|
97
121
|
{
|
|
98
|
-
"token": "
|
|
122
|
+
"token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
|
|
99
123
|
"identity": "user@example.com",
|
|
100
124
|
"identityType": "email"
|
|
101
125
|
}
|
|
102
126
|
```
|
|
103
127
|
|
|
104
|
-
Your backend can respond with:
|
|
105
|
-
```json
|
|
106
|
-
{ "redirectUrl": "/dashboard" } // SDK redirects to this URL
|
|
107
|
-
{ "reload": true } // SDK reloads the page
|
|
108
|
-
```
|
|
128
|
+
Your backend can respond with redirect instructions:
|
|
109
129
|
|
|
110
|
-
|
|
130
|
+
```javascript
|
|
131
|
+
// Option 1: Redirect to a URL
|
|
132
|
+
{ "redirectUrl": "/dashboard" }
|
|
133
|
+
// or
|
|
134
|
+
{ "redirect_url": "/dashboard" }
|
|
111
135
|
|
|
112
|
-
|
|
136
|
+
// Option 2: Reload the current page
|
|
137
|
+
{ "reload": true }
|
|
113
138
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
<script>
|
|
117
|
-
const sv = SparkVault.init({
|
|
118
|
-
accountId: 'acc_your_account'
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// Trigger verification popup manually
|
|
122
|
-
document.getElementById('login-btn').addEventListener('click', async () => {
|
|
123
|
-
const result = await sv.identity.pop();
|
|
124
|
-
// Handle result...
|
|
125
|
-
});
|
|
126
|
-
</script>
|
|
139
|
+
// Option 3: Return data (no automatic action)
|
|
140
|
+
{ "success": true, "user": { "id": 123, "email": "user@example.com" } }
|
|
127
141
|
```
|
|
128
142
|
|
|
129
|
-
|
|
143
|
+
### Manual Trigger from JavaScript
|
|
130
144
|
|
|
131
|
-
|
|
132
|
-
|------|-------------|
|
|
133
|
-
| `sparkvault.js` | Minified UMD bundle (recommended) |
|
|
134
|
-
| `sparkvault.esm.js` | ES module bundle |
|
|
135
|
-
| `sparkvault.cjs.js` | CommonJS bundle |
|
|
145
|
+
You can also trigger authentication programmatically using the `SparkVault` global:
|
|
136
146
|
|
|
137
|
-
|
|
147
|
+
```javascript
|
|
148
|
+
// Trigger authentication from anywhere in your code
|
|
149
|
+
SparkVault.identity.pop();
|
|
138
150
|
|
|
139
|
-
|
|
140
|
-
npm install @sparkvault/sdk
|
|
151
|
+
// This uses your configured success/error handlers
|
|
141
152
|
```
|
|
142
153
|
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
// CommonJS
|
|
148
|
-
const { SparkVault } = require('@sparkvault/sdk');
|
|
154
|
+
```html
|
|
155
|
+
<button onclick="SparkVault.identity.pop()">
|
|
156
|
+
Sign In with SparkVault
|
|
157
|
+
</button>
|
|
149
158
|
```
|
|
150
159
|
|
|
151
|
-
##
|
|
160
|
+
## Manual Initialization
|
|
161
|
+
|
|
162
|
+
For full control over the SDK, initialize it manually in JavaScript:
|
|
152
163
|
|
|
153
164
|
```javascript
|
|
154
|
-
|
|
155
|
-
const sv = SparkVault.init({
|
|
156
|
-
accountId: 'acc_your_account'
|
|
157
|
-
});
|
|
165
|
+
import SparkVault from '@sparkvault/sdk';
|
|
158
166
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
email: 'user@example.com'
|
|
167
|
+
const sparkvault = SparkVault.init({
|
|
168
|
+
accountId: 'acc_YOUR_ACCOUNT_ID' // Required
|
|
162
169
|
});
|
|
163
170
|
|
|
164
|
-
// The
|
|
165
|
-
await
|
|
166
|
-
method: 'POST',
|
|
167
|
-
headers: { 'Content-Type': 'application/json' },
|
|
168
|
-
body: JSON.stringify({ token: result.token })
|
|
169
|
-
});
|
|
170
|
-
// Your backend verifies the token, then creates YOUR session
|
|
171
|
+
// The SDK is now ready to use
|
|
172
|
+
const result = await sparkvault.identity.pop();
|
|
171
173
|
```
|
|
172
174
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
The SparkVault SDK follows a Stripe.js-like architecture:
|
|
176
|
-
|
|
177
|
-
```
|
|
178
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
179
|
-
│ Your Application │
|
|
180
|
-
├─────────────────────────────────────────────────────────────┤
|
|
181
|
-
│ SparkVault SDK │
|
|
182
|
-
│ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │
|
|
183
|
-
│ │ Identity │ Sparks │ Vaults │ RNG │ │
|
|
184
|
-
│ │ Module │ Module │ Module │ Module │ │
|
|
185
|
-
│ └─────────────┴─────────────┴─────────────┴─────────────┘ │
|
|
186
|
-
│ │ │
|
|
187
|
-
│ HTTP Client │
|
|
188
|
-
├─────────────────────────────────────────────────────────────┤
|
|
189
|
-
│ api.sparkvault.com │
|
|
190
|
-
└─────────────────────────────────────────────────────────────┘
|
|
191
|
-
```
|
|
175
|
+
### Configuration Options
|
|
192
176
|
|
|
193
|
-
|
|
177
|
+
| Option | Type | Default | Description |
|
|
178
|
+
|--------|------|---------|-------------|
|
|
179
|
+
| `accountId` | `string` | **required** | Your SparkVault account ID (starts with `acc_`) |
|
|
180
|
+
| `timeout` | `number` | `30000` | HTTP request timeout in milliseconds |
|
|
181
|
+
| `preloadConfig` | `boolean` | `true` | Preload Identity config for instant modal |
|
|
194
182
|
|
|
195
|
-
|
|
183
|
+
### CDN Usage
|
|
196
184
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
185
|
+
```html
|
|
186
|
+
<script src="https://cdn.sparkvault.com/sdk/v1/sparkvault.js"></script>
|
|
187
|
+
<script>
|
|
188
|
+
const sparkvault = SparkVault.init({
|
|
189
|
+
accountId: 'acc_YOUR_ACCOUNT_ID'
|
|
190
|
+
});
|
|
202
191
|
|
|
192
|
+
document.getElementById('login-btn').addEventListener('click', async () => {
|
|
193
|
+
const result = await sparkvault.identity.pop();
|
|
194
|
+
console.log('Verified:', result.identity);
|
|
195
|
+
});
|
|
196
|
+
</script>
|
|
203
197
|
```
|
|
204
|
-
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
|
|
205
|
-
│ Your App │ │ SDK Popup │ │ SparkVault │
|
|
206
|
-
│ │ │ │ │ Identity │
|
|
207
|
-
├──────────────┤ ├──────────────────┤ ├──────────────┤
|
|
208
|
-
│ sv.identity │────>│ Opens popup │ │ │
|
|
209
|
-
│ .pop() │ │ window │────>│ /embed page │
|
|
210
|
-
│ │ │ │ │ │
|
|
211
|
-
│ │ │ User selects │ │ Auth methods │
|
|
212
|
-
│ │ │ auth method │<────│ displayed │
|
|
213
|
-
│ │ │ │ │ │
|
|
214
|
-
│ │ │ User completes │────>│ Verify │
|
|
215
|
-
│ │ │ authentication │ │ credentials │
|
|
216
|
-
│ │ │ │ │ │
|
|
217
|
-
│ Promise │<────│ postMessage │<────│ Sign JWT │
|
|
218
|
-
│ resolves │ │ with token │ │ token │
|
|
219
|
-
└──────────────┘ └──────────────────┘ └──────────────┘
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
## Modules
|
|
223
198
|
|
|
224
|
-
|
|
199
|
+
## Identity Verification
|
|
225
200
|
|
|
226
|
-
|
|
201
|
+
The Identity module provides passwordless authentication through a beautiful, customizable modal. Users can verify via passkeys, email codes, SMS codes, magic links, or social providers.
|
|
227
202
|
|
|
228
|
-
|
|
229
|
-
- **`pop()`** — Opens a modal popup (most common)
|
|
230
|
-
- **`render()`** — Embeds UI inline in your page
|
|
203
|
+
### Basic Usage
|
|
231
204
|
|
|
232
205
|
```javascript
|
|
233
|
-
// Open
|
|
234
|
-
const result = await
|
|
235
|
-
|
|
236
|
-
// With options
|
|
237
|
-
const result = await sv.identity.pop({
|
|
238
|
-
email: 'user@example.com', // Pre-fill email
|
|
239
|
-
methods: ['passkey', 'totp_email', 'magic_link'], // Filter methods
|
|
240
|
-
theme: 'dark', // 'light', 'dark', or 'auto' (system preference)
|
|
241
|
-
backdropBlur: true, // Enable backdrop blur effect (default: true)
|
|
242
|
-
locale: 'en', // UI language
|
|
243
|
-
onSuccess: (result) => {}, // Success callback
|
|
244
|
-
onCancel: () => {}, // User cancelled
|
|
245
|
-
onError: (error) => {} // Error callback
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
// Result structure
|
|
249
|
-
console.log(result.token); // Signed JWT
|
|
250
|
-
console.log(result.identity); // Verified identity (email or phone)
|
|
251
|
-
console.log(result.identityType); // 'email' or 'phone'
|
|
252
|
-
```
|
|
206
|
+
// Open the verification modal
|
|
207
|
+
const result = await sparkvault.identity.pop();
|
|
253
208
|
|
|
254
|
-
|
|
209
|
+
// User verified!
|
|
210
|
+
console.log(result.identity); // "user@example.com" or "+14155551234"
|
|
211
|
+
console.log(result.identityType); // "email" or "phone"
|
|
212
|
+
console.log(result.token); // Signed JWT token
|
|
255
213
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
target: document.getElementById('auth-container'),
|
|
262
|
-
email: 'user@example.com'
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
// Embed in your own dialog without SparkVault header/footer
|
|
266
|
-
const result = await sv.identity.render({
|
|
267
|
-
target: dialogContentElement,
|
|
268
|
-
showHeader: false,
|
|
269
|
-
showFooter: false
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
// Minimal - just the auth flow, no chrome
|
|
273
|
-
const result = await sv.identity.render({
|
|
274
|
-
target: myElement,
|
|
275
|
-
showHeader: false,
|
|
276
|
-
showCloseButton: false,
|
|
277
|
-
showFooter: false
|
|
214
|
+
// Send the token to your backend for verification
|
|
215
|
+
await fetch('/api/auth/login', {
|
|
216
|
+
method: 'POST',
|
|
217
|
+
headers: { 'Content-Type': 'application/json' },
|
|
218
|
+
body: JSON.stringify({ token: result.token })
|
|
278
219
|
});
|
|
279
220
|
```
|
|
280
221
|
|
|
281
|
-
|
|
282
|
-
|--------|------|---------|-------------|
|
|
283
|
-
| `target` | `HTMLElement` | **required** | Element to render the auth UI into |
|
|
284
|
-
| `showHeader` | `boolean` | `true` | Show header with branding |
|
|
285
|
-
| `showCloseButton` | `boolean` | `true` | Show close button in header |
|
|
286
|
-
| `showFooter` | `boolean` | `true` | Show "Secured by SparkVault" footer |
|
|
287
|
-
|
|
288
|
-
All other options from `pop()` are also supported (`email`, `phone`, `onSuccess`, etc.).
|
|
222
|
+
### pop() Options
|
|
289
223
|
|
|
290
|
-
|
|
224
|
+
| Option | Type | Description |
|
|
225
|
+
|--------|------|-------------|
|
|
226
|
+
| `email` | `string` | Pre-fill email address (mutually exclusive with phone) |
|
|
227
|
+
| `phone` | `string` | Pre-fill phone in E.164 format, e.g. `"+14155551234"` |
|
|
228
|
+
| `backdropBlur` | `boolean` | Override backdrop blur setting from app config |
|
|
229
|
+
| `onCancel` | `function` | Callback when user closes modal without verifying |
|
|
230
|
+
| `onSuccess` | `function` | Callback on success (before promise resolves) |
|
|
231
|
+
| `onError` | `function` | Callback on error (before promise rejects) |
|
|
291
232
|
|
|
292
|
-
|
|
293
|
-
|--------|-------------|
|
|
294
|
-
| `passkey` | WebAuthn/FIDO2 passkey authentication |
|
|
295
|
-
| `totp_email` | Time-based OTP sent via email |
|
|
296
|
-
| `totp_sms` | Time-based OTP sent via SMS |
|
|
297
|
-
| `magic_link` | One-click email verification link |
|
|
298
|
-
| `google` | Google OAuth |
|
|
299
|
-
| `apple` | Apple Sign In |
|
|
300
|
-
| `microsoft` | Microsoft OAuth |
|
|
301
|
-
| `github` | GitHub OAuth |
|
|
233
|
+
### pop() Result
|
|
302
234
|
|
|
303
|
-
|
|
235
|
+
| Field | Type | Description |
|
|
236
|
+
|-------|------|-------------|
|
|
237
|
+
| `token` | `string` | Signed JWT token (Ed25519). Verify this on your backend. |
|
|
238
|
+
| `identity` | `string` | The verified email address or phone number |
|
|
239
|
+
| `identityType` | `"email" \| "phone"` | Type of identity that was verified |
|
|
304
240
|
|
|
305
|
-
|
|
241
|
+
### Example with Options
|
|
306
242
|
|
|
307
243
|
```javascript
|
|
308
|
-
|
|
309
|
-
await
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
//
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Auto (follows system preference)
|
|
319
|
-
await sv.identity.pop({
|
|
320
|
-
theme: 'auto'
|
|
321
|
-
});
|
|
244
|
+
try {
|
|
245
|
+
const result = await sparkvault.identity.pop({
|
|
246
|
+
// Pre-fill the email field
|
|
247
|
+
email: 'user@example.com',
|
|
248
|
+
|
|
249
|
+
// Handle cancellation
|
|
250
|
+
onCancel: () => {
|
|
251
|
+
console.log('User cancelled');
|
|
252
|
+
}
|
|
253
|
+
});
|
|
322
254
|
|
|
323
|
-
//
|
|
324
|
-
await
|
|
325
|
-
backdropBlur: false
|
|
326
|
-
});
|
|
255
|
+
// Success! Send token to your backend
|
|
256
|
+
await loginWithToken(result.token);
|
|
327
257
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (error.code === 'user_cancelled') {
|
|
260
|
+
// User closed the modal
|
|
261
|
+
} else {
|
|
262
|
+
// Handle other errors
|
|
263
|
+
console.error('Verification failed:', error.message);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
334
266
|
```
|
|
335
267
|
|
|
336
|
-
|
|
337
|
-
|--------|------|---------|-------------|
|
|
338
|
-
| `theme` | `'light' \| 'dark' \| 'auto'` | Tenant default | UI color theme. `'auto'` follows system preference. |
|
|
339
|
-
| `backdropBlur` | `boolean` | `true` | Apply blur effect to the page behind the popup. |
|
|
268
|
+
### Authentication Methods
|
|
340
269
|
|
|
341
|
-
|
|
270
|
+
Authentication methods are configured in your Identity App settings. The SDK automatically shows only the methods you have enabled.
|
|
342
271
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
272
|
+
| Method | Description | Identity Type |
|
|
273
|
+
|--------|-------------|---------------|
|
|
274
|
+
| `passkey` | WebAuthn/FIDO2 biometric or security key | Email |
|
|
275
|
+
| `totp_email` | 6-digit code sent via email | Email |
|
|
276
|
+
| `totp_sms` | 6-digit code sent via SMS | Phone |
|
|
277
|
+
| `totp_voice` | 6-digit code sent via voice call | Phone |
|
|
278
|
+
| `magic_link` / `sparklink` | One-click magic link via email | Email |
|
|
279
|
+
| `google` | Sign in with Google | Email |
|
|
280
|
+
| `apple` | Sign in with Apple | Email |
|
|
281
|
+
| `microsoft` | Sign in with Microsoft | Email |
|
|
282
|
+
| `github` | Sign in with GitHub | Email |
|
|
283
|
+
| `facebook` | Sign in with Facebook | Email |
|
|
284
|
+
| `linkedin` | Sign in with LinkedIn | Email |
|
|
285
|
+
|
|
286
|
+
## Backend Token Verification
|
|
352
287
|
|
|
353
|
-
|
|
288
|
+
> **Security Critical:** Always verify tokens on your backend. Never trust the frontend result alone. Tokens are signed with Ed25519 — use the JWKS endpoint to verify the signature.
|
|
354
289
|
|
|
355
|
-
|
|
290
|
+
### Node.js
|
|
356
291
|
|
|
357
292
|
```javascript
|
|
358
|
-
|
|
359
|
-
const spark = await sv.sparks.create({
|
|
360
|
-
payload: 'secret data', // String or binary data
|
|
361
|
-
ttl: 3600, // Time-to-live in seconds (default: 3600)
|
|
362
|
-
maxReads: 1, // Max reads before burn (default: 1)
|
|
363
|
-
password: 'optional-password' // Optional password protection
|
|
364
|
-
});
|
|
293
|
+
import * as jose from 'jose';
|
|
365
294
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
console.log(spark.expiresAt); // Expiration timestamp
|
|
369
|
-
console.log(spark.maxReads); // Remaining reads
|
|
295
|
+
const ACCOUNT_ID = 'acc_YOUR_ACCOUNT_ID';
|
|
296
|
+
const IDENTITY_URL = 'https://api.sparkvault.com/v1/apps/identity';
|
|
370
297
|
|
|
371
|
-
|
|
372
|
-
const
|
|
373
|
-
// or
|
|
374
|
-
const data = await sv.sparks.read(sparkId, 'password');
|
|
298
|
+
app.post('/api/auth/login', async (req, res) => {
|
|
299
|
+
const { token } = req.body;
|
|
375
300
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
301
|
+
try {
|
|
302
|
+
// Fetch public keys and verify signature
|
|
303
|
+
const jwksUrl = `${IDENTITY_URL}/${ACCOUNT_ID}/.well-known/jwks.json`;
|
|
304
|
+
const JWKS = jose.createRemoteJWKSet(new URL(jwksUrl));
|
|
379
305
|
|
|
380
|
-
|
|
306
|
+
const { payload } = await jose.jwtVerify(token, JWKS, {
|
|
307
|
+
issuer: `${IDENTITY_URL}/${ACCOUNT_ID}`,
|
|
308
|
+
audience: ACCOUNT_ID
|
|
309
|
+
});
|
|
381
310
|
|
|
382
|
-
|
|
311
|
+
// Token is valid! Create user session
|
|
312
|
+
const user = await findOrCreateUser(payload.identity, payload.identity_type);
|
|
313
|
+
req.session.userId = user.id;
|
|
383
314
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
315
|
+
res.json({ success: true, user });
|
|
316
|
+
} catch (error) {
|
|
317
|
+
res.status(401).json({ error: 'Invalid token' });
|
|
318
|
+
}
|
|
388
319
|
});
|
|
320
|
+
```
|
|
389
321
|
|
|
390
|
-
|
|
391
|
-
console.log(vault.name); // Vault name
|
|
392
|
-
console.log(vault.vmk); // Vault Master Key (24 chars) - SAVE THIS!
|
|
393
|
-
console.log(vault.createdAt); // Creation timestamp
|
|
322
|
+
### Python
|
|
394
323
|
|
|
395
|
-
|
|
396
|
-
|
|
324
|
+
```python
|
|
325
|
+
import jwt
|
|
326
|
+
from jwt import PyJWKClient
|
|
397
327
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
console.log(unsealed.vatToken); // Vault Access Token
|
|
401
|
-
console.log(unsealed.expiresAt); // Token expiration
|
|
328
|
+
ACCOUNT_ID = 'acc_YOUR_ACCOUNT_ID'
|
|
329
|
+
IDENTITY_URL = 'https://api.sparkvault.com/v1/apps/identity'
|
|
402
330
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
});
|
|
331
|
+
def verify_identity_token(token: str):
|
|
332
|
+
"""Verify a SparkVault Identity token and return the claims."""
|
|
333
|
+
jwks_url = f"{IDENTITY_URL}/{ACCOUNT_ID}/.well-known/jwks.json"
|
|
334
|
+
jwks_client = PyJWKClient(jwks_url)
|
|
408
335
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
console.log(ingot.size); // File size in bytes
|
|
412
|
-
console.log(ingot.mimeType); // MIME type
|
|
336
|
+
# Get the signing key
|
|
337
|
+
signing_key = jwks_client.get_signing_key_from_jwt(token)
|
|
413
338
|
|
|
414
|
-
|
|
415
|
-
|
|
339
|
+
# Verify and decode
|
|
340
|
+
claims = jwt.decode(
|
|
341
|
+
token,
|
|
342
|
+
signing_key.key,
|
|
343
|
+
algorithms=["EdDSA"],
|
|
344
|
+
issuer=f"{IDENTITY_URL}/{ACCOUNT_ID}",
|
|
345
|
+
audience=ACCOUNT_ID
|
|
346
|
+
)
|
|
416
347
|
|
|
417
|
-
|
|
418
|
-
const blob = await sv.vaults.downloadIngot(unsealed, ingotId);
|
|
419
|
-
|
|
420
|
-
// Delete an ingot
|
|
421
|
-
await sv.vaults.deleteIngot(unsealed, ingotId);
|
|
422
|
-
```
|
|
348
|
+
return claims
|
|
423
349
|
|
|
424
|
-
|
|
350
|
+
# Usage in Flask
|
|
351
|
+
@app.route('/api/auth/login', methods=['POST'])
|
|
352
|
+
def login():
|
|
353
|
+
token = request.json.get('token')
|
|
425
354
|
|
|
426
|
-
|
|
355
|
+
try:
|
|
356
|
+
claims = verify_identity_token(token)
|
|
427
357
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
bytes: 32, // 1-1024 bytes
|
|
432
|
-
format: 'hex' // Output format
|
|
433
|
-
});
|
|
358
|
+
# Create session
|
|
359
|
+
user = find_or_create_user(claims['identity'], claims['identity_type'])
|
|
360
|
+
session['user_id'] = user.id
|
|
434
361
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
// Available formats
|
|
440
|
-
// 'hex' - Hexadecimal string
|
|
441
|
-
// 'base64' - Base64 encoded
|
|
442
|
-
// 'base64url' - URL-safe Base64
|
|
443
|
-
// 'bytes' - Raw Uint8Array
|
|
444
|
-
// 'alphanumeric' - Alphanumeric string
|
|
445
|
-
// 'numeric' - Numeric string
|
|
446
|
-
// 'uuid' - UUID v4 format
|
|
447
|
-
// 'password' - Password-safe characters
|
|
448
|
-
|
|
449
|
-
// Convenience methods
|
|
450
|
-
const uuid = await sv.rng.uuid(); // UUID v4
|
|
451
|
-
const password = await sv.rng.password(16); // 16-char password
|
|
362
|
+
return jsonify({'success': True, 'user': user.to_dict()})
|
|
363
|
+
except jwt.InvalidTokenError as e:
|
|
364
|
+
return jsonify({'error': 'Invalid token'}), 401
|
|
452
365
|
```
|
|
453
366
|
|
|
454
|
-
|
|
367
|
+
### Go
|
|
368
|
+
|
|
369
|
+
```go
|
|
370
|
+
package main
|
|
371
|
+
|
|
372
|
+
import (
|
|
373
|
+
"github.com/lestrrat-go/jwx/v2/jwk"
|
|
374
|
+
"github.com/lestrrat-go/jwx/v2/jwt"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
const (
|
|
378
|
+
AccountID = "acc_YOUR_ACCOUNT_ID"
|
|
379
|
+
IdentityURL = "https://api.sparkvault.com/v1/apps/identity"
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
func verifyToken(tokenString string) (jwt.Token, error) {
|
|
383
|
+
jwksURL := fmt.Sprintf("%s/%s/.well-known/jwks.json", IdentityURL, AccountID)
|
|
384
|
+
|
|
385
|
+
// Fetch JWKS
|
|
386
|
+
keySet, err := jwk.Fetch(context.Background(), jwksURL)
|
|
387
|
+
if err != nil {
|
|
388
|
+
return nil, err
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Parse and verify token
|
|
392
|
+
token, err := jwt.Parse(
|
|
393
|
+
[]byte(tokenString),
|
|
394
|
+
jwt.WithKeySet(keySet),
|
|
395
|
+
jwt.WithIssuer(fmt.Sprintf("%s/%s", IdentityURL, AccountID)),
|
|
396
|
+
jwt.WithAudience(AccountID),
|
|
397
|
+
)
|
|
398
|
+
if err != nil {
|
|
399
|
+
return nil, err
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return token, nil
|
|
403
|
+
}
|
|
455
404
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
405
|
+
// Usage in HTTP handler
|
|
406
|
+
func loginHandler(w http.ResponseWriter, r *http.Request) {
|
|
407
|
+
var body struct {
|
|
408
|
+
Token string `json:"token"`
|
|
409
|
+
}
|
|
410
|
+
json.NewDecoder(r.Body).Decode(&body)
|
|
411
|
+
|
|
412
|
+
token, err := verifyToken(body.Token)
|
|
413
|
+
if err != nil {
|
|
414
|
+
http.Error(w, "Invalid token", http.StatusUnauthorized)
|
|
415
|
+
return
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
identity, _ := token.Get("identity")
|
|
419
|
+
// Create session with identity...
|
|
420
|
+
}
|
|
466
421
|
```
|
|
467
422
|
|
|
468
|
-
###
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
423
|
+
### Ruby
|
|
424
|
+
|
|
425
|
+
```ruby
|
|
426
|
+
require 'jwt'
|
|
427
|
+
require 'net/http'
|
|
428
|
+
require 'json'
|
|
429
|
+
|
|
430
|
+
ACCOUNT_ID = 'acc_YOUR_ACCOUNT_ID'
|
|
431
|
+
IDENTITY_URL = 'https://api.sparkvault.com/v1/apps/identity'
|
|
432
|
+
|
|
433
|
+
def verify_identity_token(token)
|
|
434
|
+
jwks_url = "#{IDENTITY_URL}/#{ACCOUNT_ID}/.well-known/jwks.json"
|
|
435
|
+
|
|
436
|
+
# Fetch JWKS
|
|
437
|
+
uri = URI(jwks_url)
|
|
438
|
+
response = Net::HTTP.get(uri)
|
|
439
|
+
jwks = JSON.parse(response)
|
|
440
|
+
|
|
441
|
+
# Decode and verify
|
|
442
|
+
JWT.decode(
|
|
443
|
+
token,
|
|
444
|
+
nil,
|
|
445
|
+
true,
|
|
446
|
+
{
|
|
447
|
+
algorithms: ['EdDSA'],
|
|
448
|
+
iss: "#{IDENTITY_URL}/#{ACCOUNT_ID}",
|
|
449
|
+
aud: ACCOUNT_ID,
|
|
450
|
+
verify_iss: true,
|
|
451
|
+
verify_aud: true,
|
|
452
|
+
jwks: jwks
|
|
453
|
+
}
|
|
454
|
+
).first
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
# Usage in Rails controller
|
|
458
|
+
class AuthController < ApplicationController
|
|
459
|
+
def login
|
|
460
|
+
claims = verify_identity_token(params[:token])
|
|
461
|
+
|
|
462
|
+
user = User.find_or_create_by(email: claims['identity'])
|
|
463
|
+
session[:user_id] = user.id
|
|
464
|
+
|
|
465
|
+
render json: { success: true, user: user }
|
|
466
|
+
rescue JWT::DecodeError => e
|
|
467
|
+
render json: { error: 'Invalid token' }, status: :unauthorized
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
```
|
|
478
471
|
|
|
479
|
-
|
|
472
|
+
### Token Claims
|
|
480
473
|
|
|
481
|
-
|
|
482
|
-
// Default behavior (preloadConfig: true)
|
|
483
|
-
// Config is fetched immediately when SDK initializes
|
|
484
|
-
const sv = SparkVault.init({ accountId: 'acc_xxx' });
|
|
474
|
+
When decoded, the JWT token contains these claims:
|
|
485
475
|
|
|
486
|
-
|
|
487
|
-
|
|
476
|
+
```json
|
|
477
|
+
{
|
|
478
|
+
"iss": "https://api.sparkvault.com/v1/apps/identity/acc_xxx",
|
|
479
|
+
"sub": "identity:a1b2c3d4e5f67890",
|
|
480
|
+
"aud": "acc_xxx",
|
|
481
|
+
"identity": "user@example.com",
|
|
482
|
+
"identity_type": "email",
|
|
483
|
+
"method": "totp_email",
|
|
484
|
+
"verified_at": 1703977195,
|
|
485
|
+
"iat": 1703977200,
|
|
486
|
+
"exp": 1703980800,
|
|
487
|
+
"jti": "tok_unique_id"
|
|
488
|
+
}
|
|
488
489
|
```
|
|
489
490
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
491
|
+
| Claim | Type | Description |
|
|
492
|
+
|-------|------|-------------|
|
|
493
|
+
| `iss` | `string` | Issuer URL — always verify this matches expected value |
|
|
494
|
+
| `sub` | `string` | Subject — hashed identity for consistent user identification |
|
|
495
|
+
| `aud` | `string` | Audience — your account ID |
|
|
496
|
+
| `identity` | `string` | The verified email address or phone number |
|
|
497
|
+
| `identity_type` | `string` | `"email"` or `"phone"` |
|
|
498
|
+
| `method` | `string` | Authentication method used (e.g., `"totp_email"`, `"passkey"`) |
|
|
499
|
+
| `verified_at` | `number` | Unix timestamp when identity was verified |
|
|
500
|
+
| `iat` | `number` | Issued at timestamp |
|
|
501
|
+
| `exp` | `number` | Expiration timestamp |
|
|
502
|
+
| `jti` | `string` | Unique token ID for replay protection |
|
|
503
|
+
|
|
504
|
+
## React Integration
|
|
505
|
+
|
|
506
|
+
```jsx
|
|
507
|
+
import { useState, useCallback } from 'react';
|
|
508
|
+
import SparkVault from '@sparkvault/sdk';
|
|
509
|
+
|
|
510
|
+
// Initialize once, outside component
|
|
511
|
+
const sparkvault = SparkVault.init({
|
|
512
|
+
accountId: 'acc_YOUR_ACCOUNT_ID'
|
|
497
513
|
});
|
|
498
514
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
515
|
+
function useSparkVaultAuth() {
|
|
516
|
+
const [user, setUser] = useState(null);
|
|
517
|
+
const [loading, setLoading] = useState(false);
|
|
518
|
+
const [error, setError] = useState(null);
|
|
519
|
+
|
|
520
|
+
const login = useCallback(async (options = {}) => {
|
|
521
|
+
setLoading(true);
|
|
522
|
+
setError(null);
|
|
523
|
+
|
|
524
|
+
try {
|
|
525
|
+
// Open verification modal
|
|
526
|
+
const result = await sparkvault.identity.pop(options);
|
|
527
|
+
|
|
528
|
+
// Send token to backend
|
|
529
|
+
const response = await fetch('/api/auth/login', {
|
|
530
|
+
method: 'POST',
|
|
531
|
+
headers: { 'Content-Type': 'application/json' },
|
|
532
|
+
body: JSON.stringify({ token: result.token })
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
if (!response.ok) throw new Error('Login failed');
|
|
536
|
+
|
|
537
|
+
const data = await response.json();
|
|
538
|
+
setUser(data.user);
|
|
539
|
+
return data.user;
|
|
540
|
+
|
|
541
|
+
} catch (err) {
|
|
542
|
+
if (err.code !== 'user_cancelled') {
|
|
543
|
+
setError(err.message);
|
|
544
|
+
}
|
|
545
|
+
throw err;
|
|
546
|
+
} finally {
|
|
547
|
+
setLoading(false);
|
|
548
|
+
}
|
|
549
|
+
}, []);
|
|
550
|
+
|
|
551
|
+
const logout = useCallback(async () => {
|
|
552
|
+
await fetch('/api/auth/logout', { method: 'POST' });
|
|
553
|
+
setUser(null);
|
|
554
|
+
}, []);
|
|
555
|
+
|
|
556
|
+
return { user, loading, error, login, logout };
|
|
557
|
+
}
|
|
502
558
|
|
|
503
|
-
|
|
559
|
+
// Usage in component
|
|
560
|
+
function LoginPage() {
|
|
561
|
+
const { user, loading, error, login, logout } = useSparkVaultAuth();
|
|
562
|
+
|
|
563
|
+
if (user) {
|
|
564
|
+
return (
|
|
565
|
+
<div>
|
|
566
|
+
<p>Welcome, {user.email}!</p>
|
|
567
|
+
<button onClick={logout}>Sign out</button>
|
|
568
|
+
</div>
|
|
569
|
+
);
|
|
570
|
+
}
|
|
504
571
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
572
|
+
return (
|
|
573
|
+
<div>
|
|
574
|
+
<button onClick={() => login()} disabled={loading}>
|
|
575
|
+
{loading ? 'Verifying...' : 'Sign in'}
|
|
576
|
+
</button>
|
|
577
|
+
{error && <p className="error">{error}</p>}
|
|
578
|
+
</div>
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
```
|
|
509
582
|
|
|
510
583
|
## Error Handling
|
|
511
584
|
|
|
512
|
-
The SDK
|
|
585
|
+
The SDK throws typed errors that you can catch and handle appropriately.
|
|
513
586
|
|
|
514
587
|
```javascript
|
|
515
|
-
import {
|
|
516
|
-
|
|
517
|
-
ValidationError,
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
NetworkError, // Network failure
|
|
521
|
-
TimeoutError, // Request timeout
|
|
522
|
-
PopupBlockedError, // Popup was blocked
|
|
523
|
-
UserCancelledError // User closed popup
|
|
588
|
+
import SparkVault, {
|
|
589
|
+
UserCancelledError,
|
|
590
|
+
ValidationError,
|
|
591
|
+
NetworkError,
|
|
592
|
+
TimeoutError
|
|
524
593
|
} from '@sparkvault/sdk';
|
|
525
594
|
|
|
526
595
|
try {
|
|
527
|
-
await
|
|
596
|
+
const result = await sparkvault.identity.pop();
|
|
528
597
|
} catch (error) {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
598
|
+
switch (error.code) {
|
|
599
|
+
case 'user_cancelled':
|
|
600
|
+
// User closed the modal — not necessarily an error
|
|
601
|
+
console.log('User cancelled verification');
|
|
602
|
+
break;
|
|
603
|
+
|
|
604
|
+
case 'validation_error':
|
|
605
|
+
// Invalid input (e.g., malformed email)
|
|
606
|
+
console.error('Validation error:', error.message);
|
|
607
|
+
break;
|
|
608
|
+
|
|
609
|
+
case 'network_error':
|
|
610
|
+
// Network connectivity issue
|
|
611
|
+
console.error('Network error — please check your connection');
|
|
612
|
+
break;
|
|
613
|
+
|
|
614
|
+
case 'timeout_error':
|
|
615
|
+
// Request timed out
|
|
616
|
+
console.error('Request timed out — please try again');
|
|
617
|
+
break;
|
|
618
|
+
|
|
619
|
+
case 'popup_blocked':
|
|
620
|
+
// Browser blocked the popup
|
|
621
|
+
console.error('Please allow popups for this site');
|
|
622
|
+
break;
|
|
623
|
+
|
|
624
|
+
default:
|
|
625
|
+
console.error('Unexpected error:', error.message);
|
|
544
626
|
}
|
|
545
627
|
}
|
|
546
628
|
```
|
|
547
629
|
|
|
548
|
-
### Error
|
|
630
|
+
### Error Types
|
|
549
631
|
|
|
550
|
-
|
|
632
|
+
| Error Class | Code | Description |
|
|
633
|
+
|-------------|------|-------------|
|
|
634
|
+
| `UserCancelledError` | `user_cancelled` | User closed the modal without completing verification |
|
|
635
|
+
| `ValidationError` | `validation_error` | Invalid input or configuration |
|
|
636
|
+
| `NetworkError` | `network_error` | Network connectivity failure |
|
|
637
|
+
| `TimeoutError` | `timeout_error` | Request exceeded timeout limit |
|
|
638
|
+
| `PopupBlockedError` | `popup_blocked` | Browser blocked the popup window |
|
|
639
|
+
| `AuthenticationError` | `authentication_error` | Authentication failed (e.g., expired token) |
|
|
551
640
|
|
|
552
|
-
|
|
553
|
-
interface SparkVaultError extends Error {
|
|
554
|
-
message: string; // Human-readable message
|
|
555
|
-
code: string; // Error code (e.g., 'validation_error')
|
|
556
|
-
statusCode?: number; // HTTP status code if applicable
|
|
557
|
-
details?: Record<string, any>; // Additional error details
|
|
558
|
-
}
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
## TypeScript
|
|
641
|
+
## TypeScript Support
|
|
562
642
|
|
|
563
|
-
The SDK is written in TypeScript and includes full type definitions
|
|
643
|
+
The SDK is written in TypeScript and includes full type definitions.
|
|
564
644
|
|
|
565
645
|
```typescript
|
|
566
|
-
import {
|
|
567
|
-
|
|
646
|
+
import SparkVault, {
|
|
647
|
+
// Configuration
|
|
568
648
|
SparkVaultConfig,
|
|
649
|
+
|
|
650
|
+
// Identity types
|
|
569
651
|
VerifyOptions,
|
|
570
|
-
RenderOptions,
|
|
571
652
|
VerifyResult,
|
|
572
653
|
TokenClaims,
|
|
573
|
-
AuthMethod,
|
|
574
|
-
CreateSparkOptions,
|
|
575
|
-
Spark,
|
|
576
|
-
SparkPayload,
|
|
577
|
-
CreateVaultOptions,
|
|
578
|
-
Vault,
|
|
579
|
-
UnsealedVault,
|
|
580
|
-
Ingot,
|
|
581
|
-
UploadIngotOptions,
|
|
582
|
-
GenerateOptions,
|
|
583
|
-
RandomResult,
|
|
584
|
-
RNGFormat
|
|
585
|
-
} from '@sparkvault/sdk';
|
|
586
|
-
|
|
587
|
-
// All methods are fully typed
|
|
588
|
-
const sv = SparkVault.init({
|
|
589
|
-
accountId: 'acc_test'
|
|
590
|
-
});
|
|
591
654
|
|
|
592
|
-
//
|
|
593
|
-
|
|
594
|
-
|
|
655
|
+
// Error types
|
|
656
|
+
SparkVaultError,
|
|
657
|
+
UserCancelledError,
|
|
658
|
+
ValidationError,
|
|
659
|
+
NetworkError,
|
|
660
|
+
TimeoutError,
|
|
661
|
+
PopupBlockedError,
|
|
662
|
+
} from '@sparkvault/sdk';
|
|
595
663
|
|
|
596
|
-
|
|
664
|
+
// Fully typed configuration
|
|
665
|
+
const config: SparkVaultConfig = {
|
|
666
|
+
accountId: 'acc_YOUR_ACCOUNT_ID',
|
|
667
|
+
timeout: 30000,
|
|
668
|
+
};
|
|
597
669
|
|
|
598
|
-
|
|
670
|
+
const sparkvault = SparkVault.init(config);
|
|
599
671
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
672
|
+
// Fully typed options and result
|
|
673
|
+
const options: VerifyOptions = {
|
|
674
|
+
email: 'user@example.com',
|
|
675
|
+
onCancel: () => console.log('Cancelled'),
|
|
676
|
+
};
|
|
604
677
|
|
|
605
|
-
|
|
678
|
+
const result: VerifyResult = await sparkvault.identity.pop(options);
|
|
679
|
+
// result.token: string
|
|
680
|
+
// result.identity: string
|
|
681
|
+
// result.identityType: 'email' | 'phone'
|
|
682
|
+
```
|
|
606
683
|
|
|
607
|
-
|
|
684
|
+
## Best Practices
|
|
608
685
|
|
|
609
|
-
1. **SparkVault
|
|
610
|
-
2. **Your backend validates the token** → Checks signature via JWKS
|
|
611
|
-
3. **Your backend creates YOUR session** → Cookie, JWT, database session
|
|
612
|
-
4. **Token is discarded** → Your session takes over
|
|
686
|
+
1. **Initialize Once** — Create the SparkVault instance once when your app loads, not on every login attempt. The SDK preloads configuration for instant modal opening.
|
|
613
687
|
|
|
614
|
-
|
|
688
|
+
2. **Always Verify Server-Side** — Never trust client-side token validation in production. Always verify the JWT signature on your backend using the JWKS endpoint before creating a session.
|
|
615
689
|
|
|
616
|
-
|
|
690
|
+
3. **Handle Cancellation Gracefully** — Users may close the modal without completing verification. This throws a `UserCancelledError` — handle it gracefully rather than showing an error message.
|
|
617
691
|
|
|
618
|
-
|
|
619
|
-
- `postMessage` is used securely with origin checking
|
|
620
|
-
- Popups are isolated from your main window context
|
|
692
|
+
4. **Cache JWKS Keys** — The JWKS endpoint returns public keys with caching headers. Use a library like `jose` that handles caching automatically, or cache keys for 5-10 minutes.
|
|
621
693
|
|
|
622
|
-
|
|
694
|
+
## Browser Support
|
|
623
695
|
|
|
624
|
-
|
|
625
|
-
- The SDK only requires your account ID (no secrets in client-side code)
|
|
696
|
+
The SDK supports all modern browsers:
|
|
626
697
|
|
|
627
|
-
|
|
698
|
+
- Chrome 80+
|
|
699
|
+
- Firefox 75+
|
|
700
|
+
- Safari 13.1+
|
|
701
|
+
- Edge 80+
|
|
628
702
|
|
|
629
|
-
|
|
630
|
-
2. **Use HTTPS** - Always serve your application over HTTPS
|
|
631
|
-
3. **Keep VMKs secure** - Vault Master Keys should be stored securely by users
|
|
703
|
+
**Note:** Passkey authentication requires WebAuthn support. On unsupported browsers, passkey will not appear as an option — other methods will still work.
|
|
632
704
|
|
|
633
705
|
## Development
|
|
634
706
|
|
|
@@ -660,19 +732,7 @@ sdk-js/
|
|
|
660
732
|
│ ├── config.ts # Configuration management
|
|
661
733
|
│ ├── http.ts # HTTP client
|
|
662
734
|
│ ├── errors.ts # Error types
|
|
663
|
-
│
|
|
664
|
-
│ │ ├── index.ts
|
|
665
|
-
│ │ ├── modal.ts # Popup manager
|
|
666
|
-
│ │ └── types.ts
|
|
667
|
-
│ ├── sparks/ # Sparks module
|
|
668
|
-
│ │ ├── index.ts
|
|
669
|
-
│ │ └── types.ts
|
|
670
|
-
│ ├── vaults/ # Vaults module
|
|
671
|
-
│ │ ├── index.ts
|
|
672
|
-
│ │ └── types.ts
|
|
673
|
-
│ └── rng/ # RNG module
|
|
674
|
-
│ ├── index.ts
|
|
675
|
-
│ └── types.ts
|
|
735
|
+
│ └── identity/ # Identity module
|
|
676
736
|
├── dist/ # Built bundles
|
|
677
737
|
├── tests/ # Test files
|
|
678
738
|
├── .github/workflows/ # CI/CD workflows
|
|
@@ -681,32 +741,6 @@ sdk-js/
|
|
|
681
741
|
└── package.json
|
|
682
742
|
```
|
|
683
743
|
|
|
684
|
-
### CI/CD
|
|
685
|
-
|
|
686
|
-
The SDK uses GitHub Actions for automated testing and deployment:
|
|
687
|
-
|
|
688
|
-
- **On PR**: Runs build, lint, typecheck, and tests
|
|
689
|
-
- **On push to main**: Deploys to CDN (`cdn.sparkvault.com`)
|
|
690
|
-
- **On release commit**: Publishes to npm
|
|
691
|
-
|
|
692
|
-
Required secrets for deployment:
|
|
693
|
-
- `CLOUDFLARE_API_TOKEN` - Cloudflare API token with R2 access
|
|
694
|
-
- `CLOUDFLARE_ACCOUNT_ID` - Cloudflare account ID
|
|
695
|
-
- `NPM_TOKEN` - npm publish token (for releases)
|
|
696
|
-
|
|
697
|
-
## Browser Support
|
|
698
|
-
|
|
699
|
-
- Chrome 80+
|
|
700
|
-
- Firefox 75+
|
|
701
|
-
- Safari 13.1+
|
|
702
|
-
- Edge 80+
|
|
703
|
-
|
|
704
|
-
The SDK requires:
|
|
705
|
-
- `fetch` API
|
|
706
|
-
- `Promise`
|
|
707
|
-
- `window.open` (for Identity popup)
|
|
708
|
-
- `postMessage` (for popup communication)
|
|
709
|
-
|
|
710
744
|
## License
|
|
711
745
|
|
|
712
746
|
MIT License - see [LICENSE](LICENSE) for details.
|