@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 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();