@xtr-dev/rondevu-client 0.7.12 → 0.8.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.
@@ -0,0 +1,93 @@
1
+ import RondevuPeer from './peer/index.js';
2
+ /**
3
+ * Service info from discovery
4
+ */
5
+ export interface ServiceInfo {
6
+ uuid: string;
7
+ isPublic: boolean;
8
+ serviceFqn?: string;
9
+ metadata?: Record<string, any>;
10
+ }
11
+ /**
12
+ * Service list result
13
+ */
14
+ export interface ServiceListResult {
15
+ username: string;
16
+ services: ServiceInfo[];
17
+ }
18
+ /**
19
+ * Service query result
20
+ */
21
+ export interface ServiceQueryResult {
22
+ uuid: string;
23
+ allowed: boolean;
24
+ }
25
+ /**
26
+ * Service details
27
+ */
28
+ export interface ServiceDetails {
29
+ serviceId: string;
30
+ username: string;
31
+ serviceFqn: string;
32
+ offerId: string;
33
+ sdp: string;
34
+ isPublic: boolean;
35
+ metadata?: Record<string, any>;
36
+ createdAt: number;
37
+ expiresAt: number;
38
+ }
39
+ /**
40
+ * Connect result
41
+ */
42
+ export interface ConnectResult {
43
+ peer: RondevuPeer;
44
+ channel: RTCDataChannel;
45
+ }
46
+ /**
47
+ * Rondevu Discovery API
48
+ * Handles service discovery and connections
49
+ */
50
+ export declare class RondevuDiscovery {
51
+ private baseUrl;
52
+ private credentials;
53
+ private offersApi;
54
+ constructor(baseUrl: string, credentials: {
55
+ peerId: string;
56
+ secret: string;
57
+ });
58
+ /**
59
+ * Lists all services for a username
60
+ * Returns UUIDs only for private services, full details for public
61
+ */
62
+ listServices(username: string): Promise<ServiceListResult>;
63
+ /**
64
+ * Queries a service by FQN
65
+ * Returns UUID if service exists and is allowed
66
+ */
67
+ queryService(username: string, serviceFqn: string): Promise<ServiceQueryResult>;
68
+ /**
69
+ * Gets service details by UUID
70
+ */
71
+ getServiceDetails(uuid: string): Promise<ServiceDetails>;
72
+ /**
73
+ * Connects to a service by UUID
74
+ */
75
+ connectToService(uuid: string, options?: {
76
+ rtcConfig?: RTCConfiguration;
77
+ onConnected?: () => void;
78
+ onData?: (data: any) => void;
79
+ }): Promise<RondevuPeer>;
80
+ /**
81
+ * Convenience method: Query and connect in one call
82
+ * Returns both peer and data channel
83
+ */
84
+ connect(username: string, serviceFqn: string, options?: {
85
+ rtcConfig?: RTCConfiguration;
86
+ }): Promise<ConnectResult>;
87
+ /**
88
+ * Convenience method: Connect to service by UUID with channel
89
+ */
90
+ connectByUuid(uuid: string, options?: {
91
+ rtcConfig?: RTCConfiguration;
92
+ }): Promise<ConnectResult>;
93
+ }
@@ -0,0 +1,164 @@
1
+ import RondevuPeer from './peer/index.js';
2
+ import { RondevuOffers } from './offers.js';
3
+ /**
4
+ * Rondevu Discovery API
5
+ * Handles service discovery and connections
6
+ */
7
+ export class RondevuDiscovery {
8
+ constructor(baseUrl, credentials) {
9
+ this.baseUrl = baseUrl;
10
+ this.credentials = credentials;
11
+ this.offersApi = new RondevuOffers(baseUrl, credentials);
12
+ }
13
+ /**
14
+ * Lists all services for a username
15
+ * Returns UUIDs only for private services, full details for public
16
+ */
17
+ async listServices(username) {
18
+ const response = await fetch(`${this.baseUrl}/usernames/${username}/services`);
19
+ if (!response.ok) {
20
+ throw new Error('Failed to list services');
21
+ }
22
+ const data = await response.json();
23
+ return {
24
+ username: data.username,
25
+ services: data.services
26
+ };
27
+ }
28
+ /**
29
+ * Queries a service by FQN
30
+ * Returns UUID if service exists and is allowed
31
+ */
32
+ async queryService(username, serviceFqn) {
33
+ const response = await fetch(`${this.baseUrl}/index/${username}/query`, {
34
+ method: 'POST',
35
+ headers: { 'Content-Type': 'application/json' },
36
+ body: JSON.stringify({ serviceFqn })
37
+ });
38
+ if (!response.ok) {
39
+ const error = await response.json();
40
+ throw new Error(error.error || 'Service not found');
41
+ }
42
+ const data = await response.json();
43
+ return {
44
+ uuid: data.uuid,
45
+ allowed: data.allowed
46
+ };
47
+ }
48
+ /**
49
+ * Gets service details by UUID
50
+ */
51
+ async getServiceDetails(uuid) {
52
+ const response = await fetch(`${this.baseUrl}/services/${uuid}`);
53
+ if (!response.ok) {
54
+ const error = await response.json();
55
+ throw new Error(error.error || 'Service not found');
56
+ }
57
+ const data = await response.json();
58
+ return {
59
+ serviceId: data.serviceId,
60
+ username: data.username,
61
+ serviceFqn: data.serviceFqn,
62
+ offerId: data.offerId,
63
+ sdp: data.sdp,
64
+ isPublic: data.isPublic,
65
+ metadata: data.metadata,
66
+ createdAt: data.createdAt,
67
+ expiresAt: data.expiresAt
68
+ };
69
+ }
70
+ /**
71
+ * Connects to a service by UUID
72
+ */
73
+ async connectToService(uuid, options) {
74
+ // Get service details
75
+ const service = await this.getServiceDetails(uuid);
76
+ // Create peer with the offer
77
+ const peer = new RondevuPeer(this.offersApi, options?.rtcConfig || {
78
+ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
79
+ });
80
+ // Set up event handlers
81
+ if (options?.onConnected) {
82
+ peer.on('connected', options.onConnected);
83
+ }
84
+ if (options?.onData) {
85
+ peer.on('datachannel', (channel) => {
86
+ channel.onmessage = (e) => options.onData(e.data);
87
+ });
88
+ }
89
+ // Answer the offer
90
+ await peer.answer(service.offerId, service.sdp, {
91
+ topics: [], // V2 doesn't use topics
92
+ rtcConfig: options?.rtcConfig
93
+ });
94
+ return peer;
95
+ }
96
+ /**
97
+ * Convenience method: Query and connect in one call
98
+ * Returns both peer and data channel
99
+ */
100
+ async connect(username, serviceFqn, options) {
101
+ // Query service
102
+ const query = await this.queryService(username, serviceFqn);
103
+ if (!query.allowed) {
104
+ throw new Error('Service access denied');
105
+ }
106
+ // Get service details
107
+ const service = await this.getServiceDetails(query.uuid);
108
+ // Create peer
109
+ const peer = new RondevuPeer(this.offersApi, options?.rtcConfig || {
110
+ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
111
+ });
112
+ // Answer the offer
113
+ await peer.answer(service.offerId, service.sdp, {
114
+ topics: [], // V2 doesn't use topics
115
+ rtcConfig: options?.rtcConfig
116
+ });
117
+ // Wait for data channel
118
+ const channel = await new Promise((resolve, reject) => {
119
+ const timeout = setTimeout(() => {
120
+ reject(new Error('Timeout waiting for data channel'));
121
+ }, 30000);
122
+ peer.on('datachannel', (ch) => {
123
+ clearTimeout(timeout);
124
+ resolve(ch);
125
+ });
126
+ peer.on('failed', (error) => {
127
+ clearTimeout(timeout);
128
+ reject(error);
129
+ });
130
+ });
131
+ return { peer, channel };
132
+ }
133
+ /**
134
+ * Convenience method: Connect to service by UUID with channel
135
+ */
136
+ async connectByUuid(uuid, options) {
137
+ // Get service details
138
+ const service = await this.getServiceDetails(uuid);
139
+ // Create peer
140
+ const peer = new RondevuPeer(this.offersApi, options?.rtcConfig || {
141
+ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
142
+ });
143
+ // Answer the offer
144
+ await peer.answer(service.offerId, service.sdp, {
145
+ topics: [], // V2 doesn't use topics
146
+ rtcConfig: options?.rtcConfig
147
+ });
148
+ // Wait for data channel
149
+ const channel = await new Promise((resolve, reject) => {
150
+ const timeout = setTimeout(() => {
151
+ reject(new Error('Timeout waiting for data channel'));
152
+ }, 30000);
153
+ peer.on('datachannel', (ch) => {
154
+ clearTimeout(timeout);
155
+ resolve(ch);
156
+ });
157
+ peer.on('failed', (error) => {
158
+ clearTimeout(timeout);
159
+ reject(error);
160
+ });
161
+ });
162
+ return { peer, channel };
163
+ }
164
+ }
package/dist/index.d.ts CHANGED
@@ -8,6 +8,5 @@ export { RondevuAuth } from './auth.js';
8
8
  export type { Credentials, FetchFunction } from './auth.js';
9
9
  export { RondevuOffers } from './offers.js';
10
10
  export type { CreateOfferRequest, Offer, IceCandidate, TopicInfo } from './offers.js';
11
- export { BloomFilter } from './bloom.js';
12
11
  export { default as RondevuPeer } from './peer/index.js';
13
12
  export type { PeerOptions, PeerEvents, PeerTimeouts } from './peer/index.js';
package/dist/index.js CHANGED
@@ -8,7 +8,5 @@ export { Rondevu } from './rondevu.js';
8
8
  export { RondevuAuth } from './auth.js';
9
9
  // Export offers API
10
10
  export { RondevuOffers } from './offers.js';
11
- // Export bloom filter
12
- export { BloomFilter } from './bloom.js';
13
11
  // Export peer manager
14
12
  export { default as RondevuPeer } from './peer/index.js';
@@ -0,0 +1,74 @@
1
+ import { RondevuOffers, Offer } from './offers.js';
2
+ /**
3
+ * Represents an offer that has been answered
4
+ */
5
+ export interface AnsweredOffer {
6
+ offerId: string;
7
+ answererId: string;
8
+ sdp: string;
9
+ answeredAt: number;
10
+ }
11
+ /**
12
+ * Configuration options for the offer pool
13
+ */
14
+ export interface OfferPoolOptions {
15
+ /** Number of simultaneous open offers to maintain */
16
+ poolSize: number;
17
+ /** Polling interval in milliseconds (default: 2000ms) */
18
+ pollingInterval?: number;
19
+ /** Callback invoked when an offer is answered */
20
+ onAnswered: (answer: AnsweredOffer) => Promise<void>;
21
+ /** Callback to create new offers when refilling the pool */
22
+ onRefill: (count: number) => Promise<Offer[]>;
23
+ /** Error handler for pool operations */
24
+ onError: (error: Error, context: string) => void;
25
+ }
26
+ /**
27
+ * Manages a pool of offers with automatic polling and refill
28
+ *
29
+ * The OfferPool maintains a configurable number of simultaneous offers,
30
+ * polls for answers periodically, and automatically refills the pool
31
+ * when offers are consumed.
32
+ */
33
+ export declare class OfferPool {
34
+ private offersApi;
35
+ private options;
36
+ private offers;
37
+ private polling;
38
+ private pollingTimer?;
39
+ private lastPollTime;
40
+ private readonly pollingInterval;
41
+ constructor(offersApi: RondevuOffers, options: OfferPoolOptions);
42
+ /**
43
+ * Add offers to the pool
44
+ */
45
+ addOffers(offers: Offer[]): Promise<void>;
46
+ /**
47
+ * Start polling for answers
48
+ */
49
+ start(): Promise<void>;
50
+ /**
51
+ * Stop polling for answers
52
+ */
53
+ stop(): Promise<void>;
54
+ /**
55
+ * Poll for answers and refill the pool if needed
56
+ */
57
+ private poll;
58
+ /**
59
+ * Get the current number of active offers in the pool
60
+ */
61
+ getActiveOfferCount(): number;
62
+ /**
63
+ * Get all active offer IDs
64
+ */
65
+ getActiveOfferIds(): string[];
66
+ /**
67
+ * Get the last poll timestamp
68
+ */
69
+ getLastPollTime(): number;
70
+ /**
71
+ * Check if the pool is currently polling
72
+ */
73
+ isPolling(): boolean;
74
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Manages a pool of offers with automatic polling and refill
3
+ *
4
+ * The OfferPool maintains a configurable number of simultaneous offers,
5
+ * polls for answers periodically, and automatically refills the pool
6
+ * when offers are consumed.
7
+ */
8
+ export class OfferPool {
9
+ constructor(offersApi, options) {
10
+ this.offersApi = offersApi;
11
+ this.options = options;
12
+ this.offers = new Map();
13
+ this.polling = false;
14
+ this.lastPollTime = 0;
15
+ this.pollingInterval = options.pollingInterval || 2000;
16
+ }
17
+ /**
18
+ * Add offers to the pool
19
+ */
20
+ async addOffers(offers) {
21
+ for (const offer of offers) {
22
+ this.offers.set(offer.id, offer);
23
+ }
24
+ }
25
+ /**
26
+ * Start polling for answers
27
+ */
28
+ async start() {
29
+ if (this.polling) {
30
+ return;
31
+ }
32
+ this.polling = true;
33
+ // Do an immediate poll
34
+ await this.poll().catch((error) => {
35
+ this.options.onError(error, 'initial-poll');
36
+ });
37
+ // Start polling interval
38
+ this.pollingTimer = setInterval(async () => {
39
+ if (this.polling) {
40
+ await this.poll().catch((error) => {
41
+ this.options.onError(error, 'poll');
42
+ });
43
+ }
44
+ }, this.pollingInterval);
45
+ }
46
+ /**
47
+ * Stop polling for answers
48
+ */
49
+ async stop() {
50
+ this.polling = false;
51
+ if (this.pollingTimer) {
52
+ clearInterval(this.pollingTimer);
53
+ this.pollingTimer = undefined;
54
+ }
55
+ }
56
+ /**
57
+ * Poll for answers and refill the pool if needed
58
+ */
59
+ async poll() {
60
+ try {
61
+ // Get all answers from server
62
+ const answers = await this.offersApi.getAnswers();
63
+ // Filter for our pool's offers
64
+ const myAnswers = answers.filter(a => this.offers.has(a.offerId));
65
+ // Process each answer
66
+ for (const answer of myAnswers) {
67
+ // Notify ServicePool
68
+ await this.options.onAnswered({
69
+ offerId: answer.offerId,
70
+ answererId: answer.answererId,
71
+ sdp: answer.sdp,
72
+ answeredAt: answer.answeredAt
73
+ });
74
+ // Remove consumed offer from pool
75
+ this.offers.delete(answer.offerId);
76
+ }
77
+ // Immediate refill if below pool size
78
+ if (this.offers.size < this.options.poolSize) {
79
+ const needed = this.options.poolSize - this.offers.size;
80
+ try {
81
+ const newOffers = await this.options.onRefill(needed);
82
+ await this.addOffers(newOffers);
83
+ }
84
+ catch (refillError) {
85
+ this.options.onError(refillError, 'refill');
86
+ }
87
+ }
88
+ this.lastPollTime = Date.now();
89
+ }
90
+ catch (error) {
91
+ // Don't crash the pool on errors - let error handler deal with it
92
+ this.options.onError(error, 'poll');
93
+ }
94
+ }
95
+ /**
96
+ * Get the current number of active offers in the pool
97
+ */
98
+ getActiveOfferCount() {
99
+ return this.offers.size;
100
+ }
101
+ /**
102
+ * Get all active offer IDs
103
+ */
104
+ getActiveOfferIds() {
105
+ return Array.from(this.offers.keys());
106
+ }
107
+ /**
108
+ * Get the last poll timestamp
109
+ */
110
+ getLastPollTime() {
111
+ return this.lastPollTime;
112
+ }
113
+ /**
114
+ * Check if the pool is currently polling
115
+ */
116
+ isPolling() {
117
+ return this.polling;
118
+ }
119
+ }
package/dist/rondevu.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import { RondevuAuth, Credentials, FetchFunction } from './auth.js';
2
2
  import { RondevuOffers } from './offers.js';
3
+ import { RondevuUsername } from './usernames.js';
4
+ import { RondevuServices } from './services.js';
5
+ import { RondevuDiscovery } from './discovery.js';
3
6
  import RondevuPeer from './peer/index.js';
4
7
  export interface RondevuOptions {
5
8
  /**
@@ -58,7 +61,10 @@ export interface RondevuOptions {
58
61
  }
59
62
  export declare class Rondevu {
60
63
  readonly auth: RondevuAuth;
64
+ readonly usernames: RondevuUsername;
61
65
  private _offers?;
66
+ private _services?;
67
+ private _discovery?;
62
68
  private credentials?;
63
69
  private baseUrl;
64
70
  private fetchFn?;
@@ -67,9 +73,18 @@ export declare class Rondevu {
67
73
  private rtcIceCandidate?;
68
74
  constructor(options?: RondevuOptions);
69
75
  /**
70
- * Get offers API (requires authentication)
76
+ * Get offers API (low-level access, requires authentication)
77
+ * For most use cases, use services and discovery APIs instead
71
78
  */
72
79
  get offers(): RondevuOffers;
80
+ /**
81
+ * Get services API (requires authentication)
82
+ */
83
+ get services(): RondevuServices;
84
+ /**
85
+ * Get discovery API (requires authentication)
86
+ */
87
+ get discovery(): RondevuDiscovery;
73
88
  /**
74
89
  * Register and initialize authenticated client
75
90
  * Generates a cryptographically random peer ID (128-bit)
package/dist/rondevu.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import { RondevuAuth } from './auth.js';
2
2
  import { RondevuOffers } from './offers.js';
3
+ import { RondevuUsername } from './usernames.js';
4
+ import { RondevuServices } from './services.js';
5
+ import { RondevuDiscovery } from './discovery.js';
3
6
  import RondevuPeer from './peer/index.js';
4
7
  export class Rondevu {
5
8
  constructor(options = {}) {
@@ -9,13 +12,17 @@ export class Rondevu {
9
12
  this.rtcSessionDescription = options.RTCSessionDescription;
10
13
  this.rtcIceCandidate = options.RTCIceCandidate;
11
14
  this.auth = new RondevuAuth(this.baseUrl, this.fetchFn);
15
+ this.usernames = new RondevuUsername(this.baseUrl);
12
16
  if (options.credentials) {
13
17
  this.credentials = options.credentials;
14
18
  this._offers = new RondevuOffers(this.baseUrl, this.credentials, this.fetchFn);
19
+ this._services = new RondevuServices(this.baseUrl, this.credentials);
20
+ this._discovery = new RondevuDiscovery(this.baseUrl, this.credentials);
15
21
  }
16
22
  }
17
23
  /**
18
- * Get offers API (requires authentication)
24
+ * Get offers API (low-level access, requires authentication)
25
+ * For most use cases, use services and discovery APIs instead
19
26
  */
20
27
  get offers() {
21
28
  if (!this._offers) {
@@ -23,14 +30,34 @@ export class Rondevu {
23
30
  }
24
31
  return this._offers;
25
32
  }
33
+ /**
34
+ * Get services API (requires authentication)
35
+ */
36
+ get services() {
37
+ if (!this._services) {
38
+ throw new Error('Not authenticated. Call register() first or provide credentials.');
39
+ }
40
+ return this._services;
41
+ }
42
+ /**
43
+ * Get discovery API (requires authentication)
44
+ */
45
+ get discovery() {
46
+ if (!this._discovery) {
47
+ throw new Error('Not authenticated. Call register() first or provide credentials.');
48
+ }
49
+ return this._discovery;
50
+ }
26
51
  /**
27
52
  * Register and initialize authenticated client
28
53
  * Generates a cryptographically random peer ID (128-bit)
29
54
  */
30
55
  async register() {
31
56
  this.credentials = await this.auth.register();
32
- // Create offers API instance
57
+ // Create API instances
33
58
  this._offers = new RondevuOffers(this.baseUrl, this.credentials, this.fetchFn);
59
+ this._services = new RondevuServices(this.baseUrl, this.credentials);
60
+ this._discovery = new RondevuDiscovery(this.baseUrl, this.credentials);
34
61
  return this.credentials;
35
62
  }
36
63
  /**
@@ -0,0 +1,115 @@
1
+ import RondevuPeer from './peer/index.js';
2
+ import { ServiceHandle } from './services.js';
3
+ /**
4
+ * Status information about the pool
5
+ */
6
+ export interface PoolStatus {
7
+ /** Number of active offers in the pool */
8
+ activeOffers: number;
9
+ /** Number of currently connected peers */
10
+ activeConnections: number;
11
+ /** Total number of connections handled since start */
12
+ totalConnectionsHandled: number;
13
+ /** Number of failed offer creation attempts */
14
+ failedOfferCreations: number;
15
+ }
16
+ /**
17
+ * Configuration options for a pooled service
18
+ */
19
+ export interface ServicePoolOptions {
20
+ /** Username that owns the service */
21
+ username: string;
22
+ /** Private key for signing service operations */
23
+ privateKey: string;
24
+ /** Fully qualified service name (e.g., com.example.chat@1.0.0) */
25
+ serviceFqn: string;
26
+ /** WebRTC configuration */
27
+ rtcConfig?: RTCConfiguration;
28
+ /** Whether the service is publicly discoverable */
29
+ isPublic?: boolean;
30
+ /** Optional metadata for the service */
31
+ metadata?: Record<string, any>;
32
+ /** Time-to-live for offers in milliseconds */
33
+ ttl?: number;
34
+ /** Handler invoked for each new connection */
35
+ handler: (channel: RTCDataChannel, peer: RondevuPeer, connectionId: string) => void;
36
+ /** Number of simultaneous open offers to maintain (default: 1) */
37
+ poolSize?: number;
38
+ /** Polling interval in milliseconds (default: 2000ms) */
39
+ pollingInterval?: number;
40
+ /** Callback for pool status updates */
41
+ onPoolStatus?: (status: PoolStatus) => void;
42
+ /** Error handler for pool operations */
43
+ onError?: (error: Error, context: string) => void;
44
+ }
45
+ /**
46
+ * Extended service handle with pool-specific methods
47
+ */
48
+ export interface PooledServiceHandle extends ServiceHandle {
49
+ /** Get current pool status */
50
+ getStatus: () => PoolStatus;
51
+ /** Manually add offers to the pool */
52
+ addOffers: (count: number) => Promise<void>;
53
+ }
54
+ /**
55
+ * Manages a pooled service with multiple concurrent connections
56
+ *
57
+ * ServicePool coordinates offer creation, answer polling, and connection
58
+ * management for services that need to handle multiple simultaneous connections.
59
+ */
60
+ export declare class ServicePool {
61
+ private baseUrl;
62
+ private credentials;
63
+ private options;
64
+ private offerPool?;
65
+ private connections;
66
+ private status;
67
+ private serviceId?;
68
+ private uuid?;
69
+ private offersApi;
70
+ private usernameApi;
71
+ constructor(baseUrl: string, credentials: {
72
+ peerId: string;
73
+ secret: string;
74
+ }, options: ServicePoolOptions);
75
+ /**
76
+ * Start the pooled service
77
+ */
78
+ start(): Promise<PooledServiceHandle>;
79
+ /**
80
+ * Stop the pooled service and clean up
81
+ */
82
+ stop(): Promise<void>;
83
+ /**
84
+ * Handle an answered offer by setting up the connection
85
+ */
86
+ private handleConnection;
87
+ /**
88
+ * Create multiple offers
89
+ */
90
+ private createOffers;
91
+ /**
92
+ * Publish the initial service (creates first offer)
93
+ */
94
+ private publishInitialService;
95
+ /**
96
+ * Manually add offers to the pool
97
+ */
98
+ private manualRefill;
99
+ /**
100
+ * Get current pool status
101
+ */
102
+ private getStatus;
103
+ /**
104
+ * Update status and notify listeners
105
+ */
106
+ private updateStatus;
107
+ /**
108
+ * Handle errors
109
+ */
110
+ private handleError;
111
+ /**
112
+ * Generate a unique connection ID
113
+ */
114
+ private generateConnectionId;
115
+ }