@xtr-dev/rondevu-server 0.3.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 +139 -251
- package/dist/index.js +715 -770
- 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 -677
- 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/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@xtr-dev/rondevu-server)
|
|
4
4
|
|
|
5
|
-
🌐 **
|
|
5
|
+
🌐 **Simple WebRTC signaling with RPC interface**
|
|
6
6
|
|
|
7
|
-
Scalable WebRTC signaling server with cryptographic username claiming, service publishing, and
|
|
7
|
+
Scalable WebRTC signaling server with cryptographic username claiming, service publishing with semantic versioning, and efficient offer/answer exchange via JSON-RPC interface.
|
|
8
8
|
|
|
9
9
|
**Related repositories:**
|
|
10
10
|
- [@xtr-dev/rondevu-client](https://github.com/xtr-dev/rondevu-client) - TypeScript client library ([npm](https://www.npmjs.com/package/@xtr-dev/rondevu-client))
|
|
@@ -15,12 +15,14 @@ Scalable WebRTC signaling server with cryptographic username claiming, service p
|
|
|
15
15
|
|
|
16
16
|
## Features
|
|
17
17
|
|
|
18
|
+
- **RPC Interface**: Single endpoint for all operations with batching support
|
|
18
19
|
- **Username Claiming**: Cryptographic username ownership with Ed25519 signatures (365-day validity, auto-renewed on use)
|
|
19
|
-
- **Service Publishing**:
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
20
|
+
- **Service Publishing**: Service:version@username naming (e.g., `chat:1.0.0@alice`)
|
|
21
|
+
- **Service Discovery**: Random and paginated discovery for finding services without knowing usernames
|
|
22
|
+
- **Semantic Versioning**: Compatible version matching (chat:1.0.0 matches any 1.x.x)
|
|
23
|
+
- **Signature-Based Authentication**: All authenticated requests use Ed25519 signatures
|
|
23
24
|
- **Complete WebRTC Signaling**: Offer/answer exchange and ICE candidate relay
|
|
25
|
+
- **Batch Operations**: Execute multiple operations in a single HTTP request
|
|
24
26
|
- **Dual Storage**: SQLite (Node.js/Docker) and Cloudflare D1 (Workers) backends
|
|
25
27
|
|
|
26
28
|
## Architecture
|
|
@@ -30,11 +32,13 @@ Username Claiming → Service Publishing → Service Discovery → WebRTC Connec
|
|
|
30
32
|
|
|
31
33
|
alice claims "alice" with Ed25519 signature
|
|
32
34
|
↓
|
|
33
|
-
alice publishes
|
|
35
|
+
alice publishes chat:1.0.0@alice with offers
|
|
34
36
|
↓
|
|
35
|
-
bob
|
|
37
|
+
bob queries chat:1.0.0@alice (direct) or chat:1.0.0 (discovery) → gets offer SDP
|
|
36
38
|
↓
|
|
37
|
-
WebRTC connection established
|
|
39
|
+
bob posts answer SDP → WebRTC connection established
|
|
40
|
+
↓
|
|
41
|
+
ICE candidates exchanged via server relay
|
|
38
42
|
```
|
|
39
43
|
|
|
40
44
|
## Quick Start
|
|
@@ -46,7 +50,7 @@ npm install && npm start
|
|
|
46
50
|
|
|
47
51
|
**Docker:**
|
|
48
52
|
```bash
|
|
49
|
-
docker build -t rondevu . && docker run -p 3000:3000 -e STORAGE_PATH=:memory:
|
|
53
|
+
docker build -t rondevu . && docker run -p 3000:3000 -e STORAGE_PATH=:memory: rondevu
|
|
50
54
|
```
|
|
51
55
|
|
|
52
56
|
**Cloudflare Workers:**
|
|
@@ -54,317 +58,201 @@ docker build -t rondevu . && docker run -p 3000:3000 -e STORAGE_PATH=:memory: -e
|
|
|
54
58
|
npx wrangler deploy
|
|
55
59
|
```
|
|
56
60
|
|
|
57
|
-
##
|
|
58
|
-
|
|
59
|
-
### Public Endpoints
|
|
60
|
-
|
|
61
|
-
#### `GET /`
|
|
62
|
-
Returns server version and info
|
|
63
|
-
|
|
64
|
-
#### `GET /health`
|
|
65
|
-
Health check endpoint with version
|
|
61
|
+
## RPC Interface
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
Register a new peer and receive credentials (peerId + secret)
|
|
63
|
+
All API calls are made to `POST /rpc` with JSON-RPC format.
|
|
69
64
|
|
|
70
|
-
|
|
65
|
+
### Request Format
|
|
71
66
|
|
|
72
|
-
**
|
|
67
|
+
**Single method call:**
|
|
73
68
|
```json
|
|
74
69
|
{
|
|
75
|
-
"
|
|
76
|
-
"
|
|
70
|
+
"method": "getUser",
|
|
71
|
+
"message": "getUser:alice:1733404800000",
|
|
72
|
+
"signature": "base64-encoded-signature",
|
|
73
|
+
"params": {
|
|
74
|
+
"username": "alice"
|
|
75
|
+
}
|
|
77
76
|
}
|
|
78
77
|
```
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
#### `GET /users/:username`
|
|
83
|
-
Check username availability and claim status
|
|
84
|
-
|
|
85
|
-
**Response:**
|
|
79
|
+
**Batch calls:**
|
|
86
80
|
```json
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
81
|
+
[
|
|
82
|
+
{
|
|
83
|
+
"method": "getUser",
|
|
84
|
+
"message": "getUser:alice:1733404800000",
|
|
85
|
+
"signature": "base64-encoded-signature",
|
|
86
|
+
"params": { "username": "alice" }
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"method": "claimUsername",
|
|
90
|
+
"message": "claim:bob:1733404800000",
|
|
91
|
+
"signature": "base64-encoded-signature",
|
|
92
|
+
"params": {
|
|
93
|
+
"username": "bob",
|
|
94
|
+
"publicKey": "base64-encoded-public-key"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
]
|
|
94
98
|
```
|
|
95
99
|
|
|
96
|
-
|
|
97
|
-
Claim a username with cryptographic proof
|
|
100
|
+
### Response Format
|
|
98
101
|
|
|
99
|
-
**
|
|
102
|
+
**Single response:**
|
|
100
103
|
```json
|
|
101
104
|
{
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
"message": "claim:alice:1733404800000"
|
|
105
|
+
"success": true,
|
|
106
|
+
"result": { /* method-specific data */ }
|
|
105
107
|
}
|
|
106
108
|
```
|
|
107
109
|
|
|
108
|
-
**
|
|
110
|
+
**Error response:**
|
|
109
111
|
```json
|
|
110
112
|
{
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"expiresAt": 1765027200000
|
|
113
|
+
"success": false,
|
|
114
|
+
"error": "Error message"
|
|
114
115
|
}
|
|
115
116
|
```
|
|
116
117
|
|
|
117
|
-
**
|
|
118
|
-
- Username format: `^[a-z0-9][a-z0-9-]*[a-z0-9]$` (3-32 characters)
|
|
119
|
-
- Signature must be valid Ed25519 signature
|
|
120
|
-
- Timestamp must be within 5 minutes (replay protection)
|
|
121
|
-
- Expires after 365 days, auto-renewed on use
|
|
118
|
+
**Batch responses:** Array of responses matching request array order.
|
|
122
119
|
|
|
123
|
-
|
|
124
|
-
Get service by username and FQN with semver-compatible matching
|
|
120
|
+
## Core Methods
|
|
125
121
|
|
|
126
|
-
|
|
127
|
-
- Requesting `chat@1.0.0` matches any `1.x.x` version
|
|
128
|
-
- Major version must match exactly (`chat@1.0.0` will NOT match `chat@2.0.0`)
|
|
129
|
-
- For major version 0, minor must also match (`0.1.0` will NOT match `0.2.0`)
|
|
130
|
-
- Returns the most recently published compatible version
|
|
122
|
+
### Username Management
|
|
131
123
|
|
|
132
|
-
|
|
133
|
-
|
|
124
|
+
```typescript
|
|
125
|
+
// Check username availability
|
|
126
|
+
POST /rpc
|
|
134
127
|
{
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
"username": "alice",
|
|
138
|
-
"serviceFqn": "chat.app@1.0.0",
|
|
139
|
-
"offerId": "offer-hash",
|
|
140
|
-
"sdp": "v=0...",
|
|
141
|
-
"isPublic": true,
|
|
142
|
-
"metadata": {},
|
|
143
|
-
"createdAt": 1733404800000,
|
|
144
|
-
"expiresAt": 1733405100000
|
|
128
|
+
"method": "getUser",
|
|
129
|
+
"params": { "username": "alice" }
|
|
145
130
|
}
|
|
146
|
-
```
|
|
147
131
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
### Service Management (RESTful)
|
|
151
|
-
|
|
152
|
-
#### `POST /users/:username/services`
|
|
153
|
-
Publish a service with multiple offers (requires authentication and username signature)
|
|
154
|
-
|
|
155
|
-
**Headers:**
|
|
156
|
-
- `Authorization: Bearer {peerId}:{secret}`
|
|
157
|
-
|
|
158
|
-
**Request:**
|
|
159
|
-
```json
|
|
132
|
+
// Claim username (requires signature)
|
|
133
|
+
POST /rpc
|
|
160
134
|
{
|
|
161
|
-
"
|
|
162
|
-
"
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
"metadata": { "description": "Chat service" },
|
|
169
|
-
"signature": "base64-encoded-signature",
|
|
170
|
-
"message": "publish:alice:com.example.chat@1.0.0:1733404800000"
|
|
135
|
+
"method": "claimUsername",
|
|
136
|
+
"message": "claim:alice:1733404800000",
|
|
137
|
+
"signature": "base64-signature",
|
|
138
|
+
"params": {
|
|
139
|
+
"username": "alice",
|
|
140
|
+
"publicKey": "base64-public-key"
|
|
141
|
+
}
|
|
171
142
|
}
|
|
172
143
|
```
|
|
173
144
|
|
|
174
|
-
|
|
175
|
-
|
|
145
|
+
### Service Publishing
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// Publish service (requires signature)
|
|
149
|
+
POST /rpc
|
|
176
150
|
{
|
|
177
|
-
"
|
|
178
|
-
"
|
|
179
|
-
"
|
|
180
|
-
"
|
|
181
|
-
|
|
182
|
-
{
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
"createdAt": 1733404800000,
|
|
186
|
-
"expiresAt": 1733405100000
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
"offerId": "offer-hash-2",
|
|
190
|
-
"sdp": "v=0...",
|
|
191
|
-
"createdAt": 1733404800000,
|
|
192
|
-
"expiresAt": 1733405100000
|
|
193
|
-
}
|
|
194
|
-
],
|
|
195
|
-
"isPublic": false,
|
|
196
|
-
"metadata": { "description": "Chat service" },
|
|
197
|
-
"createdAt": 1733404800000,
|
|
198
|
-
"expiresAt": 1733405100000
|
|
151
|
+
"method": "publishService",
|
|
152
|
+
"message": "publishService:alice:chat:1.0.0@alice:1733404800000",
|
|
153
|
+
"signature": "base64-signature",
|
|
154
|
+
"params": {
|
|
155
|
+
"serviceFqn": "chat:1.0.0@alice",
|
|
156
|
+
"offers": [{ "sdp": "webrtc-offer-sdp" }],
|
|
157
|
+
"ttl": 300000
|
|
158
|
+
}
|
|
199
159
|
}
|
|
200
160
|
```
|
|
201
161
|
|
|
202
|
-
|
|
203
|
-
- Service name: Reverse domain notation (e.g., `com.example.chat`)
|
|
204
|
-
- Version: Semantic versioning (e.g., `1.0.0`, `2.1.3-beta`)
|
|
205
|
-
- Complete FQN: `service-name@version` (e.g., `com.example.chat@1.0.0`)
|
|
206
|
-
|
|
207
|
-
**Validation:**
|
|
208
|
-
- Service name pattern: `^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$`
|
|
209
|
-
- Length: 3-128 characters
|
|
210
|
-
- Version pattern: `^[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9.-]+)?$`
|
|
211
|
-
|
|
212
|
-
#### `GET /services/:uuid`
|
|
213
|
-
Get service details by UUID
|
|
162
|
+
### Service Discovery
|
|
214
163
|
|
|
215
|
-
|
|
216
|
-
|
|
164
|
+
```typescript
|
|
165
|
+
// Get specific service
|
|
166
|
+
POST /rpc
|
|
217
167
|
{
|
|
218
|
-
"
|
|
219
|
-
"
|
|
220
|
-
"serviceFqn": "com.example.chat@1.0.0",
|
|
221
|
-
"offerId": "...",
|
|
222
|
-
"sdp": "v=0...",
|
|
223
|
-
"isPublic": false,
|
|
224
|
-
"metadata": { ... },
|
|
225
|
-
"createdAt": 1733404800000,
|
|
226
|
-
"expiresAt": 1733405100000
|
|
168
|
+
"method": "getService",
|
|
169
|
+
"params": { "serviceFqn": "chat:1.0.0@alice" }
|
|
227
170
|
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
#### `DELETE /users/:username/services/:fqn`
|
|
231
|
-
Unpublish a service (requires authentication and ownership)
|
|
232
171
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
**Request:**
|
|
237
|
-
```json
|
|
172
|
+
// Random discovery
|
|
173
|
+
POST /rpc
|
|
238
174
|
{
|
|
239
|
-
"
|
|
175
|
+
"method": "getService",
|
|
176
|
+
"params": { "serviceFqn": "chat:1.0.0" }
|
|
240
177
|
}
|
|
241
|
-
```
|
|
242
178
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
#### `POST /offers`
|
|
246
|
-
Create one or more offers (requires authentication)
|
|
247
|
-
|
|
248
|
-
**Headers:**
|
|
249
|
-
- `Authorization: Bearer {peerId}:{secret}`
|
|
250
|
-
|
|
251
|
-
**Request:**
|
|
252
|
-
```json
|
|
179
|
+
// Paginated discovery
|
|
180
|
+
POST /rpc
|
|
253
181
|
{
|
|
254
|
-
"
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
182
|
+
"method": "getService",
|
|
183
|
+
"params": {
|
|
184
|
+
"serviceFqn": "chat:1.0.0",
|
|
185
|
+
"limit": 10,
|
|
186
|
+
"offset": 0
|
|
187
|
+
}
|
|
260
188
|
}
|
|
261
189
|
```
|
|
262
190
|
|
|
263
|
-
|
|
264
|
-
List all offers owned by authenticated peer
|
|
265
|
-
|
|
266
|
-
#### `DELETE /offers/:offerId`
|
|
267
|
-
Delete a specific offer
|
|
191
|
+
### WebRTC Signaling
|
|
268
192
|
|
|
269
|
-
|
|
270
|
-
Answer
|
|
271
|
-
|
|
272
|
-
**Request:**
|
|
273
|
-
```json
|
|
193
|
+
```typescript
|
|
194
|
+
// Answer offer (requires signature)
|
|
195
|
+
POST /rpc
|
|
274
196
|
{
|
|
275
|
-
"
|
|
197
|
+
"method": "answerOffer",
|
|
198
|
+
"message": "answer:bob:offer-id:1733404800000",
|
|
199
|
+
"signature": "base64-signature",
|
|
200
|
+
"params": {
|
|
201
|
+
"serviceFqn": "chat:1.0.0@alice",
|
|
202
|
+
"offerId": "offer-id",
|
|
203
|
+
"sdp": "webrtc-answer-sdp"
|
|
204
|
+
}
|
|
276
205
|
}
|
|
277
|
-
```
|
|
278
206
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
207
|
+
// Add ICE candidates (requires signature)
|
|
208
|
+
POST /rpc
|
|
209
|
+
{
|
|
210
|
+
"method": "addIceCandidates",
|
|
211
|
+
"params": {
|
|
212
|
+
"serviceFqn": "chat:1.0.0@alice",
|
|
213
|
+
"offerId": "offer-id",
|
|
214
|
+
"candidates": [{ /* RTCIceCandidateInit */ }]
|
|
215
|
+
}
|
|
216
|
+
}
|
|
284
217
|
|
|
285
|
-
|
|
286
|
-
|
|
218
|
+
// Poll for answers and ICE candidates (requires signature)
|
|
219
|
+
POST /rpc
|
|
287
220
|
{
|
|
288
|
-
"
|
|
221
|
+
"method": "poll",
|
|
222
|
+
"params": { "since": 1733404800000 }
|
|
289
223
|
}
|
|
290
224
|
```
|
|
291
225
|
|
|
292
|
-
#### `GET /offers/:offerId/ice-candidates?since=1234567890`
|
|
293
|
-
Get ICE candidates from the other peer
|
|
294
|
-
|
|
295
226
|
## Configuration
|
|
296
227
|
|
|
297
|
-
|
|
228
|
+
Quick reference for common environment variables:
|
|
298
229
|
|
|
299
230
|
| Variable | Default | Description |
|
|
300
231
|
|----------|---------|-------------|
|
|
301
232
|
| `PORT` | `3000` | Server port (Node.js/Docker) |
|
|
302
233
|
| `CORS_ORIGINS` | `*` | Comma-separated allowed origins |
|
|
303
234
|
| `STORAGE_PATH` | `./rondevu.db` | SQLite database path (use `:memory:` for in-memory) |
|
|
304
|
-
| `VERSION` | `2.0.0` | Server version (semver) |
|
|
305
|
-
| `AUTH_SECRET` | Random 32-byte hex | Secret key for credential encryption (required for production) |
|
|
306
|
-
| `OFFER_DEFAULT_TTL` | `300000` | Default offer TTL in ms (5 minutes) |
|
|
307
|
-
| `OFFER_MIN_TTL` | `60000` | Minimum offer TTL in ms (1 minute) |
|
|
308
|
-
| `OFFER_MAX_TTL` | `3600000` | Maximum offer TTL in ms (1 hour) |
|
|
309
|
-
| `MAX_OFFERS_PER_REQUEST` | `10` | Maximum offers per create request |
|
|
310
|
-
|
|
311
|
-
## Database Schema
|
|
312
|
-
|
|
313
|
-
### usernames
|
|
314
|
-
- `username` (PK): Claimed username
|
|
315
|
-
- `public_key`: Ed25519 public key (base64)
|
|
316
|
-
- `claimed_at`: Claim timestamp
|
|
317
|
-
- `expires_at`: Expiry timestamp (365 days)
|
|
318
|
-
- `last_used`: Last activity timestamp
|
|
319
|
-
- `metadata`: Optional JSON metadata
|
|
320
|
-
|
|
321
|
-
### services
|
|
322
|
-
- `id` (PK): Service ID (UUID)
|
|
323
|
-
- `username` (FK): Owner username
|
|
324
|
-
- `service_fqn`: Fully qualified name (com.example.chat@1.0.0)
|
|
325
|
-
- `is_public`: Public/private flag
|
|
326
|
-
- `metadata`: JSON metadata
|
|
327
|
-
- `created_at`, `expires_at`: Timestamps
|
|
328
|
-
|
|
329
|
-
### offers
|
|
330
|
-
- `id` (PK): Offer ID (hash of SDP)
|
|
331
|
-
- `peer_id` (FK): Owner peer ID
|
|
332
|
-
- `service_id` (FK): Optional link to service (null for standalone offers)
|
|
333
|
-
- `sdp`: WebRTC offer SDP
|
|
334
|
-
- `answerer_peer_id`: Peer ID of answerer (null until answered)
|
|
335
|
-
- `answer_sdp`: WebRTC answer SDP (null until answered)
|
|
336
|
-
- `created_at`, `expires_at`, `last_seen`: Timestamps
|
|
337
|
-
|
|
338
|
-
### service_index (privacy layer)
|
|
339
|
-
- `uuid` (PK): Random UUID for discovery
|
|
340
|
-
- `service_id` (FK): Links to service
|
|
341
|
-
- `username`, `service_fqn`: Denormalized for performance
|
|
342
|
-
|
|
343
|
-
## Security
|
|
344
235
|
|
|
345
|
-
|
|
346
|
-
- **Algorithm**: Ed25519 signatures
|
|
347
|
-
- **Message Format**: `claim:{username}:{timestamp}`
|
|
348
|
-
- **Replay Protection**: Timestamp must be within 5 minutes
|
|
349
|
-
- **Key Management**: Private keys never leave the client
|
|
236
|
+
📚 See [ADVANCED.md](./ADVANCED.md#configuration) for complete configuration reference.
|
|
350
237
|
|
|
351
|
-
|
|
352
|
-
- **Ownership Verification**: Every publish requires username signature
|
|
353
|
-
- **Message Format**: `publish:{username}:{serviceFqn}:{timestamp}`
|
|
354
|
-
- **Auto-Renewal**: Publishing a service extends username expiry
|
|
238
|
+
## Documentation
|
|
355
239
|
|
|
356
|
-
|
|
357
|
-
-
|
|
358
|
-
-
|
|
359
|
-
-
|
|
240
|
+
📚 **[ADVANCED.md](./ADVANCED.md)** - Comprehensive guide including:
|
|
241
|
+
- Complete RPC method reference with examples
|
|
242
|
+
- Full configuration options
|
|
243
|
+
- Database schema documentation
|
|
244
|
+
- Security implementation details
|
|
245
|
+
- Migration guides
|
|
360
246
|
|
|
361
|
-
##
|
|
247
|
+
## Security
|
|
362
248
|
|
|
363
|
-
|
|
249
|
+
All authenticated operations require Ed25519 signatures:
|
|
250
|
+
- **Message Format**: `{method}:{username}:{context}:{timestamp}`
|
|
251
|
+
- **Signature**: Base64-encoded Ed25519 signature of the message
|
|
252
|
+
- **Replay Protection**: Timestamps must be within 5 minutes
|
|
253
|
+
- **Username Ownership**: Verified via public key signature
|
|
364
254
|
|
|
365
|
-
|
|
366
|
-
- ❌ Removed: Topic-based discovery, bloom filters, public peer listings
|
|
367
|
-
- ✅ Added: Username claiming, service publishing, UUID-based privacy
|
|
255
|
+
See [ADVANCED.md](./ADVANCED.md#security) for detailed security documentation.
|
|
368
256
|
|
|
369
257
|
## License
|
|
370
258
|
|