mirror of
https://github.com/xtr-dev/rondevu-client.git
synced 2025-12-10 02:43:25 +00:00
Simplify client: remove topics, ID-based connections only
- Remove join(), listTopics(), listSessions() methods - Simplify to just create(id) and connect(id) - Remove topic-related types and interfaces - Add automatic version checking against server - Update README with simplified API - Client version: 0.3.2 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
82
README.md
82
README.md
@@ -1,8 +1,8 @@
|
|||||||
# Rondevu
|
# Rondevu
|
||||||
|
|
||||||
🎯 **Simple WebRTC peer signaling and discovery**
|
🎯 **Simple WebRTC peer signaling**
|
||||||
|
|
||||||
Meet peers by topic, by peer ID, or by connection ID.
|
Connect peers directly by ID with automatic WebRTC negotiation.
|
||||||
|
|
||||||
**Related repositories:**
|
**Related repositories:**
|
||||||
- [rondevu-server](https://github.com/xtr-dev/rondevu-server) - HTTP signaling server
|
- [rondevu-server](https://github.com/xtr-dev/rondevu-server) - HTTP signaling server
|
||||||
@@ -30,68 +30,57 @@ npm install @xtr-dev/rondevu-client
|
|||||||
import { Rondevu } from '@xtr-dev/rondevu-client';
|
import { Rondevu } from '@xtr-dev/rondevu-client';
|
||||||
|
|
||||||
const rdv = new Rondevu({
|
const rdv = new Rondevu({
|
||||||
baseUrl: 'https://server.com',
|
baseUrl: 'https://api.ronde.vu',
|
||||||
rtcConfig: {
|
rtcConfig: {
|
||||||
iceServers: [
|
iceServers: [
|
||||||
// your ICE servers here
|
|
||||||
{ urls: 'stun:stun.l.google.com:19302' },
|
{ urls: 'stun:stun.l.google.com:19302' },
|
||||||
{ urls: 'stun:stun1.l.google.com:19302' },
|
{ urls: 'stun:stun1.l.google.com:19302' }
|
||||||
{
|
|
||||||
urls: 'turn:relay1.example.com:3480',
|
|
||||||
username: 'example',
|
|
||||||
credential: 'example'
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Connect by topic
|
// Create a connection with custom ID
|
||||||
const conn = await rdv.join('room');
|
const connection = await rdv.create('my-room-123');
|
||||||
|
|
||||||
// Or connect by ID
|
// Or connect to an existing connection
|
||||||
const conn = await rdv.connect('meeting-123');
|
const connection = await rdv.connect('my-room-123');
|
||||||
|
|
||||||
// Use the connection
|
// Use data channels
|
||||||
conn.on('connect', () => {
|
connection.on('connect', () => {
|
||||||
const channel = conn.dataChannel('chat');
|
const channel = connection.dataChannel('chat');
|
||||||
channel.send('Hello!');
|
channel.send('Hello!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connection.on('datachannel', (channel) => {
|
||||||
|
if (channel.label === 'chat') {
|
||||||
|
channel.onmessage = (event) => {
|
||||||
|
console.log('Received:', event.data);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Node.js
|
#### Node.js
|
||||||
|
|
||||||
In Node.js, you need to provide a WebRTC polyfill since WebRTC APIs are not natively available:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install @roamhq/wrtc
|
|
||||||
# or
|
|
||||||
npm install wrtc
|
|
||||||
```
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Rondevu } from '@xtr-dev/rondevu-client';
|
import { Rondevu } from '@xtr-dev/rondevu-client';
|
||||||
import wrtc from '@roamhq/wrtc';
|
import wrtc from '@roamhq/wrtc';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
|
|
||||||
const rdv = new Rondevu({
|
const rdv = new Rondevu({
|
||||||
baseUrl: 'https://server.com',
|
baseUrl: 'https://api.ronde.vu',
|
||||||
fetch: fetch as any,
|
fetch: fetch as any,
|
||||||
wrtc: {
|
wrtc: {
|
||||||
RTCPeerConnection: wrtc.RTCPeerConnection,
|
RTCPeerConnection: wrtc.RTCPeerConnection,
|
||||||
RTCSessionDescription: wrtc.RTCSessionDescription,
|
RTCSessionDescription: wrtc.RTCSessionDescription,
|
||||||
RTCIceCandidate: wrtc.RTCIceCandidate,
|
RTCIceCandidate: wrtc.RTCIceCandidate,
|
||||||
},
|
|
||||||
rtcConfig: {
|
|
||||||
iceServers: [
|
|
||||||
{ urls: 'stun:stun.l.google.com:19302' }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Rest is the same as browser usage
|
const connection = await rdv.create('my-room-123');
|
||||||
const conn = await rdv.join('room');
|
|
||||||
conn.on('connect', () => {
|
connection.on('connect', () => {
|
||||||
const channel = conn.dataChannel('chat');
|
const channel = connection.dataChannel('chat');
|
||||||
channel.send('Hello from Node.js!');
|
channel.send('Hello from Node.js!');
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
@@ -99,23 +88,24 @@ conn.on('connect', () => {
|
|||||||
### API
|
### API
|
||||||
|
|
||||||
**Main Methods:**
|
**Main Methods:**
|
||||||
- `rdv.join(topic)` - Auto-connect to first peer in topic
|
- `rdv.create(id)` - Create connection with custom ID
|
||||||
- `rdv.join(topic, {filter})` - Connect to specific peer by ID
|
- `rdv.connect(id)` - Connect to existing connection by ID
|
||||||
- `rdv.create(id, topic)` - Create connection for others to join
|
|
||||||
- `rdv.connect(id)` - Join connection by ID
|
|
||||||
|
|
||||||
**Connection Events:**
|
**Connection Events:**
|
||||||
- `connect` - Connection established
|
- `connect` - Connection established
|
||||||
- `disconnect` - Connection closed
|
- `disconnect` - Connection closed
|
||||||
- `datachannel` - Remote peer created data channel
|
- `error` - Connection error
|
||||||
- `stream` - Remote media stream received
|
- `datachannel` - New data channel received
|
||||||
- `error` - Error occurred
|
- `stream` - Media stream received
|
||||||
|
|
||||||
**Connection Methods:**
|
**Connection Methods:**
|
||||||
- `conn.dataChannel(label)` - Get or create data channel
|
- `connection.dataChannel(label)` - Get or create data channel
|
||||||
- `conn.addStream(stream)` - Add media stream
|
- `connection.addStream(stream)` - Add media stream
|
||||||
- `conn.getPeerConnection()` - Get underlying RTCPeerConnection
|
- `connection.close()` - Close connection
|
||||||
- `conn.close()` - Close connection
|
|
||||||
|
### Version Compatibility
|
||||||
|
|
||||||
|
The client automatically checks server compatibility via the `/health` endpoint. If the server version is incompatible, an error will be thrown during initialization.
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@xtr-dev/rondevu-client",
|
"name": "@xtr-dev/rondevu-client",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"description": "TypeScript client for Rondevu peer signaling and discovery server",
|
"description": "TypeScript client for Rondevu peer signaling and discovery server",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
RondevuClientOptions,
|
RondevuClientOptions,
|
||||||
ListTopicsResponse,
|
|
||||||
ListSessionsResponse,
|
|
||||||
CreateOfferRequest,
|
CreateOfferRequest,
|
||||||
CreateOfferResponse,
|
CreateOfferResponse,
|
||||||
AnswerRequest,
|
AnswerRequest,
|
||||||
@@ -16,7 +14,7 @@ import {
|
|||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP API client for Rondevu peer signaling and discovery server
|
* HTTP API client for Rondevu peer signaling server
|
||||||
*/
|
*/
|
||||||
export class RondevuAPI {
|
export class RondevuAPI {
|
||||||
private readonly baseUrl: string;
|
private readonly baseUrl: string;
|
||||||
@@ -66,7 +64,7 @@ export class RondevuAPI {
|
|||||||
/**
|
/**
|
||||||
* Gets server version information
|
* Gets server version information
|
||||||
*
|
*
|
||||||
* @returns Server version (git commit hash)
|
* @returns Server version
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
@@ -82,82 +80,33 @@ export class RondevuAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists all topics with peer counts
|
* Creates a new offer
|
||||||
*
|
*
|
||||||
* @param page - Page number (starting from 1)
|
* @param request - Offer details including peer ID, signaling data, and optional custom code
|
||||||
* @param limit - Results per page (max 1000)
|
* @returns Unique offer code (UUID or custom code)
|
||||||
* @returns List of topics with pagination info
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* const api = new RondevuAPI({ baseUrl: 'https://example.com' });
|
* const api = new RondevuAPI({ baseUrl: 'https://example.com' });
|
||||||
* const { topics, pagination } = await api.listTopics();
|
* const { code } = await api.createOffer({
|
||||||
* console.log(`Found ${topics.length} topics`);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
async listTopics(page = 1, limit = 100): Promise<ListTopicsResponse> {
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
page: page.toString(),
|
|
||||||
limit: limit.toString(),
|
|
||||||
});
|
|
||||||
return this.request<ListTopicsResponse>(`/topics?${params}`, {
|
|
||||||
method: 'GET',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Discovers available peers for a given topic
|
|
||||||
*
|
|
||||||
* @param topic - Topic identifier
|
|
||||||
* @returns List of available sessions
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```typescript
|
|
||||||
* const api = new RondevuAPI({ baseUrl: 'https://example.com' });
|
|
||||||
* const { sessions } = await api.listSessions('my-room');
|
|
||||||
* const otherPeers = sessions.filter(s => s.peerId !== myPeerId);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
async listSessions(topic: string): Promise<ListSessionsResponse> {
|
|
||||||
return this.request<ListSessionsResponse>(`/${encodeURIComponent(topic)}/sessions`, {
|
|
||||||
method: 'GET',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Announces peer availability and creates a new session
|
|
||||||
*
|
|
||||||
* @param topic - Topic identifier for grouping peers (max 1024 characters)
|
|
||||||
* @param request - Offer details including peer ID and signaling data
|
|
||||||
* @returns Unique session code (UUID)
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```typescript
|
|
||||||
* const api = new RondevuAPI({ baseUrl: 'https://example.com' });
|
|
||||||
* const { code } = await api.createOffer('my-room', {
|
|
||||||
* peerId: 'peer-123',
|
* peerId: 'peer-123',
|
||||||
* offer: signalingData
|
* offer: signalingData,
|
||||||
|
* code: 'my-custom-code' // optional
|
||||||
* });
|
* });
|
||||||
* console.log('Session code:', code);
|
* console.log('Offer code:', code);
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
async createOffer(
|
async createOffer(request: CreateOfferRequest): Promise<CreateOfferResponse> {
|
||||||
topic: string,
|
return this.request<CreateOfferResponse>('/offer', {
|
||||||
request: CreateOfferRequest
|
|
||||||
): Promise<CreateOfferResponse> {
|
|
||||||
return this.request<CreateOfferResponse>(
|
|
||||||
`/${encodeURIComponent(topic)}/offer`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(request),
|
body: JSON.stringify(request),
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an answer or candidate to an existing session
|
* Sends an answer or candidate to an existing offer
|
||||||
*
|
*
|
||||||
* @param request - Answer details including session code and signaling data
|
* @param request - Answer details including offer code and signaling data
|
||||||
* @returns Success confirmation
|
* @returns Success confirmation
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
@@ -166,14 +115,14 @@ export class RondevuAPI {
|
|||||||
*
|
*
|
||||||
* // Send answer
|
* // Send answer
|
||||||
* await api.sendAnswer({
|
* await api.sendAnswer({
|
||||||
* code: sessionCode,
|
* code: offerCode,
|
||||||
* answer: answerData,
|
* answer: answerData,
|
||||||
* side: 'answerer'
|
* side: 'answerer'
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
* // Send candidate
|
* // Send candidate
|
||||||
* await api.sendAnswer({
|
* await api.sendAnswer({
|
||||||
* code: sessionCode,
|
* code: offerCode,
|
||||||
* candidate: candidateData,
|
* candidate: candidateData,
|
||||||
* side: 'offerer'
|
* side: 'offerer'
|
||||||
* });
|
* });
|
||||||
@@ -187,24 +136,24 @@ export class RondevuAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Polls for session data from the other peer
|
* Polls for offer data from the other peer
|
||||||
*
|
*
|
||||||
* @param code - Session UUID
|
* @param code - Offer code
|
||||||
* @param side - Which side is polling ('offerer' or 'answerer')
|
* @param side - Which side is polling ('offerer' or 'answerer')
|
||||||
* @returns Session data including offers, answers, and candidates
|
* @returns Offer data including offers, answers, and candidates
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* const api = new RondevuAPI({ baseUrl: 'https://example.com' });
|
* const api = new RondevuAPI({ baseUrl: 'https://example.com' });
|
||||||
*
|
*
|
||||||
* // Offerer polls for answer
|
* // Offerer polls for answer
|
||||||
* const offererData = await api.poll(sessionCode, 'offerer');
|
* const offererData = await api.poll(offerCode, 'offerer');
|
||||||
* if (offererData.answer) {
|
* if (offererData.answer) {
|
||||||
* console.log('Received answer:', offererData.answer);
|
* console.log('Received answer:', offererData.answer);
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* // Answerer polls for offer
|
* // Answerer polls for offer
|
||||||
* const answererData = await api.poll(sessionCode, 'answerer');
|
* const answererData = await api.poll(offerCode, 'answerer');
|
||||||
* console.log('Received offer:', answererData.offer);
|
* console.log('Received offer:', answererData.offer);
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
@@ -220,15 +169,16 @@ export class RondevuAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks server health
|
* Checks server health and version
|
||||||
*
|
*
|
||||||
* @returns Health status and timestamp
|
* @returns Health status, timestamp, and version
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* const api = new RondevuAPI({ baseUrl: 'https://example.com' });
|
* const api = new RondevuAPI({ baseUrl: 'https://example.com' });
|
||||||
* const health = await api.health();
|
* const health = await api.health();
|
||||||
* console.log('Server status:', health.status);
|
* console.log('Server status:', health.status);
|
||||||
|
* console.log('Server version:', health.version);
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
async health(): Promise<HealthResponse> {
|
async health(): Promise<HealthResponse> {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { RondevuConnectionParams, WebRTCPolyfill } from './types.js';
|
|||||||
*/
|
*/
|
||||||
export class RondevuConnection extends EventEmitter {
|
export class RondevuConnection extends EventEmitter {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly topic: string;
|
|
||||||
readonly role: 'offerer' | 'answerer';
|
readonly role: 'offerer' | 'answerer';
|
||||||
readonly remotePeerId: string;
|
readonly remotePeerId: string;
|
||||||
|
|
||||||
@@ -27,7 +26,6 @@ export class RondevuConnection extends EventEmitter {
|
|||||||
constructor(params: RondevuConnectionParams, client: RondevuAPI) {
|
constructor(params: RondevuConnectionParams, client: RondevuAPI) {
|
||||||
super();
|
super();
|
||||||
this.id = params.id;
|
this.id = params.id;
|
||||||
this.topic = params.topic;
|
|
||||||
this.role = params.role;
|
this.role = params.role;
|
||||||
this.pc = params.pc;
|
this.pc = params.pc;
|
||||||
this.localPeerId = params.localPeerId;
|
this.localPeerId = params.localPeerId;
|
||||||
|
|||||||
@@ -16,18 +16,12 @@ export { RondevuAPI } from './client.js';
|
|||||||
export type {
|
export type {
|
||||||
// WebRTC types
|
// WebRTC types
|
||||||
RondevuOptions,
|
RondevuOptions,
|
||||||
JoinOptions,
|
|
||||||
ConnectionRole,
|
ConnectionRole,
|
||||||
RondevuConnectionParams,
|
RondevuConnectionParams,
|
||||||
RondevuConnectionEvents,
|
RondevuConnectionEvents,
|
||||||
WebRTCPolyfill,
|
WebRTCPolyfill,
|
||||||
// Signaling types
|
// Signaling types
|
||||||
Side,
|
Side,
|
||||||
Session,
|
|
||||||
TopicInfo,
|
|
||||||
Pagination,
|
|
||||||
ListTopicsResponse,
|
|
||||||
ListSessionsResponse,
|
|
||||||
CreateOfferRequest,
|
CreateOfferRequest,
|
||||||
CreateOfferResponse,
|
CreateOfferResponse,
|
||||||
AnswerRequest,
|
AnswerRequest,
|
||||||
|
|||||||
146
src/rondevu.ts
146
src/rondevu.ts
@@ -1,6 +1,6 @@
|
|||||||
import { RondevuAPI } from './client.js';
|
import { RondevuAPI } from './client.js';
|
||||||
import { RondevuConnection } from './connection.js';
|
import { RondevuConnection } from './connection.js';
|
||||||
import { RondevuOptions, JoinOptions, RondevuConnectionParams, WebRTCPolyfill } from './types.js';
|
import { RondevuOptions, RondevuConnectionParams, WebRTCPolyfill } from './types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main Rondevu WebRTC client with automatic connection management
|
* Main Rondevu WebRTC client with automatic connection management
|
||||||
@@ -49,6 +49,43 @@ export class Rondevu {
|
|||||||
'Install: npm install @roamhq/wrtc or npm install wrtc'
|
'Install: npm install @roamhq/wrtc or npm install wrtc'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check server version compatibility (async, don't block constructor)
|
||||||
|
this.checkServerVersion().catch(() => {
|
||||||
|
// Silently fail version check - connection will work even if version check fails
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check server version compatibility
|
||||||
|
*/
|
||||||
|
private async checkServerVersion(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const { version: serverVersion } = await this.api.health();
|
||||||
|
const clientVersion = '0.3.2'; // Should match package.json
|
||||||
|
|
||||||
|
if (!this.isVersionCompatible(clientVersion, serverVersion)) {
|
||||||
|
console.warn(
|
||||||
|
`[Rondevu] Version mismatch: client v${clientVersion}, server v${serverVersion}. ` +
|
||||||
|
'This may cause compatibility issues.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Version check failed - server might not support /health endpoint
|
||||||
|
console.debug('[Rondevu] Could not check server version');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if client and server versions are compatible
|
||||||
|
* For now, just check major version compatibility
|
||||||
|
*/
|
||||||
|
private isVersionCompatible(clientVersion: string, serverVersion: string): boolean {
|
||||||
|
const clientMajor = parseInt(clientVersion.split('.')[0]);
|
||||||
|
const serverMajor = parseInt(serverVersion.split('.')[0]);
|
||||||
|
|
||||||
|
// Major versions must match
|
||||||
|
return clientMajor === serverMajor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,11 +104,10 @@ export class Rondevu {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new connection (offerer role)
|
* Create a new connection (offerer role)
|
||||||
* @param id - Connection identifier
|
* @param id - Connection identifier (custom code)
|
||||||
* @param topic - Topic name for grouping connections
|
|
||||||
* @returns Promise that resolves to RondevuConnection
|
* @returns Promise that resolves to RondevuConnection
|
||||||
*/
|
*/
|
||||||
async create(id: string, topic: string): Promise<RondevuConnection> {
|
async create(id: string): Promise<RondevuConnection> {
|
||||||
// Create peer connection
|
// Create peer connection
|
||||||
const pc = new this.RTCPeerConnection(this.rtcConfig);
|
const pc = new this.RTCPeerConnection(this.rtcConfig);
|
||||||
|
|
||||||
@@ -85,8 +121,8 @@ export class Rondevu {
|
|||||||
// Wait for ICE gathering to complete
|
// Wait for ICE gathering to complete
|
||||||
await this.waitForIceGathering(pc);
|
await this.waitForIceGathering(pc);
|
||||||
|
|
||||||
// Create session on server with custom code
|
// Create offer on server with custom code
|
||||||
await this.api.createOffer(topic, {
|
await this.api.createOffer({
|
||||||
peerId: this.peerId,
|
peerId: this.peerId,
|
||||||
offer: pc.localDescription!.sdp,
|
offer: pc.localDescription!.sdp,
|
||||||
code: id,
|
code: id,
|
||||||
@@ -95,7 +131,6 @@ export class Rondevu {
|
|||||||
// Create connection object
|
// Create connection object
|
||||||
const connectionParams: RondevuConnectionParams = {
|
const connectionParams: RondevuConnectionParams = {
|
||||||
id,
|
id,
|
||||||
topic,
|
|
||||||
role: 'offerer',
|
role: 'offerer',
|
||||||
pc,
|
pc,
|
||||||
localPeerId: this.peerId,
|
localPeerId: this.peerId,
|
||||||
@@ -114,16 +149,16 @@ export class Rondevu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect to an existing connection by ID (answerer role)
|
* Connect to an existing offer by ID (answerer role)
|
||||||
* @param id - Connection identifier
|
* @param id - Offer code
|
||||||
* @returns Promise that resolves to RondevuConnection
|
* @returns Promise that resolves to RondevuConnection
|
||||||
*/
|
*/
|
||||||
async connect(id: string): Promise<RondevuConnection> {
|
async connect(id: string): Promise<RondevuConnection> {
|
||||||
// Poll server to get session by ID
|
// Poll server to get offer by ID
|
||||||
const sessionData = await this.findSessionByIdWithClient(id, this.api);
|
const offerData = await this.findOfferById(id);
|
||||||
|
|
||||||
if (!sessionData) {
|
if (!offerData) {
|
||||||
throw new Error(`Connection ${id} not found or expired`);
|
throw new Error(`Offer ${id} not found or expired`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create peer connection
|
// Create peer connection
|
||||||
@@ -132,7 +167,7 @@ export class Rondevu {
|
|||||||
// Set remote offer
|
// Set remote offer
|
||||||
await pc.setRemoteDescription({
|
await pc.setRemoteDescription({
|
||||||
type: 'offer',
|
type: 'offer',
|
||||||
sdp: sessionData.offer,
|
sdp: offerData.offer,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Generate answer
|
// Generate answer
|
||||||
@@ -152,11 +187,10 @@ export class Rondevu {
|
|||||||
// Create connection object
|
// Create connection object
|
||||||
const connectionParams: RondevuConnectionParams = {
|
const connectionParams: RondevuConnectionParams = {
|
||||||
id,
|
id,
|
||||||
topic: sessionData.topic || 'unknown',
|
|
||||||
role: 'answerer',
|
role: 'answerer',
|
||||||
pc,
|
pc,
|
||||||
localPeerId: this.peerId,
|
localPeerId: this.peerId,
|
||||||
remotePeerId: sessionData.peerId,
|
remotePeerId: '', // Will be determined from peerId in offer
|
||||||
pollingInterval: this.pollingInterval,
|
pollingInterval: this.pollingInterval,
|
||||||
connectionTimeout: this.connectionTimeout,
|
connectionTimeout: this.connectionTimeout,
|
||||||
wrtc: this.wrtc,
|
wrtc: this.wrtc,
|
||||||
@@ -170,65 +204,6 @@ export class Rondevu {
|
|||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Join a topic and discover available peers (answerer role)
|
|
||||||
* @param topic - Topic name
|
|
||||||
* @param options - Optional join options for filtering and selection
|
|
||||||
* @returns Promise that resolves to RondevuConnection
|
|
||||||
*/
|
|
||||||
async join(topic: string, options?: JoinOptions): Promise<RondevuConnection> {
|
|
||||||
// List sessions in topic
|
|
||||||
const { sessions } = await this.api.listSessions(topic);
|
|
||||||
|
|
||||||
// Filter out self (sessions with our peer ID)
|
|
||||||
let availableSessions = sessions.filter(
|
|
||||||
session => session.peerId !== this.peerId
|
|
||||||
);
|
|
||||||
|
|
||||||
// Apply custom filter if provided
|
|
||||||
if (options?.filter) {
|
|
||||||
availableSessions = availableSessions.filter(options.filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (availableSessions.length === 0) {
|
|
||||||
throw new Error(`No available peers in topic: ${topic}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select session based on strategy
|
|
||||||
const selectedSession = this.selectSession(
|
|
||||||
availableSessions,
|
|
||||||
options?.select || 'first'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Connect to selected session
|
|
||||||
return this.connect(selectedSession.code);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select a session based on strategy
|
|
||||||
*/
|
|
||||||
private selectSession(
|
|
||||||
sessions: Array<{ code: string; peerId: string; createdAt: number }>,
|
|
||||||
strategy: 'first' | 'newest' | 'oldest' | 'random'
|
|
||||||
): { code: string; peerId: string; createdAt: number } {
|
|
||||||
switch (strategy) {
|
|
||||||
case 'first':
|
|
||||||
return sessions[0];
|
|
||||||
case 'newest':
|
|
||||||
return sessions.reduce((newest, session) =>
|
|
||||||
session.createdAt > newest.createdAt ? session : newest
|
|
||||||
);
|
|
||||||
case 'oldest':
|
|
||||||
return sessions.reduce((oldest, session) =>
|
|
||||||
session.createdAt < oldest.createdAt ? session : oldest
|
|
||||||
);
|
|
||||||
case 'random':
|
|
||||||
return sessions[Math.floor(Math.random() * sessions.length)];
|
|
||||||
default:
|
|
||||||
return sessions[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait for ICE gathering to complete
|
* Wait for ICE gathering to complete
|
||||||
*/
|
*/
|
||||||
@@ -256,36 +231,25 @@ export class Rondevu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a session by connection ID
|
* Find an offer by code
|
||||||
* This requires polling since we don't know which topic it's in
|
|
||||||
*/
|
*/
|
||||||
private async findSessionByIdWithClient(
|
private async findOfferById(id: string): Promise<{
|
||||||
id: string,
|
|
||||||
client: RondevuAPI
|
|
||||||
): Promise<{
|
|
||||||
code: string;
|
|
||||||
peerId: string;
|
|
||||||
offer: string;
|
offer: string;
|
||||||
topic?: string;
|
|
||||||
} | null> {
|
} | null> {
|
||||||
try {
|
try {
|
||||||
// Try to poll for the session directly
|
// Poll for the offer directly
|
||||||
// The poll endpoint should return the session data
|
const response = await this.api.poll(id, 'answerer');
|
||||||
const response = await client.poll(id, 'answerer');
|
|
||||||
const answererResponse = response as { offer: string; offerCandidates: string[] };
|
const answererResponse = response as { offer: string; offerCandidates: string[] };
|
||||||
|
|
||||||
if (answererResponse.offer) {
|
if (answererResponse.offer) {
|
||||||
return {
|
return {
|
||||||
code: id,
|
|
||||||
peerId: '', // Will be populated from session data
|
|
||||||
offer: answererResponse.offer,
|
offer: answererResponse.offer,
|
||||||
topic: undefined,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(`Failed to find session ${id}: ${(err as Error).message}`);
|
throw new Error(`Failed to find offer ${id}: ${(err as Error).message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
74
src/types.ts
74
src/types.ts
@@ -8,64 +8,7 @@
|
|||||||
export type Side = 'offerer' | 'answerer';
|
export type Side = 'offerer' | 'answerer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Session information returned from discovery endpoints
|
* Request body for POST /offer
|
||||||
*/
|
|
||||||
export interface Session {
|
|
||||||
/** Unique session identifier (UUID) */
|
|
||||||
code: string;
|
|
||||||
/** Peer identifier/metadata */
|
|
||||||
peerId: string;
|
|
||||||
/** Signaling data for peer connection */
|
|
||||||
offer: string;
|
|
||||||
/** Additional signaling data from offerer */
|
|
||||||
offerCandidates: string[];
|
|
||||||
/** Unix timestamp when session was created */
|
|
||||||
createdAt: number;
|
|
||||||
/** Unix timestamp when session expires */
|
|
||||||
expiresAt: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Topic information with peer count
|
|
||||||
*/
|
|
||||||
export interface TopicInfo {
|
|
||||||
/** Topic identifier */
|
|
||||||
topic: string;
|
|
||||||
/** Number of available peers in this topic */
|
|
||||||
count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pagination information
|
|
||||||
*/
|
|
||||||
export interface Pagination {
|
|
||||||
/** Current page number */
|
|
||||||
page: number;
|
|
||||||
/** Results per page */
|
|
||||||
limit: number;
|
|
||||||
/** Total number of results */
|
|
||||||
total: number;
|
|
||||||
/** Whether there are more results available */
|
|
||||||
hasMore: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response from GET / - list all topics
|
|
||||||
*/
|
|
||||||
export interface ListTopicsResponse {
|
|
||||||
topics: TopicInfo[];
|
|
||||||
pagination: Pagination;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response from GET /:topic/sessions - list sessions in a topic
|
|
||||||
*/
|
|
||||||
export interface ListSessionsResponse {
|
|
||||||
sessions: Session[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request body for POST /:topic/offer
|
|
||||||
*/
|
*/
|
||||||
export interface CreateOfferRequest {
|
export interface CreateOfferRequest {
|
||||||
/** Peer identifier/metadata (max 1024 characters) */
|
/** Peer identifier/metadata (max 1024 characters) */
|
||||||
@@ -77,7 +20,7 @@ export interface CreateOfferRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response from POST /:topic/offer
|
* Response from POST /offer
|
||||||
*/
|
*/
|
||||||
export interface CreateOfferResponse {
|
export interface CreateOfferResponse {
|
||||||
/** Unique session identifier (UUID) */
|
/** Unique session identifier (UUID) */
|
||||||
@@ -154,6 +97,7 @@ export interface VersionResponse {
|
|||||||
export interface HealthResponse {
|
export interface HealthResponse {
|
||||||
status: 'ok';
|
status: 'ok';
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -206,16 +150,6 @@ export interface RondevuOptions {
|
|||||||
wrtc?: WebRTCPolyfill;
|
wrtc?: WebRTCPolyfill;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Options for joining a topic
|
|
||||||
*/
|
|
||||||
export interface JoinOptions {
|
|
||||||
/** Filter function to select specific sessions */
|
|
||||||
filter?: (session: { code: string; peerId: string }) => boolean;
|
|
||||||
/** Selection strategy for choosing a session */
|
|
||||||
select?: 'first' | 'newest' | 'oldest' | 'random';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connection role - whether this peer is creating or answering
|
* Connection role - whether this peer is creating or answering
|
||||||
*/
|
*/
|
||||||
@@ -226,7 +160,7 @@ export type ConnectionRole = 'offerer' | 'answerer';
|
|||||||
*/
|
*/
|
||||||
export interface RondevuConnectionParams {
|
export interface RondevuConnectionParams {
|
||||||
id: string;
|
id: string;
|
||||||
topic: string;
|
topic?: string;
|
||||||
role: ConnectionRole;
|
role: ConnectionRole;
|
||||||
pc: RTCPeerConnection;
|
pc: RTCPeerConnection;
|
||||||
localPeerId: string;
|
localPeerId: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user