@xtr-dev/rondevu-server 0.4.0 → 0.5.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/ADVANCED.md ADDED
@@ -0,0 +1,502 @@
1
+ # Rondevu Server - Advanced Usage
2
+
3
+ Comprehensive API reference, configuration guide, database schema, and security details.
4
+
5
+ ## Table of Contents
6
+
7
+ - [RPC Methods](#rpc-methods)
8
+ - [Configuration](#configuration)
9
+ - [Database Schema](#database-schema)
10
+ - [Security](#security)
11
+ - [Migration Guide](#migration-guide)
12
+
13
+ ---
14
+
15
+ ## RPC Methods
16
+
17
+ ### `getUser`
18
+ Check username availability
19
+
20
+ **Parameters:**
21
+ - `username` - Username to check
22
+
23
+ **Message format:** `getUser:{username}:{timestamp}` (no authentication required)
24
+
25
+ **Example:**
26
+ ```json
27
+ {
28
+ "method": "getUser",
29
+ "message": "getUser:alice:1733404800000",
30
+ "signature": "base64-signature",
31
+ "params": { "username": "alice" }
32
+ }
33
+ ```
34
+
35
+ **Response:**
36
+ ```json
37
+ {
38
+ "success": true,
39
+ "result": {
40
+ "username": "alice",
41
+ "available": false,
42
+ "claimedAt": 1733404800000,
43
+ "expiresAt": 1765027200000,
44
+ "publicKey": "base64-encoded-public-key"
45
+ }
46
+ }
47
+ ```
48
+
49
+ ### `claimUsername`
50
+ Claim a username with cryptographic proof
51
+
52
+ **Parameters:**
53
+ - `username` - Username to claim
54
+ - `publicKey` - Base64-encoded Ed25519 public key
55
+
56
+ **Message format:** `claim:{username}:{timestamp}`
57
+
58
+ **Example:**
59
+ ```json
60
+ {
61
+ "method": "claimUsername",
62
+ "message": "claim:alice:1733404800000",
63
+ "signature": "base64-signature",
64
+ "params": {
65
+ "username": "alice",
66
+ "publicKey": "base64-encoded-public-key"
67
+ }
68
+ }
69
+ ```
70
+
71
+ **Response:**
72
+ ```json
73
+ {
74
+ "success": true,
75
+ "result": {
76
+ "success": true,
77
+ "username": "alice"
78
+ }
79
+ }
80
+ ```
81
+
82
+ ### `getService`
83
+ Get service by FQN (direct lookup, random discovery, or paginated)
84
+
85
+ **Parameters:**
86
+ - `serviceFqn` - Service FQN (e.g., `chat:1.0.0` or `chat:1.0.0@alice`)
87
+ - `limit` - (optional) Number of results for paginated mode
88
+ - `offset` - (optional) Offset for paginated mode
89
+
90
+ **Message format:** `getService:{username}:{serviceFqn}:{timestamp}`
91
+
92
+ **Modes:**
93
+ 1. **Direct lookup** (with @username): Returns specific user's service
94
+ 2. **Random** (without @username, no limit): Returns random service
95
+ 3. **Paginated** (without @username, with limit): Returns multiple services
96
+
97
+ **Example:**
98
+ ```json
99
+ {
100
+ "method": "getService",
101
+ "message": "getService:bob:chat:1.0.0:1733404800000",
102
+ "signature": "base64-signature",
103
+ "params": {
104
+ "serviceFqn": "chat:1.0.0@alice"
105
+ }
106
+ }
107
+ ```
108
+
109
+ **Response:**
110
+ ```json
111
+ {
112
+ "success": true,
113
+ "result": {
114
+ "serviceId": "uuid",
115
+ "username": "alice",
116
+ "serviceFqn": "chat:1.0.0@alice",
117
+ "offerId": "offer-hash",
118
+ "sdp": "v=0...",
119
+ "createdAt": 1733404800000,
120
+ "expiresAt": 1733405100000
121
+ }
122
+ }
123
+ ```
124
+
125
+ ### `publishService`
126
+ Publish a service with offers
127
+
128
+ **Parameters:**
129
+ - `serviceFqn` - Service FQN with username (e.g., `chat:1.0.0@alice`)
130
+ - `offers` - Array of offers, each with `sdp` field
131
+ - `ttl` - (optional) Time to live in milliseconds
132
+
133
+ **Message format:** `publishService:{username}:{serviceFqn}:{timestamp}`
134
+
135
+ **Example:**
136
+ ```json
137
+ {
138
+ "method": "publishService",
139
+ "message": "publishService:alice:chat:1.0.0@alice:1733404800000",
140
+ "signature": "base64-signature",
141
+ "params": {
142
+ "serviceFqn": "chat:1.0.0@alice",
143
+ "offers": [
144
+ { "sdp": "v=0..." },
145
+ { "sdp": "v=0..." }
146
+ ],
147
+ "ttl": 300000
148
+ }
149
+ }
150
+ ```
151
+
152
+ **Response:**
153
+ ```json
154
+ {
155
+ "success": true,
156
+ "result": {
157
+ "serviceId": "uuid",
158
+ "username": "alice",
159
+ "serviceFqn": "chat:1.0.0@alice",
160
+ "offers": [
161
+ {
162
+ "offerId": "offer-hash-1",
163
+ "sdp": "v=0...",
164
+ "createdAt": 1733404800000,
165
+ "expiresAt": 1733405100000
166
+ }
167
+ ],
168
+ "createdAt": 1733404800000,
169
+ "expiresAt": 1733405100000
170
+ }
171
+ }
172
+ ```
173
+
174
+ ### `deleteService`
175
+ Delete a service
176
+
177
+ **Parameters:**
178
+ - `serviceFqn` - Service FQN with username
179
+
180
+ **Message format:** `deleteService:{username}:{serviceFqn}:{timestamp}`
181
+
182
+ **Example:**
183
+ ```json
184
+ {
185
+ "method": "deleteService",
186
+ "message": "deleteService:alice:chat:1.0.0@alice:1733404800000",
187
+ "signature": "base64-signature",
188
+ "params": {
189
+ "serviceFqn": "chat:1.0.0@alice"
190
+ }
191
+ }
192
+ ```
193
+
194
+ **Response:**
195
+ ```json
196
+ {
197
+ "success": true,
198
+ "result": { "success": true }
199
+ }
200
+ ```
201
+
202
+ ### `answerOffer`
203
+ Answer a specific offer
204
+
205
+ **Parameters:**
206
+ - `serviceFqn` - Service FQN
207
+ - `offerId` - Offer ID
208
+ - `sdp` - Answer SDP
209
+
210
+ **Message format:** `answerOffer:{username}:{offerId}:{timestamp}`
211
+
212
+ **Example:**
213
+ ```json
214
+ {
215
+ "method": "answerOffer",
216
+ "message": "answerOffer:bob:offer-hash:1733404800000",
217
+ "signature": "base64-signature",
218
+ "params": {
219
+ "serviceFqn": "chat:1.0.0@alice",
220
+ "offerId": "offer-hash",
221
+ "sdp": "v=0..."
222
+ }
223
+ }
224
+ ```
225
+
226
+ **Response:**
227
+ ```json
228
+ {
229
+ "success": true,
230
+ "result": {
231
+ "success": true,
232
+ "offerId": "offer-hash"
233
+ }
234
+ }
235
+ ```
236
+
237
+ ### `getOfferAnswer`
238
+ Get answer for an offer (offerer polls this)
239
+
240
+ **Parameters:**
241
+ - `serviceFqn` - Service FQN
242
+ - `offerId` - Offer ID
243
+
244
+ **Message format:** `getOfferAnswer:{username}:{offerId}:{timestamp}`
245
+
246
+ **Example:**
247
+ ```json
248
+ {
249
+ "method": "getOfferAnswer",
250
+ "message": "getOfferAnswer:alice:offer-hash:1733404800000",
251
+ "signature": "base64-signature",
252
+ "params": {
253
+ "serviceFqn": "chat:1.0.0@alice",
254
+ "offerId": "offer-hash"
255
+ }
256
+ }
257
+ ```
258
+
259
+ **Response:**
260
+ ```json
261
+ {
262
+ "success": true,
263
+ "result": {
264
+ "sdp": "v=0...",
265
+ "offerId": "offer-hash",
266
+ "answererId": "bob",
267
+ "answeredAt": 1733404800000
268
+ }
269
+ }
270
+ ```
271
+
272
+ ### `poll`
273
+ Combined polling for answers and ICE candidates
274
+
275
+ **Parameters:**
276
+ - `since` - (optional) Timestamp to get only new data
277
+
278
+ **Message format:** `poll:{username}:{timestamp}`
279
+
280
+ **Example:**
281
+ ```json
282
+ {
283
+ "method": "poll",
284
+ "message": "poll:alice:1733404800000",
285
+ "signature": "base64-signature",
286
+ "params": {
287
+ "since": 1733404800000
288
+ }
289
+ }
290
+ ```
291
+
292
+ **Response:**
293
+ ```json
294
+ {
295
+ "success": true,
296
+ "result": {
297
+ "answers": [
298
+ {
299
+ "offerId": "offer-hash",
300
+ "serviceId": "service-uuid",
301
+ "answererId": "bob",
302
+ "sdp": "v=0...",
303
+ "answeredAt": 1733404800000
304
+ }
305
+ ],
306
+ "iceCandidates": {
307
+ "offer-hash": [
308
+ {
309
+ "candidate": { "candidate": "...", "sdpMid": "0", "sdpMLineIndex": 0 },
310
+ "role": "answerer",
311
+ "username": "bob",
312
+ "createdAt": 1733404800000
313
+ }
314
+ ]
315
+ }
316
+ }
317
+ }
318
+ ```
319
+
320
+ ### `addIceCandidates`
321
+ Add ICE candidates to an offer
322
+
323
+ **Parameters:**
324
+ - `serviceFqn` - Service FQN
325
+ - `offerId` - Offer ID
326
+ - `candidates` - Array of ICE candidates
327
+
328
+ **Message format:** `addIceCandidates:{username}:{offerId}:{timestamp}`
329
+
330
+ **Example:**
331
+ ```json
332
+ {
333
+ "method": "addIceCandidates",
334
+ "message": "addIceCandidates:alice:offer-hash:1733404800000",
335
+ "signature": "base64-signature",
336
+ "params": {
337
+ "serviceFqn": "chat:1.0.0@alice",
338
+ "offerId": "offer-hash",
339
+ "candidates": [
340
+ {
341
+ "candidate": "candidate:...",
342
+ "sdpMid": "0",
343
+ "sdpMLineIndex": 0
344
+ }
345
+ ]
346
+ }
347
+ }
348
+ ```
349
+
350
+ **Response:**
351
+ ```json
352
+ {
353
+ "success": true,
354
+ "result": {
355
+ "count": 1,
356
+ "offerId": "offer-hash"
357
+ }
358
+ }
359
+ ```
360
+
361
+ ### `getIceCandidates`
362
+ Get ICE candidates for an offer
363
+
364
+ **Parameters:**
365
+ - `serviceFqn` - Service FQN
366
+ - `offerId` - Offer ID
367
+ - `since` - (optional) Timestamp to get only new candidates
368
+
369
+ **Message format:** `getIceCandidates:{username}:{offerId}:{timestamp}`
370
+
371
+ **Example:**
372
+ ```json
373
+ {
374
+ "method": "getIceCandidates",
375
+ "message": "getIceCandidates:alice:offer-hash:1733404800000",
376
+ "signature": "base64-signature",
377
+ "params": {
378
+ "serviceFqn": "chat:1.0.0@alice",
379
+ "offerId": "offer-hash",
380
+ "since": 1733404800000
381
+ }
382
+ }
383
+ ```
384
+
385
+ **Response:**
386
+ ```json
387
+ {
388
+ "success": true,
389
+ "result": {
390
+ "candidates": [
391
+ {
392
+ "candidate": {
393
+ "candidate": "candidate:...",
394
+ "sdpMid": "0",
395
+ "sdpMLineIndex": 0
396
+ },
397
+ "createdAt": 1733404800000
398
+ }
399
+ ],
400
+ "offerId": "offer-hash"
401
+ }
402
+ }
403
+ ```
404
+
405
+
406
+ ## Configuration
407
+
408
+ Environment variables:
409
+
410
+ | Variable | Default | Description |
411
+ |----------|---------|-------------|
412
+ | `PORT` | `3000` | Server port (Node.js/Docker) |
413
+ | `CORS_ORIGINS` | `*` | Comma-separated allowed origins |
414
+ | `STORAGE_PATH` | `./rondevu.db` | SQLite database path (use `:memory:` for in-memory) |
415
+ | `VERSION` | `0.5.0` | Server version (semver) |
416
+ | `OFFER_DEFAULT_TTL` | `60000` | Default offer TTL in ms (1 minute) |
417
+ | `OFFER_MIN_TTL` | `60000` | Minimum offer TTL in ms (1 minute) |
418
+ | `OFFER_MAX_TTL` | `86400000` | Maximum offer TTL in ms (24 hours) |
419
+ | `CLEANUP_INTERVAL` | `60000` | Cleanup interval in ms (1 minute) |
420
+ | `MAX_OFFERS_PER_REQUEST` | `100` | Maximum offers per create request |
421
+
422
+ ## Database Schema
423
+
424
+ ### usernames
425
+ - `username` (PK): Claimed username
426
+ - `public_key`: Ed25519 public key (base64)
427
+ - `claimed_at`: Claim timestamp
428
+ - `expires_at`: Expiry timestamp (365 days)
429
+ - `last_used`: Last activity timestamp
430
+ - `metadata`: Optional JSON metadata
431
+
432
+ ### services
433
+ - `id` (PK): Service ID (UUID)
434
+ - `username` (FK): Owner username
435
+ - `service_fqn`: Fully qualified name (chat:1.0.0@alice)
436
+ - `service_name`: Service name component (chat)
437
+ - `version`: Version component (1.0.0)
438
+ - `created_at`, `expires_at`: Timestamps
439
+ - UNIQUE constraint on (service_name, version, username)
440
+
441
+ ### offers
442
+ - `id` (PK): Offer ID (hash of SDP)
443
+ - `username` (FK): Owner username
444
+ - `service_id` (FK): Link to service
445
+ - `service_fqn`: Denormalized service FQN
446
+ - `sdp`: WebRTC offer SDP
447
+ - `answerer_username`: Username of answerer (null until answered)
448
+ - `answer_sdp`: WebRTC answer SDP (null until answered)
449
+ - `answered_at`: Timestamp when answered
450
+ - `created_at`, `expires_at`, `last_seen`: Timestamps
451
+
452
+ ### ice_candidates
453
+ - `id` (PK): Auto-increment ID
454
+ - `offer_id` (FK): Link to offer
455
+ - `username`: Username who sent the candidate
456
+ - `role`: 'offerer' or 'answerer'
457
+ - `candidate`: JSON-encoded candidate
458
+ - `created_at`: Timestamp
459
+
460
+ ## Security
461
+
462
+ ### Ed25519 Signature Authentication
463
+ All authenticated requests require:
464
+ - **message**: Signed message with format-specific structure
465
+ - **signature**: Base64-encoded Ed25519 signature of the message
466
+ - Username is extracted from the message
467
+
468
+ ### Username Claiming
469
+ - **Algorithm**: Ed25519 signatures
470
+ - **Message Format**: `claim:{username}:{timestamp}`
471
+ - **Replay Protection**: Timestamp must be within 5 minutes
472
+ - **Key Management**: Private keys never leave the client
473
+ - **Validity**: 365 days, auto-renewed on use
474
+
475
+ ### Anonymous Users
476
+ - **Format**: `anon-{timestamp}-{random}` (e.g., `anon-lx2w34-a3f501`)
477
+ - **Generation**: Can be generated by client for testing
478
+ - **Behavior**: Same as regular usernames, must be explicitly claimed like any username
479
+
480
+ ### Service Publishing
481
+ - **Ownership Verification**: Every publish requires username signature
482
+ - **Message Format**: `publishService:{username}:{serviceFqn}:{timestamp}`
483
+ - **Auto-Renewal**: Publishing a service extends username expiry
484
+
485
+ ### ICE Candidate Filtering
486
+ - Server filters candidates by role to prevent peers from receiving their own candidates
487
+ - Offerers receive only answerer candidates
488
+ - Answerers receive only offerer candidates
489
+
490
+ ## Migration from v0.4.x
491
+
492
+ See [MIGRATION.md](../MIGRATION.md) for detailed migration guide.
493
+
494
+ **Key Changes:**
495
+ - Moved from REST API to RPC interface with single `/rpc` endpoint
496
+ - All methods now use POST with JSON body
497
+ - Batch operations supported
498
+ - Authentication is per-method instead of per-endpoint middleware
499
+
500
+ ## License
501
+
502
+ MIT