@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 +502 -0
- package/README.md +136 -282
- package/dist/index.js +692 -731
- package/dist/index.js.map +4 -4
- package/migrations/0006_service_offer_refactor.sql +40 -0
- package/migrations/0007_simplify_schema.sql +54 -0
- package/migrations/0008_peer_id_to_username.sql +67 -0
- package/migrations/fresh_schema.sql +81 -0
- package/package.json +2 -1
- package/src/app.ts +38 -591
- package/src/config.ts +0 -13
- package/src/crypto.ts +98 -133
- package/src/rpc.ts +725 -0
- package/src/storage/d1.ts +169 -182
- package/src/storage/sqlite.ts +142 -168
- package/src/storage/types.ts +51 -95
- package/src/worker.ts +0 -6
- package/wrangler.toml +3 -3
- package/src/middleware/auth.ts +0 -51
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
|