Update README with secret field documentation

- Document secret parameter in offer creation examples
- Add Protected Offers section with detailed usage
- Update API reference for create() and answer() methods
- Show hasSecret flag in discovery responses

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-16 22:03:49 +01:00
parent e052464482
commit 3530213870
4 changed files with 62 additions and 11 deletions

View File

@@ -16,6 +16,7 @@ TypeScript/JavaScript client for Rondevu, providing topic-based peer discovery,
- **Topic-Based Discovery**: Find peers by topics (e.g., torrent infohashes) - **Topic-Based Discovery**: Find peers by topics (e.g., torrent infohashes)
- **Stateless Authentication**: No server-side sessions, portable credentials - **Stateless Authentication**: No server-side sessions, portable credentials
- **Protected Connections**: Optional secret-protected offers for access control
- **Bloom Filters**: Efficient peer exclusion for repeated discoveries - **Bloom Filters**: Efficient peer exclusion for repeated discoveries
- **Multi-Offer Management**: Create and manage multiple offers per peer - **Multi-Offer Management**: Create and manage multiple offers per peer
- **Complete WebRTC Signaling**: Full offer/answer and ICE candidate exchange - **Complete WebRTC Signaling**: Full offer/answer and ICE candidate exchange
@@ -68,7 +69,8 @@ peer.on('datachannel', (channel) => {
// Create offer and advertise on topics // Create offer and advertise on topics
const offerId = await peer.createOffer({ const offerId = await peer.createOffer({
topics: ['my-app', 'room-123'], topics: ['my-app', 'room-123'],
ttl: 300000 // 5 minutes ttl: 300000, // 5 minutes
secret: 'my-secret-password' // Optional: protect offer (max 128 chars)
}); });
console.log('Offer created:', offerId); console.log('Offer created:', offerId);
@@ -121,11 +123,50 @@ if (offers.length > 0) {
// Answer the offer // Answer the offer
await peer.answer(offer.id, offer.sdp, { await peer.answer(offer.id, offer.sdp, {
topics: offer.topics topics: offer.topics,
secret: 'my-secret-password' // Required if offer.hasSecret is true
}); });
} }
``` ```
## Protected Offers
You can protect offers with a secret to control who can answer them. This is useful for private rooms or invite-only connections.
### Creating a Protected Offer
```typescript
const offerId = await peer.createOffer({
topics: ['private-room'],
secret: 'my-secret-password' // Max 128 characters
});
// Share the secret with authorized peers through a secure channel
```
### Answering a Protected Offer
```typescript
const offers = await client.offers.findByTopic('private-room');
// Check if offer requires a secret
if (offers[0].hasSecret) {
console.log('This offer requires a secret');
}
// Provide the secret when answering
await peer.answer(offers[0].id, offers[0].sdp, {
topics: offers[0].topics,
secret: 'my-secret-password' // Must match the offer's secret
});
```
**Notes:**
- The actual secret is never exposed in public API responses - only a `hasSecret` boolean flag
- Answerers must provide the correct secret, or the answer will be rejected
- Secrets are limited to 128 characters
- Use this for access control, not for cryptographic security (use end-to-end encryption for that)
## Connection Lifecycle ## Connection Lifecycle
The `RondevuPeer` uses a state machine for connection management: The `RondevuPeer` uses a state machine for connection management:
@@ -368,7 +409,8 @@ localStorage.setItem('rondevu-creds', JSON.stringify(creds));
const offers = await client.offers.create([{ const offers = await client.offers.create([{
sdp: 'v=0...', // Your WebRTC offer SDP sdp: 'v=0...', // Your WebRTC offer SDP
topics: ['movie-xyz', 'hd-content'], topics: ['movie-xyz', 'hd-content'],
ttl: 300000 // 5 minutes ttl: 300000, // 5 minutes
secret: 'my-secret-password' // Optional: protect offer (max 128 chars)
}]); }]);
// Discover peers by topic // Discover peers by topic
@@ -433,7 +475,8 @@ const offers = await client.offers.create([
{ {
sdp: 'v=0...', sdp: 'v=0...',
topics: ['topic-1', 'topic-2'], topics: ['topic-1', 'topic-2'],
ttl: 300000 // optional, default 5 minutes ttl: 300000, // optional, default 5 minutes
secret: 'my-secret-password' // optional, max 128 chars
} }
]); ]);
``` ```
@@ -462,13 +505,18 @@ Delete a specific offer.
await client.offers.delete(offerId); await client.offers.delete(offerId);
``` ```
#### `client.offers.answer(offerId, sdp)` #### `client.offers.answer(offerId, sdp, secret?)`
Answer an offer (locks it to answerer). Answer an offer (locks it to answerer).
```typescript ```typescript
await client.offers.answer(offerId, answerSdp); await client.offers.answer(offerId, answerSdp, 'my-secret-password');
``` ```
**Parameters:**
- `offerId`: The offer ID to answer
- `sdp`: The WebRTC answer SDP
- `secret` (optional): Required if the offer has `hasSecret: true`
#### `client.offers.getAnswers()` #### `client.offers.getAnswers()`
Poll for answers to your offers. Poll for answers to your offers.

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@xtr-dev/rondevu-client", "name": "@xtr-dev/rondevu-client",
"version": "0.7.3", "version": "0.7.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@xtr-dev/rondevu-client", "name": "@xtr-dev/rondevu-client",
"version": "0.7.3", "version": "0.7.6",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@xtr-dev/rondevu-client": "^0.5.1" "@xtr-dev/rondevu-client": "^0.5.1"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@xtr-dev/rondevu-client", "name": "@xtr-dev/rondevu-client",
"version": "0.7.5", "version": "0.7.6",
"description": "TypeScript client for Rondevu topic-based peer discovery and signaling server", "description": "TypeScript client for Rondevu topic-based peer discovery and signaling server",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",

View File

@@ -8,6 +8,7 @@ export interface CreateOfferRequest {
sdp: string; sdp: string;
topics: string[]; topics: string[];
ttl?: number; ttl?: number;
secret?: string;
} }
export interface Offer { export interface Offer {
@@ -18,6 +19,8 @@ export interface Offer {
createdAt?: number; createdAt?: number;
expiresAt: number; expiresAt: number;
lastSeen: number; lastSeen: number;
secret?: string;
hasSecret?: boolean;
answererPeerId?: string; answererPeerId?: string;
answerSdp?: string; answerSdp?: string;
answeredAt?: number; answeredAt?: number;
@@ -221,14 +224,14 @@ export class RondevuOffers {
/** /**
* Answer an offer * Answer an offer
*/ */
async answer(offerId: string, sdp: string): Promise<void> { async answer(offerId: string, sdp: string, secret?: string): Promise<void> {
const response = await this.fetchFn(`${this.baseUrl}/offers/${encodeURIComponent(offerId)}/answer`, { const response = await this.fetchFn(`${this.baseUrl}/offers/${encodeURIComponent(offerId)}/answer`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authorization: RondevuAuth.createAuthHeader(this.credentials), Authorization: RondevuAuth.createAuthHeader(this.credentials),
}, },
body: JSON.stringify({ sdp }), body: JSON.stringify({ sdp, secret }),
}); });
if (!response.ok) { if (!response.ok) {