@stvor/sdk 2.0.0
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/docs/index.mdx +649 -0
- package/example.ts +64 -0
- package/facade/app.ts +331 -0
- package/facade/errors.ts +149 -0
- package/facade/index.ts +21 -0
- package/facade/relay-client.ts +173 -0
- package/facade/types.ts +65 -0
- package/index.ts +9 -0
- package/legacy.ts +158 -0
- package/package.json +34 -0
package/docs/index.mdx
ADDED
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: STVOR SDK Documentation
|
|
3
|
+
description: Client-side E2E encryption SDK for modern applications
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# STVOR SDK
|
|
7
|
+
|
|
8
|
+
Client-side end-to-end encryption SDK. Secure by default, zero crypto knowledge required.
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
import { Stvor } from '@stvor/sdk';
|
|
12
|
+
|
|
13
|
+
const app = await Stvor.init({ appToken: 'sk_live_...' });
|
|
14
|
+
const alice = await app.connect('alice@example.com');
|
|
15
|
+
await alice.send('bob@example.com', 'Hello!');
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Quick Links
|
|
21
|
+
|
|
22
|
+
- [Quickstart](#-quickstart) — Get started in 5 minutes
|
|
23
|
+
- [API Reference](#api-reference) — Full API documentation
|
|
24
|
+
- [Errors](#errors) — Error codes and recovery
|
|
25
|
+
- [Security](#security-model) — What STVOR protects
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
<CodeGroup>
|
|
32
|
+
```bash npm
|
|
33
|
+
npm install @stvor/sdk
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```bash pnpm
|
|
37
|
+
pnpm add @stvor/sdk
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```bash yarn
|
|
41
|
+
yarn add @stvor/sdk
|
|
42
|
+
```
|
|
43
|
+
</CodeGroup>
|
|
44
|
+
|
|
45
|
+
Requires a modern browser with [Web Crypto API](https://caniuse.com/cryptography) support.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Quickstart
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { Stvor } from '@stvor/sdk';
|
|
53
|
+
|
|
54
|
+
// 1. Initialize SDK
|
|
55
|
+
const app = await Stvor.init({
|
|
56
|
+
appToken: 'sk_live_abc123...'
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// 2. Connect as user
|
|
60
|
+
const alice = await app.connect('alice@example.com');
|
|
61
|
+
|
|
62
|
+
// 3. Send encrypted message
|
|
63
|
+
await alice.send('bob@example.com', 'Hello Bob!');
|
|
64
|
+
|
|
65
|
+
// 4. Receive messages
|
|
66
|
+
const message = await alice.receive();
|
|
67
|
+
console.log(`${message.senderId}: ${message.content}`);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
<Tip>
|
|
71
|
+
**That's it.** No crypto configuration, no key management, no protocol choices.
|
|
72
|
+
</Tip>
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## AppToken
|
|
77
|
+
|
|
78
|
+
Get your AppToken from the [developer dashboard](https://dashboard.stvor.io).
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const app = await Stvor.init({
|
|
82
|
+
appToken: 'sk_live_abc123...', // From dashboard
|
|
83
|
+
relayUrl: 'https://relay.stvor.io' // Optional
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| Parameter | Required | Default | Description |
|
|
88
|
+
|-----------|----------|---------|-------------|
|
|
89
|
+
| `appToken` | Yes | — | Token from developer dashboard |
|
|
90
|
+
| `relayUrl` | No | `https://relay.stvor.io` | Relay server URL |
|
|
91
|
+
|
|
92
|
+
<Warning>
|
|
93
|
+
Never commit AppToken to version control. Use environment variables:
|
|
94
|
+
```typescript
|
|
95
|
+
const app = await Stvor.init({
|
|
96
|
+
appToken: process.env.STVOR_APP_TOKEN
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
</Warning>
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
# SDK Basics
|
|
104
|
+
|
|
105
|
+
## Initialization
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const app = await Stvor.init({ appToken: 'sk_live_...' });
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Creates SDK instance. Call once at app startup.
|
|
112
|
+
|
|
113
|
+
| Returns | Description |
|
|
114
|
+
|---------|-------------|
|
|
115
|
+
| `StvorApp` | Main SDK instance for creating users |
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// Check if ready
|
|
119
|
+
app.isReady(); // boolean
|
|
120
|
+
|
|
121
|
+
// Cleanup when done
|
|
122
|
+
await app.disconnect();
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Users & Identity
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const user = await app.connect('alice@example.com');
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Connects a user and establishes their encryption identity.
|
|
134
|
+
|
|
135
|
+
| Parameter | Type | Description |
|
|
136
|
+
|-----------|------|-------------|
|
|
137
|
+
| `userId` | `string` | User identifier (email, username, UUID) |
|
|
138
|
+
|
|
139
|
+
| Returns | Description |
|
|
140
|
+
|---------|-------------|
|
|
141
|
+
| `StvorClient` | Client instance for this user |
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// Get current user ID
|
|
145
|
+
user.getUserId(); // 'alice@example.com'
|
|
146
|
+
|
|
147
|
+
// Multiple users
|
|
148
|
+
const bob = await app.connect('bob@example.com');
|
|
149
|
+
await alice.send('bob@example.com', 'Hey Bob!');
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
<Tip>
|
|
153
|
+
Each device = separate user. Same user on different devices has different identity.
|
|
154
|
+
</Tip>
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Sending Messages
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
await alice.send('bob@example.com', 'Hello Bob!');
|
|
162
|
+
await alice.send('bob@example.com', new Uint8Array([1, 2, 3]));
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Sends an encrypted message to a recipient.
|
|
166
|
+
|
|
167
|
+
| Parameter | Type | Description |
|
|
168
|
+
|-----------|------|-------------|
|
|
169
|
+
| `recipientId` | `string` | Recipient's user ID |
|
|
170
|
+
| `content` | `string \| Uint8Array` | Message text or binary data |
|
|
171
|
+
|
|
172
|
+
| Returns | Description |
|
|
173
|
+
|---------|-------------|
|
|
174
|
+
| `Promise<void>` | Resolves when message is sent |
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// Send text
|
|
178
|
+
await alice.send('bob@example.com', 'Hello!');
|
|
179
|
+
|
|
180
|
+
// Send binary (files, images)
|
|
181
|
+
const fileData = await fetchFile();
|
|
182
|
+
await alice.send('bob@example.com', fileData);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
<Warning>
|
|
186
|
+
Message is delivered asynchronously. Use `await` to ensure message is sent.
|
|
187
|
+
</Warning>
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Receiving Messages
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
const message = await alice.receive();
|
|
195
|
+
console.log(message.content);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Blocks until a message is available, then returns decrypted content.
|
|
199
|
+
|
|
200
|
+
| Returns | Description |
|
|
201
|
+
|---------|-------------|
|
|
202
|
+
| `Promise<DecryptedMessage>` | Decrypted message object |
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
interface DecryptedMessage {
|
|
206
|
+
id: string; // Message identifier
|
|
207
|
+
senderId: string; // Sender's user ID
|
|
208
|
+
content: string | Uint8Array;
|
|
209
|
+
timestamp: Date; // When message was sent
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// Blocking receive
|
|
215
|
+
const msg = await alice.receive();
|
|
216
|
+
console.log(`${msg.senderId}: ${msg.content}`);
|
|
217
|
+
|
|
218
|
+
// Non-blocking with subscription
|
|
219
|
+
const unsubscribe = alice.onMessage((msg) => {
|
|
220
|
+
console.log(`[Push] ${msg.senderId}: ${msg.content}`);
|
|
221
|
+
});
|
|
222
|
+
// Later...
|
|
223
|
+
unsubscribe();
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
<Tip>
|
|
227
|
+
`receive()` blocks indefinitely. For production, use `onMessage()` subscription pattern.
|
|
228
|
+
</Tip>
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Sealing Data
|
|
233
|
+
|
|
234
|
+
For encrypting files, API payloads, or any binary data:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
const sealed = await alice.seal(fileData, 'bob@example.com');
|
|
238
|
+
// Send sealed.ciphertext via your server...
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Encrypts data for a specific recipient without establishing a session.
|
|
242
|
+
|
|
243
|
+
| Parameter | Type | Description |
|
|
244
|
+
|-----------|------|-------------|
|
|
245
|
+
| `data` | `string \| Uint8Array` | Data to encrypt |
|
|
246
|
+
| `recipientId` | `string` | Recipient's user ID |
|
|
247
|
+
|
|
248
|
+
| Returns | Description |
|
|
249
|
+
|---------|-------------|
|
|
250
|
+
| `Promise<SealedPayload>` | Encrypted package |
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
interface SealedPayload {
|
|
254
|
+
ciphertext: Uint8Array; // Encrypted data
|
|
255
|
+
nonce: Uint8Array; // Encryption nonce
|
|
256
|
+
recipientId: string; // Who this was sealed for
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
// Seal a file
|
|
262
|
+
const fileContent = await readFile('document.pdf');
|
|
263
|
+
const sealed = await alice.seal(fileContent, 'bob@example.com');
|
|
264
|
+
|
|
265
|
+
// Send via your server
|
|
266
|
+
await fetch('/api/files', {
|
|
267
|
+
method: 'POST',
|
|
268
|
+
body: JSON.stringify({
|
|
269
|
+
ciphertext: Array.from(sealed.ciphertext),
|
|
270
|
+
nonce: Array.from(sealed.nonce)
|
|
271
|
+
})
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Opening Sealed Data
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
const decrypted = await bob.open(sealed);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Decrypts data that was sealed for the recipient.
|
|
284
|
+
|
|
285
|
+
| Parameter | Type | Description |
|
|
286
|
+
|-----------|------|-------------|
|
|
287
|
+
| `sealed` | `SealedPayload` | Sealed payload from sender |
|
|
288
|
+
|
|
289
|
+
| Returns | Description |
|
|
290
|
+
|---------|-------------|
|
|
291
|
+
| `Promise<Uint8Array>` | Decrypted data |
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// Receive sealed payload from server
|
|
295
|
+
const response = await fetch('/api/files/123');
|
|
296
|
+
const sealed: SealedPayload = await response.json();
|
|
297
|
+
|
|
298
|
+
// Open it
|
|
299
|
+
const decrypted = await bob.open(sealed);
|
|
300
|
+
// Use decrypted data...
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
# API Reference
|
|
306
|
+
|
|
307
|
+
## Stvor.init()
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
function init(config: StvorAppConfig): Promise<StvorApp>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Initializes the SDK with your AppToken.
|
|
314
|
+
|
|
315
|
+
### Parameters
|
|
316
|
+
|
|
317
|
+
| Name | Type | Required | Description |
|
|
318
|
+
|------|------|----------|-------------|
|
|
319
|
+
| `config.appToken` | `string` | Yes | Token from developer dashboard |
|
|
320
|
+
| `config.relayUrl` | `string` | No | Relay server URL |
|
|
321
|
+
| `config.timeout` | `number` | No | Connection timeout (ms) |
|
|
322
|
+
|
|
323
|
+
### Returns
|
|
324
|
+
|
|
325
|
+
`Promise<StvorApp>` — Main SDK instance
|
|
326
|
+
|
|
327
|
+
### Example
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
const app = await Stvor.init({
|
|
331
|
+
appToken: process.env.STVOR_APP_TOKEN,
|
|
332
|
+
relayUrl: 'https://relay.stvor.io',
|
|
333
|
+
timeout: 15000
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Errors
|
|
338
|
+
|
|
339
|
+
- `AUTH_FAILED` — Invalid or revoked AppToken
|
|
340
|
+
- `RELAY_UNAVAILABLE` — Cannot connect to relay
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## StvorApp.connect()
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
function connect(userId: string): Promise<StvorClient>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Connects a user and returns a client for messaging operations.
|
|
351
|
+
|
|
352
|
+
### Parameters
|
|
353
|
+
|
|
354
|
+
| Name | Type | Required | Description |
|
|
355
|
+
|------|------|----------|-------------|
|
|
356
|
+
| `userId` | `string` | Yes | User identifier |
|
|
357
|
+
|
|
358
|
+
### Returns
|
|
359
|
+
|
|
360
|
+
`Promise<StvorClient>` — Client for messaging
|
|
361
|
+
|
|
362
|
+
### Example
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
const alice = await app.connect('alice@example.com');
|
|
366
|
+
await alice.send('bob@example.com', 'Hello!');
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## StvorClient.send()
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
function send(recipientId: string, content: string | Uint8Array): Promise<void>
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Sends an encrypted message to a recipient.
|
|
378
|
+
|
|
379
|
+
### Parameters
|
|
380
|
+
|
|
381
|
+
| Name | Type | Required | Description |
|
|
382
|
+
|------|------|----------|-------------|
|
|
383
|
+
| `recipientId` | `string` | Yes | Recipient's user ID |
|
|
384
|
+
| `content` | `string \| Uint8Array` | Yes | Message content |
|
|
385
|
+
|
|
386
|
+
### Returns
|
|
387
|
+
|
|
388
|
+
`Promise<void>` — Resolves when sent
|
|
389
|
+
|
|
390
|
+
### Example
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
await alice.send('bob@example.com', 'Hello Bob!');
|
|
394
|
+
await alice.send('bob@example.com', binaryData);
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Errors
|
|
398
|
+
|
|
399
|
+
- `RECIPIENT_UNAVAILABLE` — Recipient offline > 24h
|
|
400
|
+
- `DELIVERY_FAILED` — Message could not be delivered
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## StvorClient.receive()
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
function receive(): Promise<DecryptedMessage>
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Receives and decrypts the next message.
|
|
411
|
+
|
|
412
|
+
### Parameters
|
|
413
|
+
|
|
414
|
+
None
|
|
415
|
+
|
|
416
|
+
### Returns
|
|
417
|
+
|
|
418
|
+
`Promise<DecryptedMessage>` — Decrypted message
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
interface DecryptedMessage {
|
|
422
|
+
id: string;
|
|
423
|
+
senderId: string;
|
|
424
|
+
content: string | Uint8Array;
|
|
425
|
+
timestamp: Date;
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Example
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
const msg = await alice.receive();
|
|
433
|
+
console.log(msg.content);
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Errors
|
|
437
|
+
|
|
438
|
+
- `MESSAGE_INTEGRITY_FAILED` — Message corrupted or tampered
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## StvorClient.seal()
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
function seal(data: string | Uint8Array, recipientId: string): Promise<SealedPayload>
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Encrypts data for a specific recipient.
|
|
449
|
+
|
|
450
|
+
### Parameters
|
|
451
|
+
|
|
452
|
+
| Name | Type | Required | Description |
|
|
453
|
+
|------|------|----------|-------------|
|
|
454
|
+
| `data` | `string \| Uint8Array` | Yes | Data to encrypt |
|
|
455
|
+
| `recipientId` | `string` | Yes | Recipient's user ID |
|
|
456
|
+
|
|
457
|
+
### Returns
|
|
458
|
+
|
|
459
|
+
`Promise<SealedPayload>` — Encrypted payload
|
|
460
|
+
|
|
461
|
+
### Example
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
const sealed = await alice.seal(fileData, 'bob@example.com');
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## StvorClient.open()
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
function open(sealed: SealedPayload): Promise<Uint8Array>
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Decrypts sealed data.
|
|
476
|
+
|
|
477
|
+
### Parameters
|
|
478
|
+
|
|
479
|
+
| Name | Type | Required | Description |
|
|
480
|
+
|------|------|----------|-------------|
|
|
481
|
+
| `sealed` | `SealedPayload` | Yes | Sealed payload |
|
|
482
|
+
|
|
483
|
+
### Returns
|
|
484
|
+
|
|
485
|
+
`Promise<Uint8Array>` — Decrypted data
|
|
486
|
+
|
|
487
|
+
### Example
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
const decrypted = await bob.open(sealed);
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## StvorClient.onMessage()
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
function onMessage(handler: (msg: DecryptedMessage) => void): () => void
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Subscribes to incoming messages.
|
|
502
|
+
|
|
503
|
+
### Parameters
|
|
504
|
+
|
|
505
|
+
| Name | Type | Required | Description |
|
|
506
|
+
|------|------|----------|-------------|
|
|
507
|
+
| `handler` | `function` | Yes | Message callback |
|
|
508
|
+
|
|
509
|
+
### Returns
|
|
510
|
+
|
|
511
|
+
`() => void` — Unsubscribe function
|
|
512
|
+
|
|
513
|
+
### Example
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
const unsubscribe = alice.onMessage((msg) => {
|
|
517
|
+
console.log(`${msg.senderId}: ${msg.content}`);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// Unsubscribe later
|
|
521
|
+
unsubscribe();
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
# Errors
|
|
527
|
+
|
|
528
|
+
## Error Codes
|
|
529
|
+
|
|
530
|
+
| Code | Message | Action |
|
|
531
|
+
|------|---------|--------|
|
|
532
|
+
| `AUTH_FAILED` | AppToken is invalid or revoked | Get new token from dashboard |
|
|
533
|
+
| `RELAY_UNAVAILABLE` | Cannot connect to relay server | Check network connection |
|
|
534
|
+
| `RECIPIENT_UNAVAILABLE` | Recipient offline > 24 hours | Wait for recipient to come online |
|
|
535
|
+
| `MESSAGE_INTEGRITY_FAILED` | Message corrupted or tampered | Request message again |
|
|
536
|
+
| `KEYSTORE_CORRUPTED` | Local keystore corrupted | User must re-register device |
|
|
537
|
+
| `DEVICE_COMPROMISED` | Security violation detected | Log out user immediately |
|
|
538
|
+
| `PROTOCOL_VERSION_MISMATCH` | App version outdated | Update SDK |
|
|
539
|
+
| `DELIVERY_FAILED` | Message delivery failed | Check recipient exists |
|
|
540
|
+
|
|
541
|
+
## Error Handling
|
|
542
|
+
|
|
543
|
+
```typescript
|
|
544
|
+
try {
|
|
545
|
+
await alice.send('bob@example.com', 'Hello!');
|
|
546
|
+
} catch (error) {
|
|
547
|
+
if (error.code === 'AUTH_FAILED') {
|
|
548
|
+
// Handle auth error
|
|
549
|
+
console.error(error.message);
|
|
550
|
+
console.error('Action:', error.action);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
557
|
+
# Security Model
|
|
558
|
+
|
|
559
|
+
## What STVOR Protects
|
|
560
|
+
|
|
561
|
+
✅ End-to-end encryption of message content
|
|
562
|
+
✅ Perfect Forward Secrecy (new keys for each message)
|
|
563
|
+
✅ Post-quantum resistance (hybrid encryption)
|
|
564
|
+
✅ Message authentication (tampering detection)
|
|
565
|
+
✅ Secure key storage (encrypted keystore)
|
|
566
|
+
|
|
567
|
+
## What STVOR Does NOT Protect
|
|
568
|
+
|
|
569
|
+
❌ **Metadata** — Relay knows who communicates with whom
|
|
570
|
+
❌ **Timing** — When messages are sent
|
|
571
|
+
❌ **Device security** — Rooted/jailbroken devices
|
|
572
|
+
❌ **Social engineering** — User sending message to wrong person
|
|
573
|
+
❌ **Screen capture** — Recipient can screenshot decrypted messages
|
|
574
|
+
|
|
575
|
+
## Threat Model
|
|
576
|
+
|
|
577
|
+
- **Attacker model**: Network eavesdropper, relay operator (untrusted)
|
|
578
|
+
- **Attacker goals**: Read message content, forge messages
|
|
579
|
+
- **Attacker capabilities**: Observe metadata, control relay
|
|
580
|
+
- **Attacker cannot**: Read content, forge authenticated messages, decrypt past messages
|
|
581
|
+
|
|
582
|
+
<Tip>
|
|
583
|
+
For metadata hiding, use a separate anonymity layer (Tor, mixnet).
|
|
584
|
+
</Tip>
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
# Limits & Guarantees
|
|
589
|
+
|
|
590
|
+
## Limits
|
|
591
|
+
|
|
592
|
+
| Limit | Value |
|
|
593
|
+
|-------|-------|
|
|
594
|
+
| Message size | 1 MB |
|
|
595
|
+
| Offline storage | 24 hours |
|
|
596
|
+
| Sealed payload size | 10 MB |
|
|
597
|
+
| Connection timeout | 10 seconds |
|
|
598
|
+
|
|
599
|
+
## Guarantees
|
|
600
|
+
|
|
601
|
+
- Messages delivered at least once
|
|
602
|
+
- Decryption fails on tampering (hard fail)
|
|
603
|
+
- No plaintext leaves the device
|
|
604
|
+
- Keys never sent to relay
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
# FAQ
|
|
609
|
+
|
|
610
|
+
**Q: Do I need to manage keys?**
|
|
611
|
+
|
|
612
|
+
No. All key management is automatic and secure-by-default.
|
|
613
|
+
|
|
614
|
+
**Q: Can users have multiple devices?**
|
|
615
|
+
|
|
616
|
+
Yes, but each device is a separate identity. Sync is out of scope.
|
|
617
|
+
|
|
618
|
+
**Q: Can users recover messages on a new device?**
|
|
619
|
+
|
|
620
|
+
No. This would require storing keys on the server, which weakens security.
|
|
621
|
+
|
|
622
|
+
**Q: Is this really end-to-end encrypted?**
|
|
623
|
+
|
|
624
|
+
Yes. Plaintext never leaves the device. Relay only sees ciphertext.
|
|
625
|
+
|
|
626
|
+
**Q: What happens if a user loses their device?**
|
|
627
|
+
|
|
628
|
+
Messages on that device are lost. Messages to that user still work (they'll appear when they register a new device).
|
|
629
|
+
|
|
630
|
+
**Q: Does STVOR work offline?**
|
|
631
|
+
|
|
632
|
+
You can seal data offline. Sending/receiving requires connection to relay.
|
|
633
|
+
|
|
634
|
+
**Q: How is post-quantum security achieved?**
|
|
635
|
+
|
|
636
|
+
Hybrid encryption (X25519 + ML-KEM-768). No action required from developers.
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
## Next Steps
|
|
641
|
+
|
|
642
|
+
1. [Get AppToken](https://dashboard.stvor.io)
|
|
643
|
+
2. [Run Quickstart](#quickstart)
|
|
644
|
+
3. [Explore API Reference](#api-reference)
|
|
645
|
+
4. [Review Security Model](#security-model)
|
|
646
|
+
|
|
647
|
+
---
|
|
648
|
+
|
|
649
|
+
*STVOR SDK v2.0 — Secure by default.*
|
package/example.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STVOR DX Facade - Quick Start Example
|
|
3
|
+
*
|
|
4
|
+
* Copy-paste ready example for getting started.
|
|
5
|
+
*
|
|
6
|
+
* IMPORTANT: This is a DX facade. The actual security guarantees
|
|
7
|
+
* depend on the STVOR core implementation.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Stvor, StvorError, DecryptedMessage } from './index';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Basic messaging example with proper error handling
|
|
14
|
+
*/
|
|
15
|
+
async function main() {
|
|
16
|
+
try {
|
|
17
|
+
// 1. Initialize SDK with AppToken from environment
|
|
18
|
+
const app = await Stvor.init({
|
|
19
|
+
appToken: process.env.STVOR_APP_TOKEN || 'stvor_demo_abc123...'
|
|
20
|
+
});
|
|
21
|
+
console.log('SDK initialized');
|
|
22
|
+
|
|
23
|
+
// 2. Connect as a user
|
|
24
|
+
const alice = await app.connect('alice@example.com');
|
|
25
|
+
console.log(`Connected as ${alice.getUserId()}`);
|
|
26
|
+
|
|
27
|
+
// 3. Subscribe to incoming messages (recommended for production)
|
|
28
|
+
const unsubscribe = alice.onMessage((msg: DecryptedMessage) => {
|
|
29
|
+
console.log(`[Push] ${msg.senderId}: ${msg.content}`);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// 4. Send encrypted message
|
|
33
|
+
await alice.send('bob@example.com', 'Hello Bob!');
|
|
34
|
+
|
|
35
|
+
// 5. Cleanup
|
|
36
|
+
await app.disconnect();
|
|
37
|
+
unsubscribe();
|
|
38
|
+
|
|
39
|
+
} catch (error: unknown) {
|
|
40
|
+
if (error instanceof StvorError) {
|
|
41
|
+
console.error(`[${error.code}] ${error.message}`);
|
|
42
|
+
console.error(`Action: ${error.action}`);
|
|
43
|
+
console.error(`Retryable: ${error.retryable}`);
|
|
44
|
+
} else {
|
|
45
|
+
console.error('Unknown error:', error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Note on security guarantees:
|
|
52
|
+
*
|
|
53
|
+
* The facade provides a convenient API, but actual security
|
|
54
|
+
* (E2EE, PFS, post-quantum resistance) depends on the STVOR core.
|
|
55
|
+
*
|
|
56
|
+
* For production use, verify:
|
|
57
|
+
* 1. Core implements Double Ratchet for PFS
|
|
58
|
+
* 2. Core implements ML-KEM for post-quantum resistance
|
|
59
|
+
* 3. Keys are stored securely (not in plain memory)
|
|
60
|
+
* 4. Relay is trusted or verified
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
// Run example
|
|
64
|
+
main();
|