@xtr-dev/rondevu-client 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,234 +1,60 @@
1
- # @xtr-dev/rondevu-client
1
+ # Rondevu
2
2
 
3
- TypeScript client for interacting with the Rondevu peer signaling and discovery server. Provides a simple, type-safe API for WebRTC peer discovery and connection establishment.
3
+ 🎯 Meet WebRTC peers by topic, by peer ID, or by connection ID.
4
4
 
5
- ## Installation
5
+ ## @xtr-dev/rondevu-client
6
6
 
7
- ```bash
8
- npm install @xtr-dev/rondevu-client
9
- ```
10
-
11
- ## Usage
12
-
13
- ### Basic Setup
14
-
15
- ```typescript
16
- import { RondevuClient } from '@xtr-dev/rondevu-client';
17
-
18
- const client = new RondevuClient({
19
- baseUrl: 'https://rondevu.example.com',
20
- // Optional: custom origin for session isolation
21
- origin: 'https://myapp.com'
22
- });
23
- ```
24
-
25
- ### Peer Discovery Flow
26
-
27
- #### 1. List Available Topics
28
-
29
- ```typescript
30
- // Get all topics with peer counts
31
- const { topics, pagination } = await client.listTopics();
32
-
33
- topics.forEach(topic => {
34
- console.log(`${topic.topic}: ${topic.count} peers available`);
35
- });
36
- ```
37
-
38
- #### 2. Create an Offer (Peer A)
39
-
40
- ```typescript
41
- // Announce availability in a topic
42
- const { code } = await client.createOffer('my-room', {
43
- info: 'peer-A-unique-id',
44
- offer: webrtcOfferData
45
- });
46
-
47
- console.log('Session code:', code);
48
- ```
7
+ [![npm version](https://img.shields.io/npm/v/@xtr-dev/rondevu-client)](https://www.npmjs.com/package/@xtr-dev/rondevu-client)
49
8
 
50
- #### 3. Discover Peers (Peer B)
51
-
52
- ```typescript
53
- // Find available peers in a topic
54
- const { sessions } = await client.listSessions('my-room');
9
+ TypeScript Rondevu HTTP and WebRTC client, for simple peer discovery and connection.
55
10
 
56
- // Filter out your own sessions
57
- const otherPeers = sessions.filter(s => s.info !== 'my-peer-id');
11
+ ### Install
58
12
 
59
- if (otherPeers.length > 0) {
60
- const peer = otherPeers[0];
61
- console.log('Found peer:', peer.info);
62
- }
13
+ ```bash
14
+ npm install @xtr-dev/rondevu-client
63
15
  ```
64
16
 
65
- #### 4. Send Answer (Peer B)
17
+ ### Usage
66
18
 
67
19
  ```typescript
68
- // Connect to a peer by answering their offer
69
- await client.sendAnswer({
70
- code: peer.code,
71
- answer: webrtcAnswerData,
72
- side: 'answerer'
73
- });
74
- ```
20
+ import { Rondevu } from '@xtr-dev/rondevu-client';
75
21
 
76
- #### 5. Poll for Data (Both Peers)
22
+ const rdv = new Rondevu({ baseUrl: 'https://server.com' });
77
23
 
78
- ```typescript
79
- // Offerer polls for answer
80
- const offererData = await client.poll(code, 'offerer');
81
- if (offererData.answer) {
82
- console.log('Received answer from peer');
83
- }
84
-
85
- // Answerer polls for offer details
86
- const answererData = await client.poll(code, 'answerer');
87
- console.log('Offer candidates:', answererData.offerCandidates);
88
- ```
24
+ // Connect by topic
25
+ const conn = await rdv.join('room');
89
26
 
90
- #### 6. Exchange ICE Candidates
27
+ // Or connect by ID
28
+ const conn = await rdv.connect('meeting-123');
91
29
 
92
- ```typescript
93
- // Send additional signaling data
94
- await client.sendAnswer({
95
- code: sessionCode,
96
- candidate: iceCandidate,
97
- side: 'offerer' // or 'answerer'
30
+ // Use the connection
31
+ conn.on('connect', () => {
32
+ const channel = conn.dataChannel('chat');
33
+ channel.send('Hello!');
98
34
  });
99
35
  ```
100
36
 
101
- ### Health Check
102
-
103
- ```typescript
104
- const health = await client.health();
105
- console.log('Server status:', health.status);
106
- console.log('Timestamp:', health.timestamp);
107
- ```
108
-
109
- ## API Reference
110
-
111
- ### `RondevuClient`
112
-
113
- #### Constructor
114
-
115
- ```typescript
116
- new RondevuClient(options: RondevuClientOptions)
117
- ```
118
-
119
- **Options:**
120
- - `baseUrl` (string, required): Base URL of the Rondevu server
121
- - `origin` (string, optional): Origin header for session isolation (defaults to baseUrl origin)
122
- - `fetch` (function, optional): Custom fetch implementation (for Node.js)
123
-
124
- #### Methods
125
-
126
- ##### `listTopics(page?, limit?)`
127
-
128
- Lists all topics with peer counts.
129
-
130
- **Parameters:**
131
- - `page` (number, optional): Page number, default 1
132
- - `limit` (number, optional): Results per page, default 100, max 1000
133
-
134
- **Returns:** `Promise<ListTopicsResponse>`
135
-
136
- ##### `listSessions(topic)`
137
-
138
- Discovers available peers for a given topic.
139
-
140
- **Parameters:**
141
- - `topic` (string): Topic identifier
142
-
143
- **Returns:** `Promise<ListSessionsResponse>`
144
-
145
- ##### `createOffer(topic, request)`
146
-
147
- Announces peer availability and creates a new session.
148
-
149
- **Parameters:**
150
- - `topic` (string): Topic identifier (max 256 characters)
151
- - `request` (CreateOfferRequest):
152
- - `info` (string): Peer identifier/metadata (max 1024 characters)
153
- - `offer` (string): WebRTC signaling data
154
-
155
- **Returns:** `Promise<CreateOfferResponse>`
156
-
157
- ##### `sendAnswer(request)`
158
-
159
- Sends an answer or candidate to an existing session.
160
-
161
- **Parameters:**
162
- - `request` (AnswerRequest):
163
- - `code` (string): Session UUID
164
- - `answer` (string, optional): Answer signaling data
165
- - `candidate` (string, optional): ICE candidate data
166
- - `side` ('offerer' | 'answerer'): Which peer is sending
167
-
168
- **Returns:** `Promise<AnswerResponse>`
169
-
170
- ##### `poll(code, side)`
171
-
172
- Polls for session data from the other peer.
173
-
174
- **Parameters:**
175
- - `code` (string): Session UUID
176
- - `side` ('offerer' | 'answerer'): Which side is polling
177
-
178
- **Returns:** `Promise<PollOffererResponse | PollAnswererResponse>`
179
-
180
- ##### `health()`
181
-
182
- Checks server health.
183
-
184
- **Returns:** `Promise<HealthResponse>`
185
-
186
- ## TypeScript Types
187
-
188
- All types are exported from the main package:
189
-
190
- ```typescript
191
- import {
192
- RondevuClient,
193
- Session,
194
- TopicInfo,
195
- CreateOfferRequest,
196
- AnswerRequest,
197
- PollRequest,
198
- Side,
199
- // ... and more
200
- } from '@xtr-dev/rondevu-client';
201
- ```
202
-
203
- ## Node.js Usage
204
-
205
- For Node.js environments (v18+), the built-in fetch is used automatically. For older Node.js versions, provide a fetch implementation:
206
-
207
- ```typescript
208
- import fetch from 'node-fetch';
209
- import { RondevuClient } from '@xtr-dev/rondevu-client';
210
-
211
- const client = new RondevuClient({
212
- baseUrl: 'https://rondevu.example.com',
213
- fetch: fetch as any
214
- });
215
- ```
37
+ ### API
216
38
 
217
- ## Error Handling
39
+ **Main Methods:**
40
+ - `rdv.join(topic)` - Auto-connect to first peer in topic
41
+ - `rdv.join(topic, {filter})` - Connect to specific peer by ID
42
+ - `rdv.create(id, topic)` - Create connection for others to join
43
+ - `rdv.connect(id)` - Join connection by ID
218
44
 
219
- All API methods throw errors with descriptive messages:
45
+ **Connection Events:**
46
+ - `connect` - Connection established
47
+ - `disconnect` - Connection closed
48
+ - `datachannel` - Remote peer created data channel
49
+ - `stream` - Remote media stream received
50
+ - `error` - Error occurred
220
51
 
221
- ```typescript
222
- try {
223
- await client.createOffer('my-room', {
224
- info: 'peer-id',
225
- offer: data
226
- });
227
- } catch (error) {
228
- console.error('Failed to create offer:', error.message);
229
- }
230
- ```
52
+ **Connection Methods:**
53
+ - `conn.dataChannel(label)` - Get or create data channel
54
+ - `conn.addStream(stream)` - Add media stream
55
+ - `conn.getPeerConnection()` - Get underlying RTCPeerConnection
56
+ - `conn.close()` - Close connection
231
57
 
232
- ## License
58
+ ### License
233
59
 
234
60
  MIT
package/dist/client.d.ts CHANGED
@@ -40,22 +40,22 @@ export declare class RondevuClient {
40
40
  * ```typescript
41
41
  * const client = new RondevuClient({ baseUrl: 'https://example.com' });
42
42
  * const { sessions } = await client.listSessions('my-room');
43
- * const otherPeers = sessions.filter(s => s.info !== myPeerId);
43
+ * const otherPeers = sessions.filter(s => s.peerId !== myPeerId);
44
44
  * ```
45
45
  */
46
46
  listSessions(topic: string): Promise<ListSessionsResponse>;
47
47
  /**
48
48
  * Announces peer availability and creates a new session
49
49
  *
50
- * @param topic - Topic identifier for grouping peers (max 256 characters)
51
- * @param request - Offer details including peer info and signaling data
50
+ * @param topic - Topic identifier for grouping peers (max 1024 characters)
51
+ * @param request - Offer details including peer ID and signaling data
52
52
  * @returns Unique session code (UUID)
53
53
  *
54
54
  * @example
55
55
  * ```typescript
56
56
  * const client = new RondevuClient({ baseUrl: 'https://example.com' });
57
57
  * const { code } = await client.createOffer('my-room', {
58
- * info: 'peer-123',
58
+ * peerId: 'peer-123',
59
59
  * offer: signalingData
60
60
  * });
61
61
  * console.log('Session code:', code);
package/dist/client.js CHANGED
@@ -1,10 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RondevuClient = void 0;
4
1
  /**
5
2
  * HTTP client for Rondevu peer signaling and discovery server
6
3
  */
7
- class RondevuClient {
4
+ export class RondevuClient {
8
5
  /**
9
6
  * Creates a new Rondevu client instance
10
7
  * @param options - Client configuration options
@@ -70,7 +67,7 @@ class RondevuClient {
70
67
  * ```typescript
71
68
  * const client = new RondevuClient({ baseUrl: 'https://example.com' });
72
69
  * const { sessions } = await client.listSessions('my-room');
73
- * const otherPeers = sessions.filter(s => s.info !== myPeerId);
70
+ * const otherPeers = sessions.filter(s => s.peerId !== myPeerId);
74
71
  * ```
75
72
  */
76
73
  async listSessions(topic) {
@@ -81,15 +78,15 @@ class RondevuClient {
81
78
  /**
82
79
  * Announces peer availability and creates a new session
83
80
  *
84
- * @param topic - Topic identifier for grouping peers (max 256 characters)
85
- * @param request - Offer details including peer info and signaling data
81
+ * @param topic - Topic identifier for grouping peers (max 1024 characters)
82
+ * @param request - Offer details including peer ID and signaling data
86
83
  * @returns Unique session code (UUID)
87
84
  *
88
85
  * @example
89
86
  * ```typescript
90
87
  * const client = new RondevuClient({ baseUrl: 'https://example.com' });
91
88
  * const { code } = await client.createOffer('my-room', {
92
- * info: 'peer-123',
89
+ * peerId: 'peer-123',
93
90
  * offer: signalingData
94
91
  * });
95
92
  * console.log('Session code:', code);
@@ -179,4 +176,3 @@ class RondevuClient {
179
176
  });
180
177
  }
181
178
  }
182
- exports.RondevuClient = RondevuClient;
@@ -0,0 +1,75 @@
1
+ import { EventEmitter } from './event-emitter';
2
+ import { RondevuClient } from './client';
3
+ import { RondevuConnectionParams } from './types';
4
+ /**
5
+ * Represents a WebRTC connection with automatic signaling and ICE exchange
6
+ */
7
+ export declare class RondevuConnection extends EventEmitter {
8
+ readonly id: string;
9
+ readonly topic: string;
10
+ readonly role: 'offerer' | 'answerer';
11
+ readonly remotePeerId: string;
12
+ private pc;
13
+ private client;
14
+ private localPeerId;
15
+ private dataChannels;
16
+ private pollingInterval?;
17
+ private pollingIntervalMs;
18
+ private connectionTimeoutMs;
19
+ private connectionTimer?;
20
+ private isPolling;
21
+ private isClosed;
22
+ constructor(params: RondevuConnectionParams, client: RondevuClient);
23
+ /**
24
+ * Setup RTCPeerConnection event handlers
25
+ */
26
+ private setupEventHandlers;
27
+ /**
28
+ * Handle RTCPeerConnection state changes
29
+ */
30
+ private handleConnectionStateChange;
31
+ /**
32
+ * Send an ICE candidate to the remote peer via signaling server
33
+ */
34
+ private sendIceCandidate;
35
+ /**
36
+ * Start polling for remote session data (answer/candidates)
37
+ */
38
+ startPolling(): void;
39
+ /**
40
+ * Stop polling
41
+ */
42
+ private stopPolling;
43
+ /**
44
+ * Poll the signaling server for remote data
45
+ */
46
+ private poll;
47
+ /**
48
+ * Handle remotely created data channel
49
+ */
50
+ private handleRemoteDataChannel;
51
+ /**
52
+ * Get or create a data channel
53
+ */
54
+ dataChannel(label: string, options?: RTCDataChannelInit): RTCDataChannel;
55
+ /**
56
+ * Add a local media stream to the connection
57
+ */
58
+ addStream(stream: MediaStream): void;
59
+ /**
60
+ * Get the underlying RTCPeerConnection for advanced usage
61
+ */
62
+ getPeerConnection(): RTCPeerConnection;
63
+ /**
64
+ * Start connection timeout
65
+ */
66
+ private startConnectionTimeout;
67
+ /**
68
+ * Clear connection timeout
69
+ */
70
+ private clearConnectionTimeout;
71
+ /**
72
+ * Close the connection and cleanup resources
73
+ */
74
+ close(): void;
75
+ }
@@ -0,0 +1,260 @@
1
+ import { EventEmitter } from './event-emitter';
2
+ /**
3
+ * Represents a WebRTC connection with automatic signaling and ICE exchange
4
+ */
5
+ export class RondevuConnection extends EventEmitter {
6
+ constructor(params, client) {
7
+ super();
8
+ this.isPolling = false;
9
+ this.isClosed = false;
10
+ this.id = params.id;
11
+ this.topic = params.topic;
12
+ this.role = params.role;
13
+ this.pc = params.pc;
14
+ this.localPeerId = params.localPeerId;
15
+ this.remotePeerId = params.remotePeerId;
16
+ this.client = client;
17
+ this.dataChannels = new Map();
18
+ this.pollingIntervalMs = params.pollingInterval;
19
+ this.connectionTimeoutMs = params.connectionTimeout;
20
+ this.setupEventHandlers();
21
+ this.startConnectionTimeout();
22
+ }
23
+ /**
24
+ * Setup RTCPeerConnection event handlers
25
+ */
26
+ setupEventHandlers() {
27
+ // ICE candidate gathering
28
+ this.pc.onicecandidate = (event) => {
29
+ if (event.candidate && !this.isClosed) {
30
+ this.sendIceCandidate(event.candidate).catch((err) => {
31
+ this.emit('error', new Error(`Failed to send ICE candidate: ${err.message}`));
32
+ });
33
+ }
34
+ };
35
+ // Connection state changes
36
+ this.pc.onconnectionstatechange = () => {
37
+ this.handleConnectionStateChange();
38
+ };
39
+ // Remote data channels
40
+ this.pc.ondatachannel = (event) => {
41
+ this.handleRemoteDataChannel(event.channel);
42
+ };
43
+ // Remote media streams
44
+ this.pc.ontrack = (event) => {
45
+ if (event.streams && event.streams[0]) {
46
+ this.emit('stream', event.streams[0]);
47
+ }
48
+ };
49
+ // ICE connection state changes
50
+ this.pc.oniceconnectionstatechange = () => {
51
+ const state = this.pc.iceConnectionState;
52
+ if (state === 'failed' || state === 'closed') {
53
+ this.emit('error', new Error(`ICE connection ${state}`));
54
+ if (state === 'failed') {
55
+ this.close();
56
+ }
57
+ }
58
+ };
59
+ }
60
+ /**
61
+ * Handle RTCPeerConnection state changes
62
+ */
63
+ handleConnectionStateChange() {
64
+ const state = this.pc.connectionState;
65
+ switch (state) {
66
+ case 'connected':
67
+ this.clearConnectionTimeout();
68
+ this.stopPolling();
69
+ this.emit('connect');
70
+ break;
71
+ case 'disconnected':
72
+ this.emit('disconnect');
73
+ break;
74
+ case 'failed':
75
+ this.emit('error', new Error('Connection failed'));
76
+ this.close();
77
+ break;
78
+ case 'closed':
79
+ this.emit('disconnect');
80
+ break;
81
+ }
82
+ }
83
+ /**
84
+ * Send an ICE candidate to the remote peer via signaling server
85
+ */
86
+ async sendIceCandidate(candidate) {
87
+ try {
88
+ await this.client.sendAnswer({
89
+ code: this.id,
90
+ candidate: JSON.stringify(candidate.toJSON()),
91
+ side: this.role,
92
+ });
93
+ }
94
+ catch (err) {
95
+ throw new Error(`Failed to send ICE candidate: ${err.message}`);
96
+ }
97
+ }
98
+ /**
99
+ * Start polling for remote session data (answer/candidates)
100
+ */
101
+ startPolling() {
102
+ if (this.isPolling || this.isClosed) {
103
+ return;
104
+ }
105
+ this.isPolling = true;
106
+ // Poll immediately
107
+ this.poll().catch((err) => {
108
+ this.emit('error', new Error(`Poll error: ${err.message}`));
109
+ });
110
+ // Set up interval polling
111
+ this.pollingInterval = setInterval(() => {
112
+ this.poll().catch((err) => {
113
+ this.emit('error', new Error(`Poll error: ${err.message}`));
114
+ });
115
+ }, this.pollingIntervalMs);
116
+ }
117
+ /**
118
+ * Stop polling
119
+ */
120
+ stopPolling() {
121
+ this.isPolling = false;
122
+ if (this.pollingInterval) {
123
+ clearInterval(this.pollingInterval);
124
+ this.pollingInterval = undefined;
125
+ }
126
+ }
127
+ /**
128
+ * Poll the signaling server for remote data
129
+ */
130
+ async poll() {
131
+ if (this.isClosed) {
132
+ this.stopPolling();
133
+ return;
134
+ }
135
+ try {
136
+ const response = await this.client.poll(this.id, this.role);
137
+ if (this.role === 'offerer') {
138
+ const offererResponse = response;
139
+ // Apply answer if received and not yet applied
140
+ if (offererResponse.answer && !this.pc.currentRemoteDescription) {
141
+ await this.pc.setRemoteDescription({
142
+ type: 'answer',
143
+ sdp: offererResponse.answer,
144
+ });
145
+ }
146
+ // Apply ICE candidates
147
+ if (offererResponse.answerCandidates && offererResponse.answerCandidates.length > 0) {
148
+ for (const candidateStr of offererResponse.answerCandidates) {
149
+ try {
150
+ const candidate = JSON.parse(candidateStr);
151
+ await this.pc.addIceCandidate(new RTCIceCandidate(candidate));
152
+ }
153
+ catch (err) {
154
+ console.warn('Failed to add ICE candidate:', err);
155
+ }
156
+ }
157
+ }
158
+ }
159
+ else {
160
+ // Answerer role
161
+ const answererResponse = response;
162
+ // Apply ICE candidates from offerer
163
+ if (answererResponse.offerCandidates && answererResponse.offerCandidates.length > 0) {
164
+ for (const candidateStr of answererResponse.offerCandidates) {
165
+ try {
166
+ const candidate = JSON.parse(candidateStr);
167
+ await this.pc.addIceCandidate(new RTCIceCandidate(candidate));
168
+ }
169
+ catch (err) {
170
+ console.warn('Failed to add ICE candidate:', err);
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ catch (err) {
177
+ // Session not found or expired
178
+ if (err.message.includes('404') || err.message.includes('not found')) {
179
+ this.emit('error', new Error('Session not found or expired'));
180
+ this.close();
181
+ }
182
+ throw err;
183
+ }
184
+ }
185
+ /**
186
+ * Handle remotely created data channel
187
+ */
188
+ handleRemoteDataChannel(channel) {
189
+ this.dataChannels.set(channel.label, channel);
190
+ this.emit('datachannel', channel);
191
+ }
192
+ /**
193
+ * Get or create a data channel
194
+ */
195
+ dataChannel(label, options) {
196
+ let channel = this.dataChannels.get(label);
197
+ if (!channel) {
198
+ channel = this.pc.createDataChannel(label, options);
199
+ this.dataChannels.set(label, channel);
200
+ }
201
+ return channel;
202
+ }
203
+ /**
204
+ * Add a local media stream to the connection
205
+ */
206
+ addStream(stream) {
207
+ stream.getTracks().forEach(track => {
208
+ this.pc.addTrack(track, stream);
209
+ });
210
+ }
211
+ /**
212
+ * Get the underlying RTCPeerConnection for advanced usage
213
+ */
214
+ getPeerConnection() {
215
+ return this.pc;
216
+ }
217
+ /**
218
+ * Start connection timeout
219
+ */
220
+ startConnectionTimeout() {
221
+ this.connectionTimer = setTimeout(() => {
222
+ if (this.pc.connectionState !== 'connected') {
223
+ this.emit('error', new Error('Connection timeout'));
224
+ this.close();
225
+ }
226
+ }, this.connectionTimeoutMs);
227
+ }
228
+ /**
229
+ * Clear connection timeout
230
+ */
231
+ clearConnectionTimeout() {
232
+ if (this.connectionTimer) {
233
+ clearTimeout(this.connectionTimer);
234
+ this.connectionTimer = undefined;
235
+ }
236
+ }
237
+ /**
238
+ * Close the connection and cleanup resources
239
+ */
240
+ close() {
241
+ if (this.isClosed) {
242
+ return;
243
+ }
244
+ this.isClosed = true;
245
+ this.stopPolling();
246
+ this.clearConnectionTimeout();
247
+ // Close all data channels
248
+ this.dataChannels.forEach(dc => {
249
+ if (dc.readyState === 'open' || dc.readyState === 'connecting') {
250
+ dc.close();
251
+ }
252
+ });
253
+ this.dataChannels.clear();
254
+ // Close peer connection
255
+ if (this.pc.connectionState !== 'closed') {
256
+ this.pc.close();
257
+ }
258
+ this.emit('disconnect');
259
+ }
260
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Simple EventEmitter implementation for browser and Node.js compatibility
3
+ */
4
+ export declare class EventEmitter {
5
+ private events;
6
+ constructor();
7
+ /**
8
+ * Register an event listener
9
+ */
10
+ on(event: string, listener: Function): this;
11
+ /**
12
+ * Register a one-time event listener
13
+ */
14
+ once(event: string, listener: Function): this;
15
+ /**
16
+ * Remove an event listener
17
+ */
18
+ off(event: string, listener: Function): this;
19
+ /**
20
+ * Emit an event
21
+ */
22
+ emit(event: string, ...args: any[]): boolean;
23
+ /**
24
+ * Remove all listeners for an event (or all events if not specified)
25
+ */
26
+ removeAllListeners(event?: string): this;
27
+ /**
28
+ * Get listener count for an event
29
+ */
30
+ listenerCount(event: string): number;
31
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Simple EventEmitter implementation for browser and Node.js compatibility
3
+ */
4
+ export class EventEmitter {
5
+ constructor() {
6
+ this.events = new Map();
7
+ }
8
+ /**
9
+ * Register an event listener
10
+ */
11
+ on(event, listener) {
12
+ if (!this.events.has(event)) {
13
+ this.events.set(event, new Set());
14
+ }
15
+ this.events.get(event).add(listener);
16
+ return this;
17
+ }
18
+ /**
19
+ * Register a one-time event listener
20
+ */
21
+ once(event, listener) {
22
+ const onceWrapper = (...args) => {
23
+ this.off(event, onceWrapper);
24
+ listener.apply(this, args);
25
+ };
26
+ return this.on(event, onceWrapper);
27
+ }
28
+ /**
29
+ * Remove an event listener
30
+ */
31
+ off(event, listener) {
32
+ const listeners = this.events.get(event);
33
+ if (listeners) {
34
+ listeners.delete(listener);
35
+ if (listeners.size === 0) {
36
+ this.events.delete(event);
37
+ }
38
+ }
39
+ return this;
40
+ }
41
+ /**
42
+ * Emit an event
43
+ */
44
+ emit(event, ...args) {
45
+ const listeners = this.events.get(event);
46
+ if (!listeners || listeners.size === 0) {
47
+ return false;
48
+ }
49
+ listeners.forEach(listener => {
50
+ try {
51
+ listener.apply(this, args);
52
+ }
53
+ catch (err) {
54
+ console.error(`Error in ${event} event listener:`, err);
55
+ }
56
+ });
57
+ return true;
58
+ }
59
+ /**
60
+ * Remove all listeners for an event (or all events if not specified)
61
+ */
62
+ removeAllListeners(event) {
63
+ if (event) {
64
+ this.events.delete(event);
65
+ }
66
+ else {
67
+ this.events.clear();
68
+ }
69
+ return this;
70
+ }
71
+ /**
72
+ * Get listener count for an event
73
+ */
74
+ listenerCount(event) {
75
+ const listeners = this.events.get(event);
76
+ return listeners ? listeners.size : 0;
77
+ }
78
+ }
package/dist/index.d.ts CHANGED
@@ -1,30 +1,8 @@
1
1
  /**
2
- * TypeScript client for Rondevu peer signaling server
3
- *
4
- * @example
5
- * ```typescript
6
- * import { RondevuClient } from '@xtr-dev/rondevu-client';
7
- *
8
- * const client = new RondevuClient({
9
- * baseUrl: 'https://rondevu.example.com'
10
- * });
11
- *
12
- * // Create an offer
13
- * const { code } = await client.createOffer('my-room', {
14
- * info: 'peer-123',
15
- * offer: signalingData
16
- * });
17
- *
18
- * // Discover peers
19
- * const { sessions } = await client.listSessions('my-room');
20
- *
21
- * // Send answer
22
- * await client.sendAnswer({
23
- * code: sessions[0].code,
24
- * answer: answerData,
25
- * side: 'answerer'
26
- * });
27
- * ```
2
+ * @xtr-dev/rondevu-client
3
+ * WebRTC peer signaling and discovery client
28
4
  */
5
+ export { Rondevu } from './rondevu';
6
+ export { RondevuConnection } from './connection';
29
7
  export { RondevuClient } from './client';
30
- export * from './types';
8
+ export type { RondevuOptions, JoinOptions, ConnectionRole, RondevuConnectionParams, RondevuConnectionEvents, Side, Session, TopicInfo, Pagination, ListTopicsResponse, ListSessionsResponse, CreateOfferRequest, CreateOfferResponse, AnswerRequest, AnswerResponse, PollRequest, PollOffererResponse, PollAnswererResponse, PollResponse, HealthResponse, ErrorResponse, RondevuClientOptions, } from './types';
package/dist/index.js CHANGED
@@ -1,48 +1,10 @@
1
- "use strict";
2
1
  /**
3
- * TypeScript client for Rondevu peer signaling server
4
- *
5
- * @example
6
- * ```typescript
7
- * import { RondevuClient } from '@xtr-dev/rondevu-client';
8
- *
9
- * const client = new RondevuClient({
10
- * baseUrl: 'https://rondevu.example.com'
11
- * });
12
- *
13
- * // Create an offer
14
- * const { code } = await client.createOffer('my-room', {
15
- * info: 'peer-123',
16
- * offer: signalingData
17
- * });
18
- *
19
- * // Discover peers
20
- * const { sessions } = await client.listSessions('my-room');
21
- *
22
- * // Send answer
23
- * await client.sendAnswer({
24
- * code: sessions[0].code,
25
- * answer: answerData,
26
- * side: 'answerer'
27
- * });
28
- * ```
2
+ * @xtr-dev/rondevu-client
3
+ * WebRTC peer signaling and discovery client
29
4
  */
30
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
31
- if (k2 === undefined) k2 = k;
32
- var desc = Object.getOwnPropertyDescriptor(m, k);
33
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
34
- desc = { enumerable: true, get: function() { return m[k]; } };
35
- }
36
- Object.defineProperty(o, k2, desc);
37
- }) : (function(o, m, k, k2) {
38
- if (k2 === undefined) k2 = k;
39
- o[k2] = m[k];
40
- }));
41
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
42
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
43
- };
44
- Object.defineProperty(exports, "__esModule", { value: true });
45
- exports.RondevuClient = void 0;
46
- var client_1 = require("./client");
47
- Object.defineProperty(exports, "RondevuClient", { enumerable: true, get: function () { return client_1.RondevuClient; } });
48
- __exportStar(require("./types"), exports);
5
+ // Export main WebRTC client class
6
+ export { Rondevu } from './rondevu';
7
+ // Export connection class
8
+ export { RondevuConnection } from './connection';
9
+ // Export low-level signaling client (for advanced usage)
10
+ export { RondevuClient } from './client';
@@ -0,0 +1,58 @@
1
+ import { RondevuConnection } from './connection';
2
+ import { RondevuOptions, JoinOptions } from './types';
3
+ /**
4
+ * Main Rondevu WebRTC client with automatic connection management
5
+ */
6
+ export declare class Rondevu {
7
+ readonly peerId: string;
8
+ private client;
9
+ private rtcConfig?;
10
+ private pollingInterval;
11
+ private connectionTimeout;
12
+ /**
13
+ * Creates a new Rondevu client instance
14
+ * @param options - Client configuration options
15
+ */
16
+ constructor(options: RondevuOptions);
17
+ /**
18
+ * Generate a unique peer ID
19
+ */
20
+ private generatePeerId;
21
+ /**
22
+ * Update the peer ID (useful when user identity changes)
23
+ */
24
+ updatePeerId(newPeerId: string): void;
25
+ /**
26
+ * Create a new connection (offerer role)
27
+ * @param id - Connection identifier
28
+ * @param topic - Topic name for grouping connections
29
+ * @returns Promise that resolves to RondevuConnection
30
+ */
31
+ create(id: string, topic: string): Promise<RondevuConnection>;
32
+ /**
33
+ * Connect to an existing connection by ID (answerer role)
34
+ * @param id - Connection identifier
35
+ * @returns Promise that resolves to RondevuConnection
36
+ */
37
+ connect(id: string): Promise<RondevuConnection>;
38
+ /**
39
+ * Join a topic and discover available peers (answerer role)
40
+ * @param topic - Topic name
41
+ * @param options - Optional join options for filtering and selection
42
+ * @returns Promise that resolves to RondevuConnection
43
+ */
44
+ join(topic: string, options?: JoinOptions): Promise<RondevuConnection>;
45
+ /**
46
+ * Select a session based on strategy
47
+ */
48
+ private selectSession;
49
+ /**
50
+ * Wait for ICE gathering to complete
51
+ */
52
+ private waitForIceGathering;
53
+ /**
54
+ * Find a session by connection ID
55
+ * This requires polling since we don't know which topic it's in
56
+ */
57
+ private findSessionById;
58
+ }
@@ -0,0 +1,204 @@
1
+ import { RondevuClient } from './client';
2
+ import { RondevuConnection } from './connection';
3
+ /**
4
+ * Main Rondevu WebRTC client with automatic connection management
5
+ */
6
+ export class Rondevu {
7
+ /**
8
+ * Creates a new Rondevu client instance
9
+ * @param options - Client configuration options
10
+ */
11
+ constructor(options) {
12
+ this.client = new RondevuClient({
13
+ baseUrl: options.baseUrl,
14
+ origin: options.origin,
15
+ fetch: options.fetch,
16
+ });
17
+ // Auto-generate peer ID if not provided
18
+ this.peerId = options.peerId || this.generatePeerId();
19
+ this.rtcConfig = options.rtcConfig;
20
+ this.pollingInterval = options.pollingInterval || 1000;
21
+ this.connectionTimeout = options.connectionTimeout || 30000;
22
+ }
23
+ /**
24
+ * Generate a unique peer ID
25
+ */
26
+ generatePeerId() {
27
+ return `rdv_${Math.random().toString(36).substring(2, 14)}`;
28
+ }
29
+ /**
30
+ * Update the peer ID (useful when user identity changes)
31
+ */
32
+ updatePeerId(newPeerId) {
33
+ this.peerId = newPeerId;
34
+ }
35
+ /**
36
+ * Create a new connection (offerer role)
37
+ * @param id - Connection identifier
38
+ * @param topic - Topic name for grouping connections
39
+ * @returns Promise that resolves to RondevuConnection
40
+ */
41
+ async create(id, topic) {
42
+ // Create peer connection
43
+ const pc = new RTCPeerConnection(this.rtcConfig);
44
+ // Create initial data channel for negotiation (required for offer creation)
45
+ pc.createDataChannel('_negotiation');
46
+ // Generate offer
47
+ const offer = await pc.createOffer();
48
+ await pc.setLocalDescription(offer);
49
+ // Wait for ICE gathering to complete
50
+ await this.waitForIceGathering(pc);
51
+ // Create session on server with custom code
52
+ await this.client.createOffer(topic, {
53
+ peerId: this.peerId,
54
+ offer: pc.localDescription.sdp,
55
+ code: id,
56
+ });
57
+ // Create connection object
58
+ const connectionParams = {
59
+ id,
60
+ topic,
61
+ role: 'offerer',
62
+ pc,
63
+ localPeerId: this.peerId,
64
+ remotePeerId: '', // Will be populated when answer is received
65
+ pollingInterval: this.pollingInterval,
66
+ connectionTimeout: this.connectionTimeout,
67
+ };
68
+ const connection = new RondevuConnection(connectionParams, this.client);
69
+ // Start polling for answer
70
+ connection.startPolling();
71
+ return connection;
72
+ }
73
+ /**
74
+ * Connect to an existing connection by ID (answerer role)
75
+ * @param id - Connection identifier
76
+ * @returns Promise that resolves to RondevuConnection
77
+ */
78
+ async connect(id) {
79
+ // Poll server to get session by ID
80
+ const sessionData = await this.findSessionById(id);
81
+ if (!sessionData) {
82
+ throw new Error(`Connection ${id} not found or expired`);
83
+ }
84
+ // Create peer connection
85
+ const pc = new RTCPeerConnection(this.rtcConfig);
86
+ // Set remote offer
87
+ await pc.setRemoteDescription({
88
+ type: 'offer',
89
+ sdp: sessionData.offer,
90
+ });
91
+ // Generate answer
92
+ const answer = await pc.createAnswer();
93
+ await pc.setLocalDescription(answer);
94
+ // Wait for ICE gathering
95
+ await this.waitForIceGathering(pc);
96
+ // Send answer to server
97
+ await this.client.sendAnswer({
98
+ code: id,
99
+ answer: pc.localDescription.sdp,
100
+ side: 'answerer',
101
+ });
102
+ // Create connection object
103
+ const connectionParams = {
104
+ id,
105
+ topic: sessionData.topic || 'unknown',
106
+ role: 'answerer',
107
+ pc,
108
+ localPeerId: this.peerId,
109
+ remotePeerId: sessionData.peerId,
110
+ pollingInterval: this.pollingInterval,
111
+ connectionTimeout: this.connectionTimeout,
112
+ };
113
+ const connection = new RondevuConnection(connectionParams, this.client);
114
+ // Start polling for ICE candidates
115
+ connection.startPolling();
116
+ return connection;
117
+ }
118
+ /**
119
+ * Join a topic and discover available peers (answerer role)
120
+ * @param topic - Topic name
121
+ * @param options - Optional join options for filtering and selection
122
+ * @returns Promise that resolves to RondevuConnection
123
+ */
124
+ async join(topic, options) {
125
+ // List sessions in topic
126
+ const { sessions } = await this.client.listSessions(topic);
127
+ // Filter out self (sessions with our peer ID)
128
+ let availableSessions = sessions.filter(session => session.peerId !== this.peerId);
129
+ // Apply custom filter if provided
130
+ if (options?.filter) {
131
+ availableSessions = availableSessions.filter(options.filter);
132
+ }
133
+ if (availableSessions.length === 0) {
134
+ throw new Error(`No available peers in topic: ${topic}`);
135
+ }
136
+ // Select session based on strategy
137
+ const selectedSession = this.selectSession(availableSessions, options?.select || 'first');
138
+ // Connect to selected session
139
+ return this.connect(selectedSession.code);
140
+ }
141
+ /**
142
+ * Select a session based on strategy
143
+ */
144
+ selectSession(sessions, strategy) {
145
+ switch (strategy) {
146
+ case 'first':
147
+ return sessions[0];
148
+ case 'newest':
149
+ return sessions.reduce((newest, session) => session.createdAt > newest.createdAt ? session : newest);
150
+ case 'oldest':
151
+ return sessions.reduce((oldest, session) => session.createdAt < oldest.createdAt ? session : oldest);
152
+ case 'random':
153
+ return sessions[Math.floor(Math.random() * sessions.length)];
154
+ default:
155
+ return sessions[0];
156
+ }
157
+ }
158
+ /**
159
+ * Wait for ICE gathering to complete
160
+ */
161
+ async waitForIceGathering(pc) {
162
+ if (pc.iceGatheringState === 'complete') {
163
+ return;
164
+ }
165
+ return new Promise((resolve) => {
166
+ const checkState = () => {
167
+ if (pc.iceGatheringState === 'complete') {
168
+ pc.removeEventListener('icegatheringstatechange', checkState);
169
+ resolve();
170
+ }
171
+ };
172
+ pc.addEventListener('icegatheringstatechange', checkState);
173
+ // Also set a timeout in case gathering takes too long
174
+ setTimeout(() => {
175
+ pc.removeEventListener('icegatheringstatechange', checkState);
176
+ resolve();
177
+ }, 5000);
178
+ });
179
+ }
180
+ /**
181
+ * Find a session by connection ID
182
+ * This requires polling since we don't know which topic it's in
183
+ */
184
+ async findSessionById(id) {
185
+ try {
186
+ // Try to poll for the session directly
187
+ // The poll endpoint should return the session data
188
+ const response = await this.client.poll(id, 'answerer');
189
+ const answererResponse = response;
190
+ if (answererResponse.offer) {
191
+ return {
192
+ code: id,
193
+ peerId: '', // Will be populated from session data
194
+ offer: answererResponse.offer,
195
+ topic: undefined,
196
+ };
197
+ }
198
+ return null;
199
+ }
200
+ catch (err) {
201
+ throw new Error(`Failed to find session ${id}: ${err.message}`);
202
+ }
203
+ }
204
+ }
package/dist/types.d.ts CHANGED
@@ -9,7 +9,7 @@ export interface Session {
9
9
  /** Unique session identifier (UUID) */
10
10
  code: string;
11
11
  /** Peer identifier/metadata */
12
- info: string;
12
+ peerId: string;
13
13
  /** Signaling data for peer connection */
14
14
  offer: string;
15
15
  /** Additional signaling data from offerer */
@@ -59,9 +59,11 @@ export interface ListSessionsResponse {
59
59
  */
60
60
  export interface CreateOfferRequest {
61
61
  /** Peer identifier/metadata (max 1024 characters) */
62
- info: string;
62
+ peerId: string;
63
63
  /** Signaling data for peer connection */
64
64
  offer: string;
65
+ /** Optional custom connection code (if not provided, server generates UUID) */
66
+ code?: string;
65
67
  }
66
68
  /**
67
69
  * Response from POST /:topic/offer
@@ -144,3 +146,61 @@ export interface RondevuClientOptions {
144
146
  /** Optional fetch implementation (for Node.js environments) */
145
147
  fetch?: typeof fetch;
146
148
  }
149
+ /**
150
+ * Configuration options for Rondevu WebRTC client
151
+ */
152
+ export interface RondevuOptions {
153
+ /** Base URL of the Rondevu server (e.g., 'https://example.com') */
154
+ baseUrl: string;
155
+ /** Peer identifier (optional, auto-generated if not provided) */
156
+ peerId?: string;
157
+ /** Origin header value for session isolation (defaults to baseUrl origin) */
158
+ origin?: string;
159
+ /** Optional fetch implementation (for Node.js environments) */
160
+ fetch?: typeof fetch;
161
+ /** WebRTC configuration (ICE servers, etc.) */
162
+ rtcConfig?: RTCConfiguration;
163
+ /** Polling interval in milliseconds (default: 1000) */
164
+ pollingInterval?: number;
165
+ /** Connection timeout in milliseconds (default: 30000) */
166
+ connectionTimeout?: number;
167
+ }
168
+ /**
169
+ * Options for joining a topic
170
+ */
171
+ export interface JoinOptions {
172
+ /** Filter function to select specific sessions */
173
+ filter?: (session: {
174
+ code: string;
175
+ peerId: string;
176
+ }) => boolean;
177
+ /** Selection strategy for choosing a session */
178
+ select?: 'first' | 'newest' | 'oldest' | 'random';
179
+ }
180
+ /**
181
+ * Connection role - whether this peer is creating or answering
182
+ */
183
+ export type ConnectionRole = 'offerer' | 'answerer';
184
+ /**
185
+ * Parameters for creating a RondevuConnection
186
+ */
187
+ export interface RondevuConnectionParams {
188
+ id: string;
189
+ topic: string;
190
+ role: ConnectionRole;
191
+ pc: RTCPeerConnection;
192
+ localPeerId: string;
193
+ remotePeerId: string;
194
+ pollingInterval: number;
195
+ connectionTimeout: number;
196
+ }
197
+ /**
198
+ * Event map for RondevuConnection events
199
+ */
200
+ export interface RondevuConnectionEvents {
201
+ connect: () => void;
202
+ disconnect: () => void;
203
+ error: (error: Error) => void;
204
+ datachannel: (channel: RTCDataChannel) => void;
205
+ stream: (stream: MediaStream) => void;
206
+ }
package/dist/types.js CHANGED
@@ -1,2 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ // ============================================================================
2
+ // Signaling Types
3
+ // ============================================================================
4
+ export {};
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@xtr-dev/rondevu-client",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "TypeScript client for Rondevu peer signaling and discovery server",
5
+ "type": "module",
5
6
  "main": "dist/index.js",
6
7
  "types": "dist/index.d.ts",
7
8
  "scripts": {