mirror of
https://github.com/xtr-dev/rondevu-server.git
synced 2025-12-10 19:03:24 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cff4c8544 | |||
| 00499732c4 | |||
| 341d043358 | |||
| 23c27d4509 |
458
API.md
458
API.md
@@ -1,458 +0,0 @@
|
|||||||
# HTTP API
|
|
||||||
|
|
||||||
This API provides peer signaling and tracking endpoints for distributed peer-to-peer applications. Uses JSON request/response bodies with Origin-based session isolation.
|
|
||||||
|
|
||||||
All endpoints require an `Origin` header and accept `application/json` content type.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Sessions are organized by:
|
|
||||||
- **Origin**: The HTTP Origin header (e.g., `https://example.com`) - isolates sessions by application
|
|
||||||
- **Topic**: A string identifier for grouping related peers (max 256 chars)
|
|
||||||
- **Info**: User-provided metadata (max 1024 chars) to uniquely identify each peer
|
|
||||||
|
|
||||||
This allows multiple peers from the same application (origin) to discover each other through topics while preventing duplicate connections by comparing the info field.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## GET `/`
|
|
||||||
|
|
||||||
Returns server version information including the git commit hash used to build the server.
|
|
||||||
|
|
||||||
### Response
|
|
||||||
|
|
||||||
**Content-Type:** `application/json`
|
|
||||||
|
|
||||||
**Success (200 OK):**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"version": "a1b2c3d"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notes:**
|
|
||||||
- Returns the git commit hash from build time
|
|
||||||
- Returns "unknown" if git information is not available
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X GET http://localhost:3000/
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## GET `/topics`
|
|
||||||
|
|
||||||
Lists all topics with the count of available peers for each (paginated). Returns only topics that have unanswered sessions.
|
|
||||||
|
|
||||||
### Request
|
|
||||||
|
|
||||||
**Headers:**
|
|
||||||
- `Origin: https://example.com` (required)
|
|
||||||
|
|
||||||
**Query Parameters:**
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Default | Description |
|
|
||||||
|-----------|--------|----------|---------|---------------------------------|
|
|
||||||
| `page` | number | No | `1` | Page number (starting from 1) |
|
|
||||||
| `limit` | number | No | `100` | Results per page (max 1000) |
|
|
||||||
|
|
||||||
### Response
|
|
||||||
|
|
||||||
**Content-Type:** `application/json`
|
|
||||||
|
|
||||||
**Success (200 OK):**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"topics": [
|
|
||||||
{
|
|
||||||
"topic": "my-room",
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"topic": "another-room",
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"pagination": {
|
|
||||||
"page": 1,
|
|
||||||
"limit": 100,
|
|
||||||
"total": 2,
|
|
||||||
"hasMore": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notes:**
|
|
||||||
- Only returns topics from the same origin as the request
|
|
||||||
- Only includes topics with at least one unanswered session
|
|
||||||
- Topics are sorted alphabetically
|
|
||||||
- Counts only include unexpired sessions
|
|
||||||
- Maximum 1000 results per page
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
**Default pagination (page 1, limit 100):**
|
|
||||||
```bash
|
|
||||||
curl -X GET http://localhost:3000/topics \
|
|
||||||
-H "Origin: https://example.com"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Custom pagination:**
|
|
||||||
```bash
|
|
||||||
curl -X GET "http://localhost:3000/topics?page=2&limit=50" \
|
|
||||||
-H "Origin: https://example.com"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## GET `/:topic/sessions`
|
|
||||||
|
|
||||||
Discovers available peers for a given topic. Returns all unanswered sessions from the requesting origin.
|
|
||||||
|
|
||||||
### Request
|
|
||||||
|
|
||||||
**Headers:**
|
|
||||||
- `Origin: https://example.com` (required)
|
|
||||||
|
|
||||||
**Path Parameters:**
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
|-----------|--------|----------|-------------------------------|
|
|
||||||
| `topic` | string | Yes | Topic identifier to query |
|
|
||||||
|
|
||||||
### Response
|
|
||||||
|
|
||||||
**Content-Type:** `application/json`
|
|
||||||
|
|
||||||
**Success (200 OK):**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"sessions": [
|
|
||||||
{
|
|
||||||
"code": "550e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"info": "peer-123",
|
|
||||||
"offer": "<SIGNALING_DATA>",
|
|
||||||
"offerCandidates": ["<SIGNALING_DATA>"],
|
|
||||||
"createdAt": 1699564800000,
|
|
||||||
"expiresAt": 1699565100000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "660e8400-e29b-41d4-a716-446655440001",
|
|
||||||
"info": "peer-456",
|
|
||||||
"offer": "<SIGNALING_DATA>",
|
|
||||||
"offerCandidates": [],
|
|
||||||
"createdAt": 1699564850000,
|
|
||||||
"expiresAt": 1699565150000
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notes:**
|
|
||||||
- Only returns sessions from the same origin as the request
|
|
||||||
- Only returns sessions that haven't been answered yet
|
|
||||||
- Sessions are ordered by creation time (newest first)
|
|
||||||
- Use the `info` field to avoid answering your own offers
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X GET http://localhost:3000/my-room/sessions \
|
|
||||||
-H "Origin: https://example.com"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## POST `/:topic/offer`
|
|
||||||
|
|
||||||
Announces peer availability and creates a new session for the specified topic. Returns a unique session code (UUID) for other peers to connect to.
|
|
||||||
|
|
||||||
### Request
|
|
||||||
|
|
||||||
**Headers:**
|
|
||||||
- `Content-Type: application/json`
|
|
||||||
- `Origin: https://example.com` (required)
|
|
||||||
|
|
||||||
**Path Parameters:**
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
|-----------|--------|----------|----------------------------------------------|
|
|
||||||
| `topic` | string | Yes | Topic identifier for grouping peers (max 256 characters) |
|
|
||||||
|
|
||||||
**Body Parameters:**
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
|-----------|--------|----------|----------------------------------------------|
|
|
||||||
| `info` | string | Yes | Peer identifier/metadata (max 1024 characters) |
|
|
||||||
| `offer` | string | Yes | Signaling data for peer connection |
|
|
||||||
|
|
||||||
### Response
|
|
||||||
|
|
||||||
**Content-Type:** `application/json`
|
|
||||||
|
|
||||||
**Success (200 OK):**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": "550e8400-e29b-41d4-a716-446655440000"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Returns a unique UUID session code.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:3000/my-room/offer \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-H "Origin: https://example.com" \
|
|
||||||
-d '{
|
|
||||||
"info": "peer-123",
|
|
||||||
"offer": "<SIGNALING_DATA>"
|
|
||||||
}'
|
|
||||||
|
|
||||||
# Response:
|
|
||||||
# {"code":"550e8400-e29b-41d4-a716-446655440000"}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## POST `/answer`
|
|
||||||
|
|
||||||
Connects to an existing peer session by sending connection data or exchanging signaling information.
|
|
||||||
|
|
||||||
### Request
|
|
||||||
|
|
||||||
**Headers:**
|
|
||||||
- `Content-Type: application/json`
|
|
||||||
- `Origin: https://example.com` (required)
|
|
||||||
|
|
||||||
**Body Parameters:**
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
|-------------|--------|----------|----------------------------------------------------------|
|
|
||||||
| `code` | string | Yes | The session UUID from the offer |
|
|
||||||
| `answer` | string | No* | Response signaling data for connection establishment |
|
|
||||||
| `candidate` | string | No* | Additional signaling data for connection negotiation |
|
|
||||||
| `side` | string | Yes | Which peer is sending: `offerer` or `answerer` |
|
|
||||||
|
|
||||||
*Either `answer` or `candidate` must be provided, but not both.
|
|
||||||
|
|
||||||
### Response
|
|
||||||
|
|
||||||
**Content-Type:** `application/json`
|
|
||||||
|
|
||||||
**Success (200 OK):**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notes:**
|
|
||||||
- Origin header must match the session's origin
|
|
||||||
- Sessions are isolated by origin to group topics by domain
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
**Sending connection response:**
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:3000/answer \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-H "Origin: https://example.com" \
|
|
||||||
-d '{
|
|
||||||
"code": "550e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"answer": "<SIGNALING_DATA>",
|
|
||||||
"side": "answerer"
|
|
||||||
}'
|
|
||||||
|
|
||||||
# Response:
|
|
||||||
# {"success":true}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Sending additional signaling data:**
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:3000/answer \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-H "Origin: https://example.com" \
|
|
||||||
-d '{
|
|
||||||
"code": "550e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"candidate": "<SIGNALING_DATA>",
|
|
||||||
"side": "offerer"
|
|
||||||
}'
|
|
||||||
|
|
||||||
# Response:
|
|
||||||
# {"success":true}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## POST `/poll`
|
|
||||||
|
|
||||||
Retrieves session data including offers, responses, and signaling information from the other peer.
|
|
||||||
|
|
||||||
### Request
|
|
||||||
|
|
||||||
**Headers:**
|
|
||||||
- `Content-Type: application/json`
|
|
||||||
- `Origin: https://example.com` (required)
|
|
||||||
|
|
||||||
**Body Parameters:**
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
|-----------|--------|----------|-------------------------------------------------|
|
|
||||||
| `code` | string | Yes | The session UUID |
|
|
||||||
| `side` | string | Yes | Which side is polling: `offerer` or `answerer` |
|
|
||||||
|
|
||||||
### Response
|
|
||||||
|
|
||||||
**Content-Type:** `application/json`
|
|
||||||
|
|
||||||
**Success (200 OK):**
|
|
||||||
|
|
||||||
Response varies by side:
|
|
||||||
|
|
||||||
**For `side=offerer` (the offerer polls for response from answerer):**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"answer": "<SIGNALING_DATA>",
|
|
||||||
"answerCandidates": [
|
|
||||||
"<SIGNALING_DATA_1>",
|
|
||||||
"<SIGNALING_DATA_2>"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**For `side=answerer` (the answerer polls for offer from offerer):**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"offer": "<SIGNALING_DATA>",
|
|
||||||
"offerCandidates": [
|
|
||||||
"<SIGNALING_DATA_1>",
|
|
||||||
"<SIGNALING_DATA_2>"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notes:**
|
|
||||||
- `answer` will be `null` if the answerer hasn't responded yet
|
|
||||||
- Candidate arrays will be empty `[]` if no additional signaling data has been sent
|
|
||||||
- Use this endpoint for polling to check for new signaling data
|
|
||||||
- Origin header must match the session's origin
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
**Answerer polling for signaling data:**
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:3000/poll \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-H "Origin: https://example.com" \
|
|
||||||
-d '{
|
|
||||||
"code": "550e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"side": "answerer"
|
|
||||||
}'
|
|
||||||
|
|
||||||
# Response:
|
|
||||||
# {
|
|
||||||
# "offer": "<SIGNALING_DATA>",
|
|
||||||
# "offerCandidates": ["<SIGNALING_DATA>"]
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
**Offerer polling for response:**
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:3000/poll \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-H "Origin: https://example.com" \
|
|
||||||
-d '{
|
|
||||||
"code": "550e8400-e29b-41d4-a716-446655440000",
|
|
||||||
"side": "offerer"
|
|
||||||
}'
|
|
||||||
|
|
||||||
# Response:
|
|
||||||
# {
|
|
||||||
# "answer": "<SIGNALING_DATA>",
|
|
||||||
# "answerCandidates": ["<SIGNALING_DATA>"]
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## GET `/health`
|
|
||||||
|
|
||||||
Health check endpoint.
|
|
||||||
|
|
||||||
### Response
|
|
||||||
|
|
||||||
**Content-Type:** `application/json`
|
|
||||||
|
|
||||||
**Success (200 OK):**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"status": "ok",
|
|
||||||
"timestamp": 1699564800000
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Error Responses
|
|
||||||
|
|
||||||
All endpoints may return the following error responses:
|
|
||||||
|
|
||||||
**400 Bad Request:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "Missing or invalid required parameter: topic"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**404 Not Found:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "Session not found, expired, or origin mismatch"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**500 Internal Server Error:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "Internal server error"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Usage Flow
|
|
||||||
|
|
||||||
### Peer Discovery and Connection
|
|
||||||
|
|
||||||
1. **Check server version (optional):**
|
|
||||||
- GET `/` to see server version information
|
|
||||||
|
|
||||||
2. **Discover active topics:**
|
|
||||||
- GET `/topics` to see all topics and peer counts
|
|
||||||
- Optional: paginate through results with `?page=2&limit=100`
|
|
||||||
|
|
||||||
3. **Peer A announces availability:**
|
|
||||||
- POST `/:topic/offer` with peer identifier and signaling data
|
|
||||||
- Receives a unique session code
|
|
||||||
|
|
||||||
4. **Peer B discovers peers:**
|
|
||||||
- GET `/:topic/sessions` to list available sessions in a topic
|
|
||||||
- Filters out sessions with their own info to avoid self-connection
|
|
||||||
- Selects a peer to connect to
|
|
||||||
|
|
||||||
5. **Peer B initiates connection:**
|
|
||||||
- POST `/answer` with the session code and their signaling data
|
|
||||||
|
|
||||||
6. **Both peers exchange signaling information:**
|
|
||||||
- POST `/answer` with additional signaling data as needed
|
|
||||||
- POST `/poll` to retrieve signaling data from the other peer
|
|
||||||
|
|
||||||
7. **Peer connection established**
|
|
||||||
- Peers use exchanged signaling data to establish direct connection
|
|
||||||
- Session automatically expires after configured timeout
|
|
||||||
19
README.md
19
README.md
@@ -53,6 +53,17 @@ 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):**
|
||||||
|
```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
|
||||||
{
|
{
|
||||||
@@ -100,7 +111,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,
|
||||||
@@ -110,6 +122,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
|
||||||
@@ -129,7 +142,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)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -137,6 +151,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.2",
|
"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.2",
|
"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.2",
|
"version": "0.1.4",
|
||||||
"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": {
|
||||||
|
|||||||
46
src/app.ts
46
src/app.ts
@@ -64,11 +64,37 @@ 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
|
||||||
*/
|
*/
|
||||||
app.post('/register', async (c) => {
|
app.post('/register', async (c) => {
|
||||||
try {
|
try {
|
||||||
|
let peerId: string;
|
||||||
|
|
||||||
|
// 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
|
// Generate new peer ID
|
||||||
const peerId = generatePeerId();
|
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);
|
||||||
@@ -125,6 +151,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);
|
||||||
@@ -156,6 +192,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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +265,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
|
||||||
@@ -295,7 +333,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);
|
||||||
@@ -325,6 +364,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