diff --git a/README.md b/README.md index 6d903f4..65f785b 100644 --- a/README.md +++ b/README.md @@ -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) - **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 - **Multi-Offer Management**: Create and manage multiple offers per peer - **Complete WebRTC Signaling**: Full offer/answer and ICE candidate exchange @@ -68,7 +69,8 @@ peer.on('datachannel', (channel) => { // Create offer and advertise on topics const offerId = await peer.createOffer({ 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); @@ -121,11 +123,50 @@ if (offers.length > 0) { // Answer the offer 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 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([{ sdp: 'v=0...', // Your WebRTC offer SDP 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 @@ -433,7 +475,8 @@ const offers = await client.offers.create([ { sdp: 'v=0...', 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); ``` -#### `client.offers.answer(offerId, sdp)` +#### `client.offers.answer(offerId, sdp, secret?)` Answer an offer (locks it to answerer). ```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()` Poll for answers to your offers. diff --git a/package-lock.json b/package-lock.json index cf10c59..d8b0b1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@xtr-dev/rondevu-client", - "version": "0.7.3", + "version": "0.7.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@xtr-dev/rondevu-client", - "version": "0.7.3", + "version": "0.7.6", "license": "MIT", "dependencies": { "@xtr-dev/rondevu-client": "^0.5.1" diff --git a/package.json b/package.json index fe3c50b..8f5a9d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "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", "type": "module", "main": "dist/index.js", diff --git a/src/offers.ts b/src/offers.ts index 6edeec2..1480947 100644 --- a/src/offers.ts +++ b/src/offers.ts @@ -8,6 +8,7 @@ export interface CreateOfferRequest { sdp: string; topics: string[]; ttl?: number; + secret?: string; } export interface Offer { @@ -18,6 +19,8 @@ export interface Offer { createdAt?: number; expiresAt: number; lastSeen: number; + secret?: string; + hasSecret?: boolean; answererPeerId?: string; answerSdp?: string; answeredAt?: number; @@ -221,14 +224,14 @@ export class RondevuOffers { /** * Answer an offer */ - async answer(offerId: string, sdp: string): Promise { + async answer(offerId: string, sdp: string, secret?: string): Promise { const response = await this.fetchFn(`${this.baseUrl}/offers/${encodeURIComponent(offerId)}/answer`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: RondevuAuth.createAuthHeader(this.credentials), }, - body: JSON.stringify({ sdp }), + body: JSON.stringify({ sdp, secret }), }); if (!response.ok) {