mirror of
https://github.com/xtr-dev/rondevu-server.git
synced 2025-12-10 10:53:24 +00:00
Compare commits
4 Commits
v0.1.3
...
08e1433088
| Author | SHA1 | Date | |
|---|---|---|---|
| 08e1433088 | |||
| 70d018c666 | |||
| 2cff4c8544 | |||
| 00499732c4 |
19
README.md
19
README.md
@@ -53,16 +53,7 @@ Health check endpoint with version
|
|||||||
#### `POST /register`
|
#### `POST /register`
|
||||||
Register a new peer and receive credentials (peerId + secret)
|
Register a new peer and receive credentials (peerId + secret)
|
||||||
|
|
||||||
**Request (optional):**
|
Generates a cryptographically random 128-bit peer ID.
|
||||||
```json
|
|
||||||
{
|
|
||||||
"peerId": "my-custom-peer-id"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notes:**
|
|
||||||
- `peerId` (optional): Custom peer ID (1-128 characters). If not provided, a random ID will be generated.
|
|
||||||
- Returns 409 Conflict if the custom peer ID is already in use.
|
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
```json
|
```json
|
||||||
@@ -111,7 +102,8 @@ Find offers by topic with optional bloom filter exclusion
|
|||||||
"topics": ["movie-xyz", "hd-content"],
|
"topics": ["movie-xyz", "hd-content"],
|
||||||
"expiresAt": 1234567890,
|
"expiresAt": 1234567890,
|
||||||
"lastSeen": 1234567890,
|
"lastSeen": 1234567890,
|
||||||
"hasSecret": true // Indicates if secret is required to answer
|
"hasSecret": true, // Indicates if secret is required to answer
|
||||||
|
"info": "Looking for peers in EU region" // Public info field (optional)
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"total": 42,
|
"total": 42,
|
||||||
@@ -121,6 +113,7 @@ Find offers by topic with optional bloom filter exclusion
|
|||||||
|
|
||||||
**Notes:**
|
**Notes:**
|
||||||
- `hasSecret`: Boolean flag indicating whether a secret is required to answer this offer. The actual secret is never exposed in public endpoints.
|
- `hasSecret`: Boolean flag indicating whether a secret is required to answer this offer. The actual secret is never exposed in public endpoints.
|
||||||
|
- `info`: Optional public metadata field (max 128 characters) visible to all peers.
|
||||||
|
|
||||||
#### `GET /peers/:peerId/offers`
|
#### `GET /peers/:peerId/offers`
|
||||||
View all offers from a specific peer
|
View all offers from a specific peer
|
||||||
@@ -140,7 +133,8 @@ Create one or more offers
|
|||||||
"sdp": "v=0...",
|
"sdp": "v=0...",
|
||||||
"topics": ["movie-xyz", "hd-content"],
|
"topics": ["movie-xyz", "hd-content"],
|
||||||
"ttl": 300000,
|
"ttl": 300000,
|
||||||
"secret": "my-secret-password" // Optional: protect offer (max 128 chars)
|
"secret": "my-secret-password", // Optional: protect offer (max 128 chars)
|
||||||
|
"info": "Looking for peers in EU region" // Optional: public info (max 128 chars)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -148,6 +142,7 @@ Create one or more offers
|
|||||||
|
|
||||||
**Notes:**
|
**Notes:**
|
||||||
- `secret` (optional): Protect the offer with a secret. Answerers must provide the correct secret to connect.
|
- `secret` (optional): Protect the offer with a secret. Answerers must provide the correct secret to connect.
|
||||||
|
- `info` (optional): Public metadata visible to all peers (max 128 characters). Useful for describing the offer or connection requirements.
|
||||||
|
|
||||||
#### `GET /offers/mine`
|
#### `GET /offers/mine`
|
||||||
List all offers owned by authenticated peer
|
List all offers owned by authenticated peer
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@xtr-dev/rondevu-server",
|
"name": "@xtr-dev/rondevu-server",
|
||||||
"version": "0.1.3",
|
"version": "0.1.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@xtr-dev/rondevu-server",
|
"name": "@xtr-dev/rondevu-server",
|
||||||
"version": "0.1.3",
|
"version": "0.1.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hono/node-server": "^1.19.6",
|
"@hono/node-server": "^1.19.6",
|
||||||
"better-sqlite3": "^12.4.1",
|
"better-sqlite3": "^12.4.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@xtr-dev/rondevu-server",
|
"name": "@xtr-dev/rondevu-server",
|
||||||
"version": "0.1.3",
|
"version": "0.1.5",
|
||||||
"description": "Topic-based peer discovery and signaling server for distributed P2P applications",
|
"description": "Topic-based peer discovery and signaling server for distributed P2P applications",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
49
src/app.ts
49
src/app.ts
@@ -64,37 +64,12 @@ export function createApp(storage: Storage, config: Config) {
|
|||||||
/**
|
/**
|
||||||
* POST /register
|
* POST /register
|
||||||
* Register a new peer and receive credentials
|
* Register a new peer and receive credentials
|
||||||
* Accepts optional peerId in request body for custom peer IDs
|
* Generates a cryptographically random peer ID (128-bit)
|
||||||
*/
|
*/
|
||||||
app.post('/register', async (c) => {
|
app.post('/register', async (c) => {
|
||||||
try {
|
try {
|
||||||
let peerId: string;
|
// Always generate a random peer ID
|
||||||
|
const peerId = generatePeerId();
|
||||||
// Check if custom peer ID is provided
|
|
||||||
const body = await c.req.json().catch(() => ({}));
|
|
||||||
const customPeerId = body.peerId;
|
|
||||||
|
|
||||||
if (customPeerId !== undefined) {
|
|
||||||
// Validate custom peer ID
|
|
||||||
if (typeof customPeerId !== 'string' || customPeerId.length === 0) {
|
|
||||||
return c.json({ error: 'Peer ID must be a non-empty string' }, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customPeerId.length > 128) {
|
|
||||||
return c.json({ error: 'Peer ID must be 128 characters or less' }, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if peer ID is already in use by checking for active offers
|
|
||||||
const existingOffers = await storage.getOffersByPeerId(customPeerId);
|
|
||||||
if (existingOffers.length > 0) {
|
|
||||||
return c.json({ error: 'Peer ID is already in use' }, 409);
|
|
||||||
}
|
|
||||||
|
|
||||||
peerId = customPeerId;
|
|
||||||
} else {
|
|
||||||
// Generate new peer ID
|
|
||||||
peerId = generatePeerId();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt peer ID with server secret (async operation)
|
// Encrypt peer ID with server secret (async operation)
|
||||||
const secret = await encryptPeerId(peerId, config.authSecret);
|
const secret = await encryptPeerId(peerId, config.authSecret);
|
||||||
@@ -151,6 +126,16 @@ export function createApp(storage: Storage, config: Config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate info if provided
|
||||||
|
if (offer.info !== undefined) {
|
||||||
|
if (typeof offer.info !== 'string') {
|
||||||
|
return c.json({ error: 'Info must be a string' }, 400);
|
||||||
|
}
|
||||||
|
if (offer.info.length > 128) {
|
||||||
|
return c.json({ error: 'Info must be 128 characters or less' }, 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validate topics
|
// Validate topics
|
||||||
if (!Array.isArray(offer.topics) || offer.topics.length === 0) {
|
if (!Array.isArray(offer.topics) || offer.topics.length === 0) {
|
||||||
return c.json({ error: 'Each offer must have a non-empty topics array' }, 400);
|
return c.json({ error: 'Each offer must have a non-empty topics array' }, 400);
|
||||||
@@ -182,6 +167,7 @@ export function createApp(storage: Storage, config: Config) {
|
|||||||
topics: offer.topics,
|
topics: offer.topics,
|
||||||
expiresAt: Date.now() + ttl,
|
expiresAt: Date.now() + ttl,
|
||||||
secret: offer.secret,
|
secret: offer.secret,
|
||||||
|
info: offer.info,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,7 +240,8 @@ export function createApp(storage: Storage, config: Config) {
|
|||||||
topics: o.topics,
|
topics: o.topics,
|
||||||
expiresAt: o.expiresAt,
|
expiresAt: o.expiresAt,
|
||||||
lastSeen: o.lastSeen,
|
lastSeen: o.lastSeen,
|
||||||
hasSecret: !!o.secret // Indicate if secret is required without exposing it
|
hasSecret: !!o.secret, // Indicate if secret is required without exposing it
|
||||||
|
info: o.info // Public info field
|
||||||
})),
|
})),
|
||||||
total: bloomParam ? total + excludePeerIds.length : total,
|
total: bloomParam ? total + excludePeerIds.length : total,
|
||||||
returned: offers.length
|
returned: offers.length
|
||||||
@@ -321,7 +308,8 @@ export function createApp(storage: Storage, config: Config) {
|
|||||||
topics: o.topics,
|
topics: o.topics,
|
||||||
expiresAt: o.expiresAt,
|
expiresAt: o.expiresAt,
|
||||||
lastSeen: o.lastSeen,
|
lastSeen: o.lastSeen,
|
||||||
hasSecret: !!o.secret // Indicate if secret is required without exposing it
|
hasSecret: !!o.secret, // Indicate if secret is required without exposing it
|
||||||
|
info: o.info // Public info field
|
||||||
})),
|
})),
|
||||||
topics: Array.from(topicsSet)
|
topics: Array.from(topicsSet)
|
||||||
}, 200);
|
}, 200);
|
||||||
@@ -351,6 +339,7 @@ export function createApp(storage: Storage, config: Config) {
|
|||||||
expiresAt: o.expiresAt,
|
expiresAt: o.expiresAt,
|
||||||
lastSeen: o.lastSeen,
|
lastSeen: o.lastSeen,
|
||||||
secret: o.secret, // Owner can see the secret
|
secret: o.secret, // Owner can see the secret
|
||||||
|
info: o.info, // Owner can see the info
|
||||||
answererPeerId: o.answererPeerId,
|
answererPeerId: o.answererPeerId,
|
||||||
answeredAt: o.answeredAt
|
answeredAt: o.answeredAt
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export interface Offer {
|
|||||||
expiresAt: number;
|
expiresAt: number;
|
||||||
lastSeen: number;
|
lastSeen: number;
|
||||||
secret?: string;
|
secret?: string;
|
||||||
|
info?: string;
|
||||||
answererPeerId?: string;
|
answererPeerId?: string;
|
||||||
answerSdp?: string;
|
answerSdp?: string;
|
||||||
answeredAt?: number;
|
answeredAt?: number;
|
||||||
@@ -46,6 +47,7 @@ export interface CreateOfferRequest {
|
|||||||
topics: string[];
|
topics: string[];
|
||||||
expiresAt: number;
|
expiresAt: number;
|
||||||
secret?: string;
|
secret?: string;
|
||||||
|
info?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user