@xtr-dev/rondevu-client 0.0.1 → 0.0.3
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 +534 -128
- package/dist/client.d.ts +4 -4
- package/dist/client.js +6 -10
- package/dist/connection.d.ts +75 -0
- package/dist/connection.js +260 -0
- package/dist/event-emitter.d.ts +31 -0
- package/dist/event-emitter.js +78 -0
- package/dist/index.d.ts +5 -27
- package/dist/index.js +8 -46
- package/dist/rondevu.d.ts +58 -0
- package/dist/rondevu.js +204 -0
- package/dist/types.d.ts +62 -2
- package/dist/types.js +4 -2
- package/package.json +2 -1
package/dist/rondevu.js
ADDED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Signaling Types
|
|
3
|
+
// ============================================================================
|
|
4
|
+
export {};
|
package/package.json
CHANGED