@xtr-dev/rondevu-client 0.12.3 → 0.13.0

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.
@@ -1,120 +0,0 @@
1
- import { ConnectionEvents, ConnectionInterface, Message, QueueMessageOptions, Signaler } from './types.js';
2
- import { EventBus } from './event-bus.js';
3
- import { WebRTCContext } from './webrtc-context';
4
- export type WebRTCRondevuConnectionOptions = {
5
- offer?: RTCSessionDescriptionInit | null;
6
- context: WebRTCContext;
7
- signaler: Signaler;
8
- };
9
- /**
10
- * WebRTCRondevuConnection - WebRTC peer connection wrapper with Rondevu signaling
11
- *
12
- * Manages a WebRTC peer connection lifecycle including:
13
- * - Automatic offer/answer creation based on role
14
- * - ICE candidate exchange via Rondevu signaling server
15
- * - Connection state management with type-safe events
16
- * - Data channel creation and message handling
17
- *
18
- * The connection automatically determines its role (offerer or answerer) based on whether
19
- * an offer is provided in the constructor. The offerer creates the data channel, while
20
- * the answerer receives it via the 'datachannel' event.
21
- *
22
- * @example
23
- * ```typescript
24
- * // Offerer side (creates offer)
25
- * const connection = new WebRTCRondevuConnection(
26
- * 'conn-123',
27
- * 'peer-username',
28
- * 'chat.service@1.0.0'
29
- * );
30
- *
31
- * await connection.ready; // Wait for local offer
32
- * const sdp = connection.connection.localDescription!.sdp!;
33
- * // Send sdp to signaling server...
34
- *
35
- * // Answerer side (receives offer)
36
- * const connection = new WebRTCRondevuConnection(
37
- * 'conn-123',
38
- * 'peer-username',
39
- * 'chat.service@1.0.0',
40
- * { type: 'offer', sdp: remoteOfferSdp }
41
- * );
42
- *
43
- * await connection.ready; // Wait for local answer
44
- * const answerSdp = connection.connection.localDescription!.sdp!;
45
- * // Send answer to signaling server...
46
- *
47
- * // Both sides: Set up signaler and listen for state changes
48
- * connection.setSignaler(signaler);
49
- * connection.events.on('state-change', (state) => {
50
- * console.log('Connection state:', state);
51
- * });
52
- * ```
53
- */
54
- export declare class RTCDurableConnection implements ConnectionInterface {
55
- private readonly side;
56
- readonly expiresAt: number;
57
- readonly lastActive: number;
58
- readonly events: EventBus<ConnectionEvents>;
59
- readonly ready: Promise<void>;
60
- private iceBin;
61
- private context;
62
- private readonly signaler;
63
- private _conn;
64
- private _state;
65
- private _dataChannel;
66
- private messageQueue;
67
- constructor({ context, offer, signaler }: WebRTCRondevuConnectionOptions);
68
- /**
69
- * Getter method for retrieving the current connection.
70
- *
71
- * @return {RTCPeerConnection|null} The current connection instance.
72
- */
73
- get connection(): RTCPeerConnection | null;
74
- /**
75
- * Update connection state and emit state-change event
76
- */
77
- private setState;
78
- /**
79
- * Start ICE candidate exchange when gathering begins
80
- */
81
- private startIce;
82
- /**
83
- * Stop ICE candidate exchange when gathering completes
84
- */
85
- private stopIce;
86
- /**
87
- * Disconnects the current connection and cleans up resources.
88
- * Closes the active connection if it exists, resets the connection instance to null,
89
- * stops the ICE process, and updates the state to 'disconnected'.
90
- *
91
- * @return {void} No return value.
92
- */
93
- disconnect(): void;
94
- /**
95
- * Current connection state
96
- */
97
- get state(): "connected" | "disconnected" | "connecting";
98
- /**
99
- * Setup data channel event listeners
100
- */
101
- private setupDataChannelListeners;
102
- /**
103
- * Flush the message queue
104
- */
105
- private flushQueue;
106
- /**
107
- * Queue a message for sending when connection is established
108
- *
109
- * @param message - Message to queue (string or ArrayBuffer)
110
- * @param options - Queue options (e.g., expiration time)
111
- */
112
- queueMessage(message: Message, options?: QueueMessageOptions): Promise<void>;
113
- /**
114
- * Send a message immediately
115
- *
116
- * @param message - Message to send (string or ArrayBuffer)
117
- * @returns Promise resolving to true if sent successfully
118
- */
119
- sendMessage(message: Message): Promise<boolean>;
120
- }
@@ -1,244 +0,0 @@
1
- import { isConnectionState, } from './types.js';
2
- import { EventBus } from './event-bus.js';
3
- import { createBin } from './bin.js';
4
- /**
5
- * WebRTCRondevuConnection - WebRTC peer connection wrapper with Rondevu signaling
6
- *
7
- * Manages a WebRTC peer connection lifecycle including:
8
- * - Automatic offer/answer creation based on role
9
- * - ICE candidate exchange via Rondevu signaling server
10
- * - Connection state management with type-safe events
11
- * - Data channel creation and message handling
12
- *
13
- * The connection automatically determines its role (offerer or answerer) based on whether
14
- * an offer is provided in the constructor. The offerer creates the data channel, while
15
- * the answerer receives it via the 'datachannel' event.
16
- *
17
- * @example
18
- * ```typescript
19
- * // Offerer side (creates offer)
20
- * const connection = new WebRTCRondevuConnection(
21
- * 'conn-123',
22
- * 'peer-username',
23
- * 'chat.service@1.0.0'
24
- * );
25
- *
26
- * await connection.ready; // Wait for local offer
27
- * const sdp = connection.connection.localDescription!.sdp!;
28
- * // Send sdp to signaling server...
29
- *
30
- * // Answerer side (receives offer)
31
- * const connection = new WebRTCRondevuConnection(
32
- * 'conn-123',
33
- * 'peer-username',
34
- * 'chat.service@1.0.0',
35
- * { type: 'offer', sdp: remoteOfferSdp }
36
- * );
37
- *
38
- * await connection.ready; // Wait for local answer
39
- * const answerSdp = connection.connection.localDescription!.sdp!;
40
- * // Send answer to signaling server...
41
- *
42
- * // Both sides: Set up signaler and listen for state changes
43
- * connection.setSignaler(signaler);
44
- * connection.events.on('state-change', (state) => {
45
- * console.log('Connection state:', state);
46
- * });
47
- * ```
48
- */
49
- export class RTCDurableConnection {
50
- constructor({ context, offer, signaler }) {
51
- this.expiresAt = 0;
52
- this.lastActive = 0;
53
- this.events = new EventBus();
54
- this.iceBin = createBin();
55
- this._conn = null;
56
- this._state = 'disconnected';
57
- this._dataChannel = null;
58
- this.messageQueue = [];
59
- this.context = context;
60
- this.signaler = signaler;
61
- this._conn = context.createPeerConnection();
62
- this.side = offer ? 'answer' : 'offer';
63
- // setup data channel
64
- if (offer) {
65
- this._conn.addEventListener('datachannel', e => {
66
- this._dataChannel = e.channel;
67
- this.setupDataChannelListeners(this._dataChannel);
68
- });
69
- }
70
- else {
71
- this._dataChannel = this._conn.createDataChannel('vu.ronde.protocol');
72
- this.setupDataChannelListeners(this._dataChannel);
73
- }
74
- // setup description exchange
75
- this.ready = offer
76
- ? this._conn
77
- .setRemoteDescription(offer)
78
- .then(() => this._conn?.createAnswer())
79
- .then(async (answer) => {
80
- if (!answer || !this._conn)
81
- throw new Error('Connection disappeared');
82
- await this._conn.setLocalDescription(answer);
83
- return await signaler.setAnswer(answer);
84
- })
85
- : this._conn.createOffer().then(async (offer) => {
86
- if (!this._conn)
87
- throw new Error('Connection disappeared');
88
- await this._conn.setLocalDescription(offer);
89
- return await signaler.setOffer(offer);
90
- });
91
- // propagate connection state changes
92
- this._conn.addEventListener('connectionstatechange', () => {
93
- console.log(this.side, 'connection state changed: ', this._conn.connectionState);
94
- const state = isConnectionState(this._conn.connectionState)
95
- ? this._conn.connectionState
96
- : 'disconnected';
97
- this.setState(state);
98
- });
99
- this._conn.addEventListener('iceconnectionstatechange', () => {
100
- console.log(this.side, 'ice connection state changed: ', this._conn.iceConnectionState);
101
- });
102
- // start ICE candidate exchange when gathering begins
103
- this._conn.addEventListener('icegatheringstatechange', () => {
104
- if (this._conn.iceGatheringState === 'gathering') {
105
- this.startIce();
106
- }
107
- else if (this._conn.iceGatheringState === 'complete') {
108
- this.stopIce();
109
- }
110
- });
111
- }
112
- /**
113
- * Getter method for retrieving the current connection.
114
- *
115
- * @return {RTCPeerConnection|null} The current connection instance.
116
- */
117
- get connection() {
118
- return this._conn;
119
- }
120
- /**
121
- * Update connection state and emit state-change event
122
- */
123
- setState(state) {
124
- this._state = state;
125
- this.events.emit('state-change', state);
126
- }
127
- /**
128
- * Start ICE candidate exchange when gathering begins
129
- */
130
- startIce() {
131
- const listener = ({ candidate }) => {
132
- if (candidate)
133
- this.signaler.addIceCandidate(candidate);
134
- };
135
- if (!this._conn)
136
- throw new Error('Connection disappeared');
137
- this._conn.addEventListener('icecandidate', listener);
138
- this.iceBin(this.signaler.addListener((candidate) => this._conn?.addIceCandidate(candidate)), () => this._conn?.removeEventListener('icecandidate', listener));
139
- }
140
- /**
141
- * Stop ICE candidate exchange when gathering completes
142
- */
143
- stopIce() {
144
- this.iceBin.clean();
145
- }
146
- /**
147
- * Disconnects the current connection and cleans up resources.
148
- * Closes the active connection if it exists, resets the connection instance to null,
149
- * stops the ICE process, and updates the state to 'disconnected'.
150
- *
151
- * @return {void} No return value.
152
- */
153
- disconnect() {
154
- this._conn?.close();
155
- this._conn = null;
156
- this.stopIce();
157
- this.setState('disconnected');
158
- }
159
- /**
160
- * Current connection state
161
- */
162
- get state() {
163
- return this._state;
164
- }
165
- /**
166
- * Setup data channel event listeners
167
- */
168
- setupDataChannelListeners(channel) {
169
- channel.addEventListener('message', e => {
170
- this.events.emit('message', e.data);
171
- });
172
- channel.addEventListener('open', () => {
173
- // Channel opened - flush queued messages
174
- this.flushQueue().catch(err => {
175
- console.error('Failed to flush message queue:', err);
176
- });
177
- });
178
- channel.addEventListener('error', err => {
179
- console.error('Data channel error:', err);
180
- });
181
- channel.addEventListener('close', () => {
182
- console.log('Data channel closed');
183
- });
184
- }
185
- /**
186
- * Flush the message queue
187
- */
188
- async flushQueue() {
189
- while (this.messageQueue.length > 0 && this._state === 'connected') {
190
- const item = this.messageQueue.shift();
191
- // Check expiration
192
- if (item.options.expiresAt && Date.now() > item.options.expiresAt) {
193
- continue;
194
- }
195
- const success = await this.sendMessage(item.message);
196
- if (!success) {
197
- // Re-queue on failure
198
- this.messageQueue.unshift(item);
199
- break;
200
- }
201
- }
202
- }
203
- /**
204
- * Queue a message for sending when connection is established
205
- *
206
- * @param message - Message to queue (string or ArrayBuffer)
207
- * @param options - Queue options (e.g., expiration time)
208
- */
209
- async queueMessage(message, options = {}) {
210
- this.messageQueue.push({
211
- message,
212
- options,
213
- timestamp: Date.now()
214
- });
215
- // Try immediate send if connected
216
- if (this._state === 'connected') {
217
- await this.flushQueue();
218
- }
219
- }
220
- /**
221
- * Send a message immediately
222
- *
223
- * @param message - Message to send (string or ArrayBuffer)
224
- * @returns Promise resolving to true if sent successfully
225
- */
226
- async sendMessage(message) {
227
- if (this._state !== 'connected' || !this._dataChannel) {
228
- return false;
229
- }
230
- if (this._dataChannel.readyState !== 'open') {
231
- return false;
232
- }
233
- try {
234
- // TypeScript has trouble with the union type, so we cast to any
235
- // Both string and ArrayBuffer are valid for RTCDataChannel.send()
236
- this._dataChannel.send(message);
237
- return true;
238
- }
239
- catch (err) {
240
- console.error('Send failed:', err);
241
- return false;
242
- }
243
- }
244
- }
@@ -1,52 +0,0 @@
1
- /**
2
- * Type-safe EventBus with event name to payload type mapping
3
- */
4
- type EventHandler<T = any> = (data: T) => void;
5
- /**
6
- * EventBus - Type-safe event emitter with inferred event data types
7
- *
8
- * @example
9
- * interface MyEvents {
10
- * 'user:connected': { userId: string; timestamp: number };
11
- * 'user:disconnected': { userId: string };
12
- * 'message:received': string;
13
- * }
14
- *
15
- * const bus = new EventBus<MyEvents>();
16
- *
17
- * // TypeScript knows data is { userId: string; timestamp: number }
18
- * bus.on('user:connected', (data) => {
19
- * console.log(data.userId, data.timestamp);
20
- * });
21
- *
22
- * // TypeScript knows data is string
23
- * bus.on('message:received', (data) => {
24
- * console.log(data.toUpperCase());
25
- * });
26
- */
27
- export declare class EventBus<TEvents extends Record<string, any>> {
28
- private handlers;
29
- constructor();
30
- /**
31
- * Subscribe to an event
32
- * Returns a cleanup function to unsubscribe
33
- */
34
- on<K extends keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): () => void;
35
- /**
36
- * Subscribe to an event once (auto-unsubscribe after first call)
37
- */
38
- once<K extends keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): void;
39
- /**
40
- * Unsubscribe from an event
41
- */
42
- off<K extends keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): void;
43
- /**
44
- * Emit an event with data
45
- */
46
- emit<K extends keyof TEvents>(event: K, data: TEvents[K]): void;
47
- /**
48
- * Remove all handlers for a specific event, or all handlers if no event specified
49
- */
50
- clear<K extends keyof TEvents>(event?: K): void;
51
- }
52
- export {};
package/dist/event-bus.js DELETED
@@ -1,84 +0,0 @@
1
- /**
2
- * Type-safe EventBus with event name to payload type mapping
3
- */
4
- /**
5
- * EventBus - Type-safe event emitter with inferred event data types
6
- *
7
- * @example
8
- * interface MyEvents {
9
- * 'user:connected': { userId: string; timestamp: number };
10
- * 'user:disconnected': { userId: string };
11
- * 'message:received': string;
12
- * }
13
- *
14
- * const bus = new EventBus<MyEvents>();
15
- *
16
- * // TypeScript knows data is { userId: string; timestamp: number }
17
- * bus.on('user:connected', (data) => {
18
- * console.log(data.userId, data.timestamp);
19
- * });
20
- *
21
- * // TypeScript knows data is string
22
- * bus.on('message:received', (data) => {
23
- * console.log(data.toUpperCase());
24
- * });
25
- */
26
- export class EventBus {
27
- constructor() {
28
- this.handlers = new Map();
29
- }
30
- /**
31
- * Subscribe to an event
32
- * Returns a cleanup function to unsubscribe
33
- */
34
- on(event, handler) {
35
- if (!this.handlers.has(event)) {
36
- this.handlers.set(event, new Set());
37
- }
38
- this.handlers.get(event).add(handler);
39
- // Return cleanup function
40
- return () => this.off(event, handler);
41
- }
42
- /**
43
- * Subscribe to an event once (auto-unsubscribe after first call)
44
- */
45
- once(event, handler) {
46
- const wrappedHandler = (data) => {
47
- handler(data);
48
- this.off(event, wrappedHandler);
49
- };
50
- this.on(event, wrappedHandler);
51
- }
52
- /**
53
- * Unsubscribe from an event
54
- */
55
- off(event, handler) {
56
- const eventHandlers = this.handlers.get(event);
57
- if (eventHandlers) {
58
- eventHandlers.delete(handler);
59
- if (eventHandlers.size === 0) {
60
- this.handlers.delete(event);
61
- }
62
- }
63
- }
64
- /**
65
- * Emit an event with data
66
- */
67
- emit(event, data) {
68
- const eventHandlers = this.handlers.get(event);
69
- if (eventHandlers) {
70
- eventHandlers.forEach(handler => handler(data));
71
- }
72
- }
73
- /**
74
- * Remove all handlers for a specific event, or all handlers if no event specified
75
- */
76
- clear(event) {
77
- if (event !== undefined) {
78
- this.handlers.delete(event);
79
- }
80
- else {
81
- this.handlers.clear();
82
- }
83
- }
84
- }
@@ -1,14 +0,0 @@
1
- import { Signaler } from './types.js';
2
- import { Binnable } from './bin.js';
3
- /**
4
- * NoOpSignaler - A signaler that does nothing
5
- * Used as a placeholder during connection setup before the real signaler is available
6
- */
7
- export declare class NoOpSignaler implements Signaler {
8
- addIceCandidate(_candidate: RTCIceCandidate): void;
9
- addListener(_callback: (candidate: RTCIceCandidate) => void): Binnable;
10
- addOfferListener(_callback: (offer: RTCSessionDescriptionInit) => void): Binnable;
11
- addAnswerListener(_callback: (answer: RTCSessionDescriptionInit) => void): Binnable;
12
- setOffer(_offer: RTCSessionDescriptionInit): Promise<void>;
13
- setAnswer(_answer: RTCSessionDescriptionInit): Promise<void>;
14
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * NoOpSignaler - A signaler that does nothing
3
- * Used as a placeholder during connection setup before the real signaler is available
4
- */
5
- export class NoOpSignaler {
6
- addIceCandidate(_candidate) {
7
- // No-op
8
- }
9
- addListener(_callback) {
10
- // Return no-op cleanup function
11
- return () => { };
12
- }
13
- addOfferListener(_callback) {
14
- // Return no-op cleanup function
15
- return () => { };
16
- }
17
- addAnswerListener(_callback) {
18
- // Return no-op cleanup function
19
- return () => { };
20
- }
21
- async setOffer(_offer) {
22
- // No-op
23
- }
24
- async setAnswer(_answer) {
25
- // No-op
26
- }
27
- }
@@ -1,29 +0,0 @@
1
- import { RondevuService } from './rondevu-service.js';
2
- import { Keypair } from './api.js';
3
- export interface QuickStartOptions {
4
- apiUrl: string;
5
- username?: string;
6
- keypair?: Keypair;
7
- }
8
- /**
9
- * Quick start helper for initializing Rondevu service
10
- *
11
- * Simplifies initialization by:
12
- * - Auto-generating username if not provided
13
- * - Auto-generating keypair if not provided
14
- * - Registering with the server
15
- * - Claiming the username
16
- *
17
- * @example
18
- * ```typescript
19
- * // Simple usage with auto-generated username
20
- * const service = await quickStart({ apiUrl: 'https://api.ronde.vu' })
21
- *
22
- * // With custom username
23
- * const service = await quickStart({
24
- * apiUrl: 'https://api.ronde.vu',
25
- * username: 'my-username'
26
- * })
27
- * ```
28
- */
29
- export declare function quickStart(options: QuickStartOptions): Promise<RondevuService>;
@@ -1,44 +0,0 @@
1
- import { RondevuService } from './rondevu-service.js';
2
- /**
3
- * Quick start helper for initializing Rondevu service
4
- *
5
- * Simplifies initialization by:
6
- * - Auto-generating username if not provided
7
- * - Auto-generating keypair if not provided
8
- * - Registering with the server
9
- * - Claiming the username
10
- *
11
- * @example
12
- * ```typescript
13
- * // Simple usage with auto-generated username
14
- * const service = await quickStart({ apiUrl: 'https://api.ronde.vu' })
15
- *
16
- * // With custom username
17
- * const service = await quickStart({
18
- * apiUrl: 'https://api.ronde.vu',
19
- * username: 'my-username'
20
- * })
21
- * ```
22
- */
23
- export async function quickStart(options) {
24
- // Generate username if not provided
25
- const username = options.username || `user-${generateId()}`;
26
- // Create service
27
- const serviceOptions = {
28
- apiUrl: options.apiUrl,
29
- username,
30
- keypair: options.keypair
31
- };
32
- const service = new RondevuService(serviceOptions);
33
- // Initialize (generates keypair and registers)
34
- await service.initialize();
35
- // Claim username
36
- await service.claimUsername();
37
- return service;
38
- }
39
- /**
40
- * Generate a random ID
41
- */
42
- function generateId() {
43
- return Math.random().toString(36).substring(2, 10);
44
- }
@@ -1,10 +0,0 @@
1
- import { RondevuService } from "./rondevu-service";
2
- import { RTCDurableConnection } from "./durable-connection";
3
- export declare class RondevuContext {
4
- readonly rondevu: RondevuService;
5
- readonly rtcConfig: RTCConfiguration;
6
- private readonly context;
7
- private readonly connections;
8
- constructor(rondevu: RondevuService, rtcConfig: RTCConfiguration);
9
- createConnection(service: string, host?: string, offer?: RTCSessionDescriptionInit): RTCDurableConnection;
10
- }
@@ -1,20 +0,0 @@
1
- import { WebRTCContext } from "./webrtc-context";
2
- import { RTCDurableConnection } from "./durable-connection";
3
- import { RondevuSignaler } from "./rondevu-signaler";
4
- export class RondevuContext {
5
- constructor(rondevu, rtcConfig) {
6
- this.rondevu = rondevu;
7
- this.rtcConfig = rtcConfig;
8
- this.connections = [];
9
- this.context = new WebRTCContext(rtcConfig);
10
- }
11
- createConnection(service, host, offer) {
12
- const conn = new RTCDurableConnection({
13
- context: this.context,
14
- offer,
15
- signaler: new RondevuSignaler(this.rondevu, service, host)
16
- });
17
- this.connections.push(conn);
18
- return conn;
19
- }
20
- }