mirror of
https://github.com/xtr-dev/rondevu-server.git
synced 2025-12-13 20:33:25 +00:00
Make README more concise with ADVANCED.md
Restructure documentation for better discoverability: Changes: - README.md: 624 → 259 lines (58% reduction) - ADVANCED.md: New comprehensive guide (502 lines) README.md now contains: ✅ Features and architecture overview ✅ Quick start commands ✅ RPC interface basics ✅ Core method examples ✅ Configuration quick reference ✅ Links to advanced docs ADVANCED.md contains: 📚 Complete RPC method reference (8 methods) 📚 Full configuration table 📚 Database schema documentation 📚 Security implementation details 📚 Migration guides Benefits: - Faster onboarding for API consumers - Essential examples in README - Detailed reference still accessible - Consistent documentation structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
502
ADVANCED.md
Normal file
502
ADVANCED.md
Normal file
@@ -0,0 +1,502 @@
|
||||
# Rondevu Server - Advanced Usage
|
||||
|
||||
Comprehensive API reference, configuration guide, database schema, and security details.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [RPC Methods](#rpc-methods)
|
||||
- [Configuration](#configuration)
|
||||
- [Database Schema](#database-schema)
|
||||
- [Security](#security)
|
||||
- [Migration Guide](#migration-guide)
|
||||
|
||||
---
|
||||
|
||||
## RPC Methods
|
||||
|
||||
### `getUser`
|
||||
Check username availability
|
||||
|
||||
**Parameters:**
|
||||
- `username` - Username to check
|
||||
|
||||
**Message format:** `getUser:{username}:{timestamp}` (no authentication required)
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"method": "getUser",
|
||||
"message": "getUser:alice:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": { "username": "alice" }
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"username": "alice",
|
||||
"available": false,
|
||||
"claimedAt": 1733404800000,
|
||||
"expiresAt": 1765027200000,
|
||||
"publicKey": "base64-encoded-public-key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `claimUsername`
|
||||
Claim a username with cryptographic proof
|
||||
|
||||
**Parameters:**
|
||||
- `username` - Username to claim
|
||||
- `publicKey` - Base64-encoded Ed25519 public key
|
||||
|
||||
**Message format:** `claim:{username}:{timestamp}`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"method": "claimUsername",
|
||||
"message": "claim:alice:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"username": "alice",
|
||||
"publicKey": "base64-encoded-public-key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"success": true,
|
||||
"username": "alice"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `getService`
|
||||
Get service by FQN (direct lookup, random discovery, or paginated)
|
||||
|
||||
**Parameters:**
|
||||
- `serviceFqn` - Service FQN (e.g., `chat:1.0.0` or `chat:1.0.0@alice`)
|
||||
- `limit` - (optional) Number of results for paginated mode
|
||||
- `offset` - (optional) Offset for paginated mode
|
||||
|
||||
**Message format:** `getService:{username}:{serviceFqn}:{timestamp}`
|
||||
|
||||
**Modes:**
|
||||
1. **Direct lookup** (with @username): Returns specific user's service
|
||||
2. **Random** (without @username, no limit): Returns random service
|
||||
3. **Paginated** (without @username, with limit): Returns multiple services
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"method": "getService",
|
||||
"message": "getService:bob:chat:1.0.0:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"serviceFqn": "chat:1.0.0@alice"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"serviceId": "uuid",
|
||||
"username": "alice",
|
||||
"serviceFqn": "chat:1.0.0@alice",
|
||||
"offerId": "offer-hash",
|
||||
"sdp": "v=0...",
|
||||
"createdAt": 1733404800000,
|
||||
"expiresAt": 1733405100000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `publishService`
|
||||
Publish a service with offers
|
||||
|
||||
**Parameters:**
|
||||
- `serviceFqn` - Service FQN with username (e.g., `chat:1.0.0@alice`)
|
||||
- `offers` - Array of offers, each with `sdp` field
|
||||
- `ttl` - (optional) Time to live in milliseconds
|
||||
|
||||
**Message format:** `publishService:{username}:{serviceFqn}:{timestamp}`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"method": "publishService",
|
||||
"message": "publishService:alice:chat:1.0.0@alice:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"serviceFqn": "chat:1.0.0@alice",
|
||||
"offers": [
|
||||
{ "sdp": "v=0..." },
|
||||
{ "sdp": "v=0..." }
|
||||
],
|
||||
"ttl": 300000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"serviceId": "uuid",
|
||||
"username": "alice",
|
||||
"serviceFqn": "chat:1.0.0@alice",
|
||||
"offers": [
|
||||
{
|
||||
"offerId": "offer-hash-1",
|
||||
"sdp": "v=0...",
|
||||
"createdAt": 1733404800000,
|
||||
"expiresAt": 1733405100000
|
||||
}
|
||||
],
|
||||
"createdAt": 1733404800000,
|
||||
"expiresAt": 1733405100000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `deleteService`
|
||||
Delete a service
|
||||
|
||||
**Parameters:**
|
||||
- `serviceFqn` - Service FQN with username
|
||||
|
||||
**Message format:** `deleteService:{username}:{serviceFqn}:{timestamp}`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"method": "deleteService",
|
||||
"message": "deleteService:alice:chat:1.0.0@alice:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"serviceFqn": "chat:1.0.0@alice"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": { "success": true }
|
||||
}
|
||||
```
|
||||
|
||||
### `answerOffer`
|
||||
Answer a specific offer
|
||||
|
||||
**Parameters:**
|
||||
- `serviceFqn` - Service FQN
|
||||
- `offerId` - Offer ID
|
||||
- `sdp` - Answer SDP
|
||||
|
||||
**Message format:** `answerOffer:{username}:{offerId}:{timestamp}`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"method": "answerOffer",
|
||||
"message": "answerOffer:bob:offer-hash:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"serviceFqn": "chat:1.0.0@alice",
|
||||
"offerId": "offer-hash",
|
||||
"sdp": "v=0..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"success": true,
|
||||
"offerId": "offer-hash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `getOfferAnswer`
|
||||
Get answer for an offer (offerer polls this)
|
||||
|
||||
**Parameters:**
|
||||
- `serviceFqn` - Service FQN
|
||||
- `offerId` - Offer ID
|
||||
|
||||
**Message format:** `getOfferAnswer:{username}:{offerId}:{timestamp}`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"method": "getOfferAnswer",
|
||||
"message": "getOfferAnswer:alice:offer-hash:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"serviceFqn": "chat:1.0.0@alice",
|
||||
"offerId": "offer-hash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"sdp": "v=0...",
|
||||
"offerId": "offer-hash",
|
||||
"answererId": "bob",
|
||||
"answeredAt": 1733404800000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `poll`
|
||||
Combined polling for answers and ICE candidates
|
||||
|
||||
**Parameters:**
|
||||
- `since` - (optional) Timestamp to get only new data
|
||||
|
||||
**Message format:** `poll:{username}:{timestamp}`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"method": "poll",
|
||||
"message": "poll:alice:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"since": 1733404800000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"answers": [
|
||||
{
|
||||
"offerId": "offer-hash",
|
||||
"serviceId": "service-uuid",
|
||||
"answererId": "bob",
|
||||
"sdp": "v=0...",
|
||||
"answeredAt": 1733404800000
|
||||
}
|
||||
],
|
||||
"iceCandidates": {
|
||||
"offer-hash": [
|
||||
{
|
||||
"candidate": { "candidate": "...", "sdpMid": "0", "sdpMLineIndex": 0 },
|
||||
"role": "answerer",
|
||||
"username": "bob",
|
||||
"createdAt": 1733404800000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `addIceCandidates`
|
||||
Add ICE candidates to an offer
|
||||
|
||||
**Parameters:**
|
||||
- `serviceFqn` - Service FQN
|
||||
- `offerId` - Offer ID
|
||||
- `candidates` - Array of ICE candidates
|
||||
|
||||
**Message format:** `addIceCandidates:{username}:{offerId}:{timestamp}`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"method": "addIceCandidates",
|
||||
"message": "addIceCandidates:alice:offer-hash:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"serviceFqn": "chat:1.0.0@alice",
|
||||
"offerId": "offer-hash",
|
||||
"candidates": [
|
||||
{
|
||||
"candidate": "candidate:...",
|
||||
"sdpMid": "0",
|
||||
"sdpMLineIndex": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"count": 1,
|
||||
"offerId": "offer-hash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `getIceCandidates`
|
||||
Get ICE candidates for an offer
|
||||
|
||||
**Parameters:**
|
||||
- `serviceFqn` - Service FQN
|
||||
- `offerId` - Offer ID
|
||||
- `since` - (optional) Timestamp to get only new candidates
|
||||
|
||||
**Message format:** `getIceCandidates:{username}:{offerId}:{timestamp}`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"method": "getIceCandidates",
|
||||
"message": "getIceCandidates:alice:offer-hash:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"serviceFqn": "chat:1.0.0@alice",
|
||||
"offerId": "offer-hash",
|
||||
"since": 1733404800000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"candidates": [
|
||||
{
|
||||
"candidate": {
|
||||
"candidate": "candidate:...",
|
||||
"sdpMid": "0",
|
||||
"sdpMLineIndex": 0
|
||||
},
|
||||
"createdAt": 1733404800000
|
||||
}
|
||||
],
|
||||
"offerId": "offer-hash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
Environment variables:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `PORT` | `3000` | Server port (Node.js/Docker) |
|
||||
| `CORS_ORIGINS` | `*` | Comma-separated allowed origins |
|
||||
| `STORAGE_PATH` | `./rondevu.db` | SQLite database path (use `:memory:` for in-memory) |
|
||||
| `VERSION` | `0.5.0` | Server version (semver) |
|
||||
| `OFFER_DEFAULT_TTL` | `60000` | Default offer TTL in ms (1 minute) |
|
||||
| `OFFER_MIN_TTL` | `60000` | Minimum offer TTL in ms (1 minute) |
|
||||
| `OFFER_MAX_TTL` | `86400000` | Maximum offer TTL in ms (24 hours) |
|
||||
| `CLEANUP_INTERVAL` | `60000` | Cleanup interval in ms (1 minute) |
|
||||
| `MAX_OFFERS_PER_REQUEST` | `100` | Maximum offers per create request |
|
||||
|
||||
## Database Schema
|
||||
|
||||
### usernames
|
||||
- `username` (PK): Claimed username
|
||||
- `public_key`: Ed25519 public key (base64)
|
||||
- `claimed_at`: Claim timestamp
|
||||
- `expires_at`: Expiry timestamp (365 days)
|
||||
- `last_used`: Last activity timestamp
|
||||
- `metadata`: Optional JSON metadata
|
||||
|
||||
### services
|
||||
- `id` (PK): Service ID (UUID)
|
||||
- `username` (FK): Owner username
|
||||
- `service_fqn`: Fully qualified name (chat:1.0.0@alice)
|
||||
- `service_name`: Service name component (chat)
|
||||
- `version`: Version component (1.0.0)
|
||||
- `created_at`, `expires_at`: Timestamps
|
||||
- UNIQUE constraint on (service_name, version, username)
|
||||
|
||||
### offers
|
||||
- `id` (PK): Offer ID (hash of SDP)
|
||||
- `username` (FK): Owner username
|
||||
- `service_id` (FK): Link to service
|
||||
- `service_fqn`: Denormalized service FQN
|
||||
- `sdp`: WebRTC offer SDP
|
||||
- `answerer_username`: Username of answerer (null until answered)
|
||||
- `answer_sdp`: WebRTC answer SDP (null until answered)
|
||||
- `answered_at`: Timestamp when answered
|
||||
- `created_at`, `expires_at`, `last_seen`: Timestamps
|
||||
|
||||
### ice_candidates
|
||||
- `id` (PK): Auto-increment ID
|
||||
- `offer_id` (FK): Link to offer
|
||||
- `username`: Username who sent the candidate
|
||||
- `role`: 'offerer' or 'answerer'
|
||||
- `candidate`: JSON-encoded candidate
|
||||
- `created_at`: Timestamp
|
||||
|
||||
## Security
|
||||
|
||||
### Ed25519 Signature Authentication
|
||||
All authenticated requests require:
|
||||
- **message**: Signed message with format-specific structure
|
||||
- **signature**: Base64-encoded Ed25519 signature of the message
|
||||
- Username is extracted from the message
|
||||
|
||||
### Username Claiming
|
||||
- **Algorithm**: Ed25519 signatures
|
||||
- **Message Format**: `claim:{username}:{timestamp}`
|
||||
- **Replay Protection**: Timestamp must be within 5 minutes
|
||||
- **Key Management**: Private keys never leave the client
|
||||
- **Validity**: 365 days, auto-renewed on use
|
||||
|
||||
### Anonymous Users
|
||||
- **Format**: `anon-{timestamp}-{random}` (e.g., `anon-lx2w34-a3f501`)
|
||||
- **Generation**: Can be generated by client for testing
|
||||
- **Behavior**: Same as regular usernames, must be explicitly claimed like any username
|
||||
|
||||
### Service Publishing
|
||||
- **Ownership Verification**: Every publish requires username signature
|
||||
- **Message Format**: `publishService:{username}:{serviceFqn}:{timestamp}`
|
||||
- **Auto-Renewal**: Publishing a service extends username expiry
|
||||
|
||||
### ICE Candidate Filtering
|
||||
- Server filters candidates by role to prevent peers from receiving their own candidates
|
||||
- Offerers receive only answerer candidates
|
||||
- Answerers receive only offerer candidates
|
||||
|
||||
## Migration from v0.4.x
|
||||
|
||||
See [MIGRATION.md](../MIGRATION.md) for detailed migration guide.
|
||||
|
||||
**Key Changes:**
|
||||
- Moved from REST API to RPC interface with single `/rpc` endpoint
|
||||
- All methods now use POST with JSON body
|
||||
- Batch operations supported
|
||||
- Authentication is per-method instead of per-endpoint middleware
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
497
README.md
497
README.md
@@ -103,521 +103,156 @@ All API calls are made to `POST /rpc` with JSON-RPC format.
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"username": "alice",
|
||||
"available": false,
|
||||
"claimedAt": 1733404800000,
|
||||
"expiresAt": 1765027200000,
|
||||
"publicKey": "base64-encoded-public-key"
|
||||
}
|
||||
"result": { /* method-specific data */ }
|
||||
}
|
||||
```
|
||||
|
||||
**Batch responses:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"success": true,
|
||||
"result": { "username": "alice", "available": false }
|
||||
},
|
||||
{
|
||||
"success": true,
|
||||
"result": { "success": true, "username": "bob" }
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Error response:**
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Username already claimed by different public key"
|
||||
"error": "Error message"
|
||||
}
|
||||
```
|
||||
|
||||
## RPC Methods
|
||||
**Batch responses:** Array of responses matching request array order.
|
||||
|
||||
### `getUser`
|
||||
Check username availability
|
||||
## Core Methods
|
||||
|
||||
**Parameters:**
|
||||
- `username` - Username to check
|
||||
### Username Management
|
||||
|
||||
**Message format:** `getUser:{username}:{timestamp}` (no authentication required)
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
```typescript
|
||||
// Check username availability
|
||||
POST /rpc
|
||||
{
|
||||
"method": "getUser",
|
||||
"message": "getUser:alice:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": { "username": "alice" }
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"username": "alice",
|
||||
"available": false,
|
||||
"claimedAt": 1733404800000,
|
||||
"expiresAt": 1765027200000,
|
||||
"publicKey": "base64-encoded-public-key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `claimUsername`
|
||||
Claim a username with cryptographic proof
|
||||
|
||||
**Parameters:**
|
||||
- `username` - Username to claim
|
||||
- `publicKey` - Base64-encoded Ed25519 public key
|
||||
|
||||
**Message format:** `claim:{username}:{timestamp}`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
// Claim username (requires signature)
|
||||
POST /rpc
|
||||
{
|
||||
"method": "claimUsername",
|
||||
"message": "claim:alice:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"username": "alice",
|
||||
"publicKey": "base64-encoded-public-key"
|
||||
"publicKey": "base64-public-key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"success": true,
|
||||
"username": "alice"
|
||||
}
|
||||
}
|
||||
```
|
||||
### Service Publishing
|
||||
|
||||
### `getService`
|
||||
Get service by FQN (direct lookup, random discovery, or paginated)
|
||||
|
||||
**Parameters:**
|
||||
- `serviceFqn` - Service FQN (e.g., `chat:1.0.0` or `chat:1.0.0@alice`)
|
||||
- `limit` - (optional) Number of results for paginated mode
|
||||
- `offset` - (optional) Offset for paginated mode
|
||||
|
||||
**Message format:** `getService:{username}:{serviceFqn}:{timestamp}`
|
||||
|
||||
**Modes:**
|
||||
1. **Direct lookup** (with @username): Returns specific user's service
|
||||
2. **Random** (without @username, no limit): Returns random service
|
||||
3. **Paginated** (without @username, with limit): Returns multiple services
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"method": "getService",
|
||||
"message": "getService:bob:chat:1.0.0:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"serviceFqn": "chat:1.0.0@alice"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"serviceId": "uuid",
|
||||
"username": "alice",
|
||||
"serviceFqn": "chat:1.0.0@alice",
|
||||
"offerId": "offer-hash",
|
||||
"sdp": "v=0...",
|
||||
"createdAt": 1733404800000,
|
||||
"expiresAt": 1733405100000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `publishService`
|
||||
Publish a service with offers
|
||||
|
||||
**Parameters:**
|
||||
- `serviceFqn` - Service FQN with username (e.g., `chat:1.0.0@alice`)
|
||||
- `offers` - Array of offers, each with `sdp` field
|
||||
- `ttl` - (optional) Time to live in milliseconds
|
||||
|
||||
**Message format:** `publishService:{username}:{serviceFqn}:{timestamp}`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
```typescript
|
||||
// Publish service (requires signature)
|
||||
POST /rpc
|
||||
{
|
||||
"method": "publishService",
|
||||
"message": "publishService:alice:chat:1.0.0@alice:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"serviceFqn": "chat:1.0.0@alice",
|
||||
"offers": [
|
||||
{ "sdp": "v=0..." },
|
||||
{ "sdp": "v=0..." }
|
||||
],
|
||||
"offers": [{ "sdp": "webrtc-offer-sdp" }],
|
||||
"ttl": 300000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
### Service Discovery
|
||||
|
||||
```typescript
|
||||
// Get specific service
|
||||
POST /rpc
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"serviceId": "uuid",
|
||||
"username": "alice",
|
||||
"serviceFqn": "chat:1.0.0@alice",
|
||||
"offers": [
|
||||
{
|
||||
"offerId": "offer-hash-1",
|
||||
"sdp": "v=0...",
|
||||
"createdAt": 1733404800000,
|
||||
"expiresAt": 1733405100000
|
||||
}
|
||||
],
|
||||
"createdAt": 1733404800000,
|
||||
"expiresAt": 1733405100000
|
||||
}
|
||||
"method": "getService",
|
||||
"params": { "serviceFqn": "chat:1.0.0@alice" }
|
||||
}
|
||||
```
|
||||
|
||||
### `deleteService`
|
||||
Delete a service
|
||||
|
||||
**Parameters:**
|
||||
- `serviceFqn` - Service FQN with username
|
||||
|
||||
**Message format:** `deleteService:{username}:{serviceFqn}:{timestamp}`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
// Random discovery
|
||||
POST /rpc
|
||||
{
|
||||
"method": "deleteService",
|
||||
"message": "deleteService:alice:chat:1.0.0@alice:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"method": "getService",
|
||||
"params": { "serviceFqn": "chat:1.0.0" }
|
||||
}
|
||||
|
||||
// Paginated discovery
|
||||
POST /rpc
|
||||
{
|
||||
"method": "getService",
|
||||
"params": {
|
||||
"serviceFqn": "chat:1.0.0@alice"
|
||||
"serviceFqn": "chat:1.0.0",
|
||||
"limit": 10,
|
||||
"offset": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": { "success": true }
|
||||
}
|
||||
```
|
||||
### WebRTC Signaling
|
||||
|
||||
### `answerOffer`
|
||||
Answer a specific offer
|
||||
|
||||
**Parameters:**
|
||||
- `serviceFqn` - Service FQN
|
||||
- `offerId` - Offer ID
|
||||
- `sdp` - Answer SDP
|
||||
|
||||
**Message format:** `answerOffer:{username}:{offerId}:{timestamp}`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
```typescript
|
||||
// Answer offer (requires signature)
|
||||
POST /rpc
|
||||
{
|
||||
"method": "answerOffer",
|
||||
"message": "answerOffer:bob:offer-hash:1733404800000",
|
||||
"message": "answer:bob:offer-id:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"serviceFqn": "chat:1.0.0@alice",
|
||||
"offerId": "offer-hash",
|
||||
"sdp": "v=0..."
|
||||
"offerId": "offer-id",
|
||||
"sdp": "webrtc-answer-sdp"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"success": true,
|
||||
"offerId": "offer-hash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `getOfferAnswer`
|
||||
Get answer for an offer (offerer polls this)
|
||||
|
||||
**Parameters:**
|
||||
- `serviceFqn` - Service FQN
|
||||
- `offerId` - Offer ID
|
||||
|
||||
**Message format:** `getOfferAnswer:{username}:{offerId}:{timestamp}`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"method": "getOfferAnswer",
|
||||
"message": "getOfferAnswer:alice:offer-hash:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"serviceFqn": "chat:1.0.0@alice",
|
||||
"offerId": "offer-hash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"sdp": "v=0...",
|
||||
"offerId": "offer-hash",
|
||||
"answererId": "bob",
|
||||
"answeredAt": 1733404800000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `poll`
|
||||
Combined polling for answers and ICE candidates
|
||||
|
||||
**Parameters:**
|
||||
- `since` - (optional) Timestamp to get only new data
|
||||
|
||||
**Message format:** `poll:{username}:{timestamp}`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"method": "poll",
|
||||
"message": "poll:alice:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"since": 1733404800000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"answers": [
|
||||
{
|
||||
"offerId": "offer-hash",
|
||||
"serviceId": "service-uuid",
|
||||
"answererId": "bob",
|
||||
"sdp": "v=0...",
|
||||
"answeredAt": 1733404800000
|
||||
}
|
||||
],
|
||||
"iceCandidates": {
|
||||
"offer-hash": [
|
||||
{
|
||||
"candidate": { "candidate": "...", "sdpMid": "0", "sdpMLineIndex": 0 },
|
||||
"role": "answerer",
|
||||
"username": "bob",
|
||||
"createdAt": 1733404800000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `addIceCandidates`
|
||||
Add ICE candidates to an offer
|
||||
|
||||
**Parameters:**
|
||||
- `serviceFqn` - Service FQN
|
||||
- `offerId` - Offer ID
|
||||
- `candidates` - Array of ICE candidates
|
||||
|
||||
**Message format:** `addIceCandidates:{username}:{offerId}:{timestamp}`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
// Add ICE candidates (requires signature)
|
||||
POST /rpc
|
||||
{
|
||||
"method": "addIceCandidates",
|
||||
"message": "addIceCandidates:alice:offer-hash:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"serviceFqn": "chat:1.0.0@alice",
|
||||
"offerId": "offer-hash",
|
||||
"candidates": [
|
||||
{
|
||||
"candidate": "candidate:...",
|
||||
"sdpMid": "0",
|
||||
"sdpMLineIndex": 0
|
||||
}
|
||||
]
|
||||
"offerId": "offer-id",
|
||||
"candidates": [{ /* RTCIceCandidateInit */ }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
// Poll for answers and ICE candidates (requires signature)
|
||||
POST /rpc
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"count": 1,
|
||||
"offerId": "offer-hash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `getIceCandidates`
|
||||
Get ICE candidates for an offer
|
||||
|
||||
**Parameters:**
|
||||
- `serviceFqn` - Service FQN
|
||||
- `offerId` - Offer ID
|
||||
- `since` - (optional) Timestamp to get only new candidates
|
||||
|
||||
**Message format:** `getIceCandidates:{username}:{offerId}:{timestamp}`
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
{
|
||||
"method": "getIceCandidates",
|
||||
"message": "getIceCandidates:alice:offer-hash:1733404800000",
|
||||
"signature": "base64-signature",
|
||||
"params": {
|
||||
"serviceFqn": "chat:1.0.0@alice",
|
||||
"offerId": "offer-hash",
|
||||
"since": 1733404800000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"candidates": [
|
||||
{
|
||||
"candidate": {
|
||||
"candidate": "candidate:...",
|
||||
"sdpMid": "0",
|
||||
"sdpMLineIndex": 0
|
||||
},
|
||||
"createdAt": 1733404800000
|
||||
}
|
||||
],
|
||||
"offerId": "offer-hash"
|
||||
}
|
||||
"method": "poll",
|
||||
"params": { "since": 1733404800000 }
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Environment variables:
|
||||
Quick reference for common environment variables:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `PORT` | `3000` | Server port (Node.js/Docker) |
|
||||
| `CORS_ORIGINS` | `*` | Comma-separated allowed origins |
|
||||
| `STORAGE_PATH` | `./rondevu.db` | SQLite database path (use `:memory:` for in-memory) |
|
||||
| `VERSION` | `0.5.0` | Server version (semver) |
|
||||
| `OFFER_DEFAULT_TTL` | `60000` | Default offer TTL in ms (1 minute) |
|
||||
| `OFFER_MIN_TTL` | `60000` | Minimum offer TTL in ms (1 minute) |
|
||||
| `OFFER_MAX_TTL` | `86400000` | Maximum offer TTL in ms (24 hours) |
|
||||
| `CLEANUP_INTERVAL` | `60000` | Cleanup interval in ms (1 minute) |
|
||||
| `MAX_OFFERS_PER_REQUEST` | `100` | Maximum offers per create request |
|
||||
|
||||
## Database Schema
|
||||
📚 See [ADVANCED.md](./ADVANCED.md#configuration) for complete configuration reference.
|
||||
|
||||
### usernames
|
||||
- `username` (PK): Claimed username
|
||||
- `public_key`: Ed25519 public key (base64)
|
||||
- `claimed_at`: Claim timestamp
|
||||
- `expires_at`: Expiry timestamp (365 days)
|
||||
- `last_used`: Last activity timestamp
|
||||
- `metadata`: Optional JSON metadata
|
||||
## Documentation
|
||||
|
||||
### services
|
||||
- `id` (PK): Service ID (UUID)
|
||||
- `username` (FK): Owner username
|
||||
- `service_fqn`: Fully qualified name (chat:1.0.0@alice)
|
||||
- `service_name`: Service name component (chat)
|
||||
- `version`: Version component (1.0.0)
|
||||
- `created_at`, `expires_at`: Timestamps
|
||||
- UNIQUE constraint on (service_name, version, username)
|
||||
|
||||
### offers
|
||||
- `id` (PK): Offer ID (hash of SDP)
|
||||
- `username` (FK): Owner username
|
||||
- `service_id` (FK): Link to service
|
||||
- `service_fqn`: Denormalized service FQN
|
||||
- `sdp`: WebRTC offer SDP
|
||||
- `answerer_username`: Username of answerer (null until answered)
|
||||
- `answer_sdp`: WebRTC answer SDP (null until answered)
|
||||
- `answered_at`: Timestamp when answered
|
||||
- `created_at`, `expires_at`, `last_seen`: Timestamps
|
||||
|
||||
### ice_candidates
|
||||
- `id` (PK): Auto-increment ID
|
||||
- `offer_id` (FK): Link to offer
|
||||
- `username`: Username who sent the candidate
|
||||
- `role`: 'offerer' or 'answerer'
|
||||
- `candidate`: JSON-encoded candidate
|
||||
- `created_at`: Timestamp
|
||||
📚 **[ADVANCED.md](./ADVANCED.md)** - Comprehensive guide including:
|
||||
- Complete RPC method reference with examples
|
||||
- Full configuration options
|
||||
- Database schema documentation
|
||||
- Security implementation details
|
||||
- Migration guides
|
||||
|
||||
## Security
|
||||
|
||||
### Ed25519 Signature Authentication
|
||||
All authenticated requests require:
|
||||
- **message**: Signed message with format-specific structure
|
||||
- **signature**: Base64-encoded Ed25519 signature of the message
|
||||
- Username is extracted from the message
|
||||
All authenticated operations require Ed25519 signatures:
|
||||
- **Message Format**: `{method}:{username}:{context}:{timestamp}`
|
||||
- **Signature**: Base64-encoded Ed25519 signature of the message
|
||||
- **Replay Protection**: Timestamps must be within 5 minutes
|
||||
- **Username Ownership**: Verified via public key signature
|
||||
|
||||
### Username Claiming
|
||||
- **Algorithm**: Ed25519 signatures
|
||||
- **Message Format**: `claim:{username}:{timestamp}`
|
||||
- **Replay Protection**: Timestamp must be within 5 minutes
|
||||
- **Key Management**: Private keys never leave the client
|
||||
- **Validity**: 365 days, auto-renewed on use
|
||||
|
||||
### Anonymous Users
|
||||
- **Format**: `anon-{timestamp}-{random}` (e.g., `anon-lx2w34-a3f501`)
|
||||
- **Generation**: Can be generated by client for testing
|
||||
- **Behavior**: Same as regular usernames, must be explicitly claimed like any username
|
||||
|
||||
### Service Publishing
|
||||
- **Ownership Verification**: Every publish requires username signature
|
||||
- **Message Format**: `publishService:{username}:{serviceFqn}:{timestamp}`
|
||||
- **Auto-Renewal**: Publishing a service extends username expiry
|
||||
|
||||
### ICE Candidate Filtering
|
||||
- Server filters candidates by role to prevent peers from receiving their own candidates
|
||||
- Offerers receive only answerer candidates
|
||||
- Answerers receive only offerer candidates
|
||||
|
||||
## Migration from v0.4.x
|
||||
|
||||
See [MIGRATION.md](../MIGRATION.md) for detailed migration guide.
|
||||
|
||||
**Key Changes:**
|
||||
- Moved from REST API to RPC interface with single `/rpc` endpoint
|
||||
- All methods now use POST with JSON body
|
||||
- Batch operations supported
|
||||
- Authentication is per-method instead of per-endpoint middleware
|
||||
See [ADVANCED.md](./ADVANCED.md#security) for detailed security documentation.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Reference in New Issue
Block a user