@xtr-dev/rondevu-client 0.12.4 → 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,87 +0,0 @@
1
- import { RondevuAPI, Credentials, Keypair, Service } from './api.js';
2
- export interface RondevuServiceOptions {
3
- apiUrl: string;
4
- username: string;
5
- keypair?: Keypair;
6
- credentials?: Credentials;
7
- }
8
- export interface PublishServiceOptions {
9
- serviceFqn: string;
10
- offers: Array<{
11
- sdp: string;
12
- }>;
13
- ttl?: number;
14
- isPublic?: boolean;
15
- metadata?: Record<string, any>;
16
- }
17
- /**
18
- * RondevuService - High-level service management with automatic signature handling
19
- *
20
- * Provides a simplified API for:
21
- * - Username claiming with Ed25519 signatures
22
- * - Service publishing with automatic signature generation
23
- * - Keypair management
24
- *
25
- * @example
26
- * ```typescript
27
- * // Initialize service (generates keypair automatically)
28
- * const service = new RondevuService({
29
- * apiUrl: 'https://signal.example.com',
30
- * username: 'myusername',
31
- * })
32
- *
33
- * await service.initialize()
34
- *
35
- * // Claim username (one time)
36
- * await service.claimUsername()
37
- *
38
- * // Publish a service
39
- * const publishedService = await service.publishService({
40
- * serviceFqn: 'chat.app@1.0.0',
41
- * offers: [{ sdp: offerSdp }],
42
- * ttl: 300000,
43
- * isPublic: true,
44
- * })
45
- * ```
46
- */
47
- export declare class RondevuService {
48
- private readonly api;
49
- private readonly username;
50
- private keypair;
51
- private usernameClaimed;
52
- constructor(options: RondevuServiceOptions);
53
- /**
54
- * Initialize the service - generates keypair if not provided
55
- * Call this before using other methods
56
- */
57
- initialize(): Promise<void>;
58
- /**
59
- * Claim the username with Ed25519 signature
60
- * Should be called once before publishing services
61
- */
62
- claimUsername(): Promise<void>;
63
- /**
64
- * Publish a service with automatic signature generation
65
- */
66
- publishService(options: PublishServiceOptions): Promise<Service>;
67
- /**
68
- * Get the current keypair (for backup/storage)
69
- */
70
- getKeypair(): Keypair | null;
71
- /**
72
- * Get the username
73
- */
74
- getUsername(): string;
75
- /**
76
- * Get the public key
77
- */
78
- getPublicKey(): string | null;
79
- /**
80
- * Check if username has been claimed (checks with server)
81
- */
82
- isUsernameClaimed(): Promise<boolean>;
83
- /**
84
- * Access to underlying API for advanced operations
85
- */
86
- getAPI(): RondevuAPI;
87
- }
@@ -1,77 +0,0 @@
1
- import { RondevuService } from './rondevu-service.js';
2
- import { RTCDurableConnection } from './durable-connection.js';
3
- import { EventBus } from './event-bus.js';
4
- export interface ServiceClientOptions {
5
- username: string;
6
- serviceFqn: string;
7
- rondevuService: RondevuService;
8
- autoReconnect?: boolean;
9
- maxReconnectAttempts?: number;
10
- rtcConfiguration?: RTCConfiguration;
11
- }
12
- export interface ServiceClientEvents {
13
- connected: RTCDurableConnection;
14
- disconnected: void;
15
- reconnecting: {
16
- attempt: number;
17
- maxAttempts: number;
18
- };
19
- error: Error;
20
- }
21
- /**
22
- * ServiceClient - High-level wrapper for connecting to a WebRTC service
23
- *
24
- * Simplifies client connection by handling:
25
- * - Service discovery
26
- * - Offer/answer exchange
27
- * - ICE candidate polling
28
- * - Automatic reconnection
29
- *
30
- * @example
31
- * ```typescript
32
- * const client = new ServiceClient({
33
- * username: 'host-user',
34
- * serviceFqn: 'chat.app@1.0.0',
35
- * rondevuService: myService
36
- * })
37
- *
38
- * client.events.on('connected', conn => {
39
- * conn.events.on('message', msg => console.log('Received:', msg))
40
- * conn.sendMessage('Hello from client!')
41
- * })
42
- *
43
- * await client.connect()
44
- * ```
45
- */
46
- export declare class ServiceClient {
47
- private options;
48
- events: EventBus<ServiceClientEvents>;
49
- private signaler;
50
- private webrtcContext;
51
- private connection;
52
- private autoReconnect;
53
- private maxReconnectAttempts;
54
- private reconnectAttempts;
55
- private isConnecting;
56
- constructor(options: ServiceClientOptions);
57
- /**
58
- * Connect to the service
59
- */
60
- connect(): Promise<RTCDurableConnection>;
61
- /**
62
- * Disconnect from the service
63
- */
64
- dispose(): void;
65
- /**
66
- * @deprecated Use dispose() instead
67
- */
68
- disconnect(): void;
69
- /**
70
- * Attempt to reconnect
71
- */
72
- private attemptReconnect;
73
- /**
74
- * Get the current connection
75
- */
76
- getConnection(): RTCDurableConnection | null;
77
- }
@@ -1,158 +0,0 @@
1
- import { RondevuSignaler } from './rondevu-signaler.js';
2
- import { WebRTCContext } from './webrtc-context.js';
3
- import { RTCDurableConnection } from './durable-connection.js';
4
- import { EventBus } from './event-bus.js';
5
- /**
6
- * ServiceClient - High-level wrapper for connecting to a WebRTC service
7
- *
8
- * Simplifies client connection by handling:
9
- * - Service discovery
10
- * - Offer/answer exchange
11
- * - ICE candidate polling
12
- * - Automatic reconnection
13
- *
14
- * @example
15
- * ```typescript
16
- * const client = new ServiceClient({
17
- * username: 'host-user',
18
- * serviceFqn: 'chat.app@1.0.0',
19
- * rondevuService: myService
20
- * })
21
- *
22
- * client.events.on('connected', conn => {
23
- * conn.events.on('message', msg => console.log('Received:', msg))
24
- * conn.sendMessage('Hello from client!')
25
- * })
26
- *
27
- * await client.connect()
28
- * ```
29
- */
30
- export class ServiceClient {
31
- constructor(options) {
32
- this.options = options;
33
- this.signaler = null;
34
- this.connection = null;
35
- this.reconnectAttempts = 0;
36
- this.isConnecting = false;
37
- this.events = new EventBus();
38
- this.webrtcContext = new WebRTCContext(options.rtcConfiguration);
39
- this.autoReconnect = options.autoReconnect !== undefined ? options.autoReconnect : true;
40
- this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
41
- }
42
- /**
43
- * Connect to the service
44
- */
45
- async connect() {
46
- if (this.isConnecting) {
47
- throw new Error('Connection already in progress');
48
- }
49
- if (this.connection) {
50
- throw new Error('Already connected. Disconnect first.');
51
- }
52
- this.isConnecting = true;
53
- try {
54
- // Create signaler
55
- this.signaler = new RondevuSignaler(this.options.rondevuService, this.options.serviceFqn, this.options.username);
56
- // Wait for remote offer from signaler
57
- const remoteOffer = await new Promise((resolve, reject) => {
58
- const timeout = setTimeout(() => {
59
- reject(new Error('Service discovery timeout'));
60
- }, 30000);
61
- this.signaler.addOfferListener((offer) => {
62
- clearTimeout(timeout);
63
- resolve(offer);
64
- });
65
- });
66
- // Create connection with remote offer (makes us the answerer)
67
- const connection = new RTCDurableConnection({
68
- context: this.webrtcContext,
69
- signaler: this.signaler,
70
- offer: remoteOffer
71
- });
72
- // Wait for connection to be ready
73
- await connection.ready;
74
- // Set up connection event listeners
75
- connection.events.on('state-change', (state) => {
76
- if (state === 'connected') {
77
- this.reconnectAttempts = 0;
78
- this.events.emit('connected', connection);
79
- }
80
- else if (state === 'disconnected') {
81
- this.events.emit('disconnected', undefined);
82
- if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
83
- this.attemptReconnect();
84
- }
85
- }
86
- });
87
- this.connection = connection;
88
- this.isConnecting = false;
89
- return connection;
90
- }
91
- catch (err) {
92
- this.isConnecting = false;
93
- const error = err instanceof Error ? err : new Error(String(err));
94
- this.events.emit('error', error);
95
- throw error;
96
- }
97
- }
98
- /**
99
- * Disconnect from the service
100
- */
101
- dispose() {
102
- if (this.signaler) {
103
- this.signaler.dispose();
104
- this.signaler = null;
105
- }
106
- if (this.connection) {
107
- this.connection.disconnect();
108
- this.connection = null;
109
- }
110
- this.isConnecting = false;
111
- this.reconnectAttempts = 0;
112
- }
113
- /**
114
- * @deprecated Use dispose() instead
115
- */
116
- disconnect() {
117
- this.dispose();
118
- }
119
- /**
120
- * Attempt to reconnect
121
- */
122
- async attemptReconnect() {
123
- this.reconnectAttempts++;
124
- this.events.emit('reconnecting', {
125
- attempt: this.reconnectAttempts,
126
- maxAttempts: this.maxReconnectAttempts
127
- });
128
- // Cleanup old connection
129
- if (this.signaler) {
130
- this.signaler.dispose();
131
- this.signaler = null;
132
- }
133
- if (this.connection) {
134
- this.connection = null;
135
- }
136
- // Wait a bit before reconnecting
137
- await new Promise(resolve => setTimeout(resolve, 1000 * this.reconnectAttempts));
138
- try {
139
- await this.connect();
140
- }
141
- catch (err) {
142
- console.error('Reconnection attempt failed:', err);
143
- if (this.reconnectAttempts < this.maxReconnectAttempts) {
144
- this.attemptReconnect();
145
- }
146
- else {
147
- const error = new Error('Max reconnection attempts reached');
148
- this.events.emit('error', error);
149
- }
150
- }
151
- }
152
- /**
153
- * Get the current connection
154
- */
155
- getConnection() {
156
- return this.connection;
157
- }
158
- }
@@ -1,67 +0,0 @@
1
- import { RondevuService } from './rondevu-service.js';
2
- import { RTCDurableConnection } from './durable-connection.js';
3
- import { EventBus } from './event-bus.js';
4
- export interface ServiceHostOptions {
5
- service: string;
6
- rondevuService: RondevuService;
7
- maxPeers?: number;
8
- ttl?: number;
9
- isPublic?: boolean;
10
- rtcConfiguration?: RTCConfiguration;
11
- metadata?: Record<string, any>;
12
- }
13
- export interface ServiceHostEvents {
14
- connection: RTCDurableConnection;
15
- error: Error;
16
- }
17
- /**
18
- * ServiceHost - High-level wrapper for hosting a WebRTC service
19
- *
20
- * Simplifies hosting by handling:
21
- * - Offer/answer exchange
22
- * - ICE candidate polling
23
- * - Connection pool management
24
- * - Automatic reconnection
25
- *
26
- * @example
27
- * ```typescript
28
- * const host = new ServiceHost({
29
- * service: 'chat.app@1.0.0',
30
- * rondevuService: myService,
31
- * maxPeers: 5
32
- * })
33
- *
34
- * host.events.on('connection', conn => {
35
- * conn.events.on('message', msg => console.log('Received:', msg))
36
- * conn.sendMessage('Hello!')
37
- * })
38
- *
39
- * await host.start()
40
- * ```
41
- */
42
- export declare class ServiceHost {
43
- private options;
44
- events: EventBus<ServiceHostEvents>;
45
- private signaler;
46
- private webrtcContext;
47
- private connections;
48
- private maxPeers;
49
- private running;
50
- constructor(options: ServiceHostOptions);
51
- /**
52
- * Start hosting the service
53
- */
54
- start(): Promise<void>;
55
- /**
56
- * Create the next connection for incoming peers
57
- */
58
- private createNextConnection;
59
- /**
60
- * Stop hosting the service
61
- */
62
- dispose(): void;
63
- /**
64
- * Get all active connections
65
- */
66
- getConnections(): RTCDurableConnection[];
67
- }
@@ -1,120 +0,0 @@
1
- import { RondevuSignaler } from './rondevu-signaler.js';
2
- import { WebRTCContext } from './webrtc-context.js';
3
- import { RTCDurableConnection } from './durable-connection.js';
4
- import { EventBus } from './event-bus.js';
5
- /**
6
- * ServiceHost - High-level wrapper for hosting a WebRTC service
7
- *
8
- * Simplifies hosting by handling:
9
- * - Offer/answer exchange
10
- * - ICE candidate polling
11
- * - Connection pool management
12
- * - Automatic reconnection
13
- *
14
- * @example
15
- * ```typescript
16
- * const host = new ServiceHost({
17
- * service: 'chat.app@1.0.0',
18
- * rondevuService: myService,
19
- * maxPeers: 5
20
- * })
21
- *
22
- * host.events.on('connection', conn => {
23
- * conn.events.on('message', msg => console.log('Received:', msg))
24
- * conn.sendMessage('Hello!')
25
- * })
26
- *
27
- * await host.start()
28
- * ```
29
- */
30
- export class ServiceHost {
31
- constructor(options) {
32
- this.options = options;
33
- this.signaler = null;
34
- this.connections = [];
35
- this.running = false;
36
- this.events = new EventBus();
37
- this.webrtcContext = new WebRTCContext(options.rtcConfiguration);
38
- this.maxPeers = options.maxPeers || 5;
39
- }
40
- /**
41
- * Start hosting the service
42
- */
43
- async start() {
44
- if (this.running) {
45
- throw new Error('ServiceHost already running');
46
- }
47
- this.running = true;
48
- // Create signaler
49
- this.signaler = new RondevuSignaler(this.options.rondevuService, this.options.service);
50
- // Create first connection (offerer)
51
- const connection = new RTCDurableConnection({
52
- context: this.webrtcContext,
53
- signaler: this.signaler,
54
- offer: null // null means we're the offerer
55
- });
56
- // Wait for connection to be ready
57
- await connection.ready;
58
- // Set up connection event listeners
59
- connection.events.on('state-change', (state) => {
60
- if (state === 'connected') {
61
- this.connections.push(connection);
62
- this.events.emit('connection', connection);
63
- // Create next connection if under maxPeers
64
- if (this.connections.length < this.maxPeers) {
65
- this.createNextConnection().catch(err => {
66
- console.error('Failed to create next connection:', err);
67
- this.events.emit('error', err);
68
- });
69
- }
70
- }
71
- else if (state === 'disconnected') {
72
- // Remove from connections list
73
- const index = this.connections.indexOf(connection);
74
- if (index > -1) {
75
- this.connections.splice(index, 1);
76
- }
77
- }
78
- });
79
- // Publish service with the offer
80
- const offer = connection.connection?.localDescription;
81
- if (!offer?.sdp) {
82
- throw new Error('Offer SDP is empty');
83
- }
84
- await this.signaler.setOffer(offer);
85
- }
86
- /**
87
- * Create the next connection for incoming peers
88
- */
89
- async createNextConnection() {
90
- if (!this.signaler || !this.running) {
91
- return;
92
- }
93
- // For now, we'll use the same offer for all connections
94
- // In a production scenario, you'd create multiple offers
95
- // This is a limitation of the current service model
96
- // which publishes one offer per service
97
- }
98
- /**
99
- * Stop hosting the service
100
- */
101
- dispose() {
102
- this.running = false;
103
- // Cleanup signaler
104
- if (this.signaler) {
105
- this.signaler.dispose();
106
- this.signaler = null;
107
- }
108
- // Disconnect all connections
109
- for (const conn of this.connections) {
110
- conn.disconnect();
111
- }
112
- this.connections = [];
113
- }
114
- /**
115
- * Get all active connections
116
- */
117
- getConnections() {
118
- return [...this.connections];
119
- }
120
- }
@@ -1,25 +0,0 @@
1
- import { Signaler } from './types.js';
2
- import { Binnable } from './bin.js';
3
- import { RondevuAPI } from './api.js';
4
- /**
5
- * RondevuSignaler - Handles ICE candidate exchange via Rondevu API
6
- * Uses polling to retrieve remote candidates
7
- */
8
- export declare class RondevuSignaler implements Signaler {
9
- private api;
10
- private offerId;
11
- constructor(api: RondevuAPI, offerId: string);
12
- addOfferListener(callback: (offer: RTCSessionDescriptionInit) => void): Binnable;
13
- addAnswerListener(callback: (answer: RTCSessionDescriptionInit) => void): Binnable;
14
- setOffer(offer: RTCSessionDescriptionInit): Promise<void>;
15
- setAnswer(answer: RTCSessionDescriptionInit): Promise<void>;
16
- /**
17
- * Send a local ICE candidate to signaling server
18
- */
19
- addIceCandidate(candidate: RTCIceCandidate): Promise<void>;
20
- /**
21
- * Poll for remote ICE candidates and call callback for each one
22
- * Returns cleanup function to stop polling
23
- */
24
- addListener(callback: (candidate: RTCIceCandidate) => void): Binnable;
25
- }
package/dist/signaler.js DELETED
@@ -1,89 +0,0 @@
1
- /**
2
- * RondevuSignaler - Handles ICE candidate exchange via Rondevu API
3
- * Uses polling to retrieve remote candidates
4
- */
5
- export class RondevuSignaler {
6
- constructor(api, offerId) {
7
- this.api = api;
8
- this.offerId = offerId;
9
- }
10
- addOfferListener(callback) {
11
- throw new Error('Method not implemented.');
12
- }
13
- addAnswerListener(callback) {
14
- throw new Error('Method not implemented.');
15
- }
16
- setOffer(offer) {
17
- throw new Error('Method not implemented.');
18
- }
19
- setAnswer(answer) {
20
- throw new Error('Method not implemented.');
21
- }
22
- /**
23
- * Send a local ICE candidate to signaling server
24
- */
25
- async addIceCandidate(candidate) {
26
- const candidateData = candidate.toJSON();
27
- // Skip empty candidates
28
- if (!candidateData.candidate || candidateData.candidate === '') {
29
- return;
30
- }
31
- await this.api.addIceCandidates(this.offerId, [candidateData]);
32
- }
33
- /**
34
- * Poll for remote ICE candidates and call callback for each one
35
- * Returns cleanup function to stop polling
36
- */
37
- addListener(callback) {
38
- let lastTimestamp = 0;
39
- let polling = true;
40
- const poll = async () => {
41
- while (polling) {
42
- try {
43
- const candidates = await this.api.getIceCandidates(this.offerId, lastTimestamp);
44
- // Process each candidate
45
- for (const item of candidates) {
46
- if (item.candidate &&
47
- item.candidate.candidate &&
48
- item.candidate.candidate !== '') {
49
- try {
50
- const rtcCandidate = new RTCIceCandidate(item.candidate);
51
- callback(rtcCandidate);
52
- lastTimestamp = item.createdAt;
53
- }
54
- catch (err) {
55
- console.warn('Failed to process ICE candidate:', err);
56
- lastTimestamp = item.createdAt;
57
- }
58
- }
59
- else {
60
- lastTimestamp = item.createdAt;
61
- }
62
- }
63
- }
64
- catch (err) {
65
- // If offer not found or expired, stop polling
66
- if (err instanceof Error &&
67
- (err.message.includes('404') || err.message.includes('410'))) {
68
- console.warn('Offer not found or expired, stopping ICE polling');
69
- polling = false;
70
- break;
71
- }
72
- console.error('Error polling for ICE candidates:', err);
73
- }
74
- // Poll every second
75
- if (polling) {
76
- await new Promise(resolve => setTimeout(resolve, 1000));
77
- }
78
- }
79
- };
80
- // Start polling in the background
81
- poll().then(() => {
82
- console.log('ICE polling started');
83
- });
84
- // Return cleanup function
85
- return () => {
86
- polling = false;
87
- };
88
- }
89
- }
@@ -1,5 +0,0 @@
1
- export declare class WebRTCContext {
2
- private readonly config?;
3
- constructor(config?: RTCConfiguration | undefined);
4
- createPeerConnection(): RTCPeerConnection;
5
- }
@@ -1,35 +0,0 @@
1
- const DEFAULT_RTC_CONFIGURATION = {
2
- iceServers: [
3
- {
4
- urls: 'stun:stun.relay.metered.ca:80',
5
- },
6
- {
7
- urls: 'turn:standard.relay.metered.ca:80',
8
- username: 'c53a9c971da5e6f3bc959d8d',
9
- credential: 'QaccPqtPPaxyokXp',
10
- },
11
- {
12
- urls: 'turn:standard.relay.metered.ca:80?transport=tcp',
13
- username: 'c53a9c971da5e6f3bc959d8d',
14
- credential: 'QaccPqtPPaxyokXp',
15
- },
16
- {
17
- urls: 'turn:standard.relay.metered.ca:443',
18
- username: 'c53a9c971da5e6f3bc959d8d',
19
- credential: 'QaccPqtPPaxyokXp',
20
- },
21
- {
22
- urls: 'turns:standard.relay.metered.ca:443?transport=tcp',
23
- username: 'c53a9c971da5e6f3bc959d8d',
24
- credential: 'QaccPqtPPaxyokXp',
25
- },
26
- ],
27
- };
28
- export class WebRTCContext {
29
- constructor(config) {
30
- this.config = config;
31
- }
32
- createPeerConnection() {
33
- return new RTCPeerConnection(this.config || DEFAULT_RTC_CONFIGURATION);
34
- }
35
- }