@xtr-dev/rondevu-client 0.9.2 → 0.10.1

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.
Files changed (73) hide show
  1. package/dist/api.d.ts +147 -0
  2. package/dist/api.js +307 -0
  3. package/dist/bin.d.ts +35 -0
  4. package/dist/bin.js +35 -0
  5. package/dist/connection-manager.d.ts +104 -0
  6. package/dist/connection-manager.js +324 -0
  7. package/dist/connection.d.ts +112 -0
  8. package/dist/connection.js +194 -0
  9. package/dist/event-bus.d.ts +52 -0
  10. package/dist/event-bus.js +84 -0
  11. package/dist/index.d.ts +15 -11
  12. package/dist/index.js +9 -11
  13. package/dist/noop-signaler.d.ts +14 -0
  14. package/dist/noop-signaler.js +27 -0
  15. package/dist/rondevu-service.d.ts +81 -0
  16. package/dist/rondevu-service.js +131 -0
  17. package/dist/service-client.d.ts +94 -0
  18. package/dist/service-client.js +186 -0
  19. package/dist/service-host.d.ts +103 -0
  20. package/dist/service-host.js +186 -0
  21. package/dist/signaler.d.ts +25 -0
  22. package/dist/signaler.js +89 -0
  23. package/dist/types.d.ts +33 -0
  24. package/dist/types.js +2 -0
  25. package/dist/webrtc-context.d.ts +7 -0
  26. package/dist/webrtc-context.js +36 -0
  27. package/package.json +16 -2
  28. package/dist/auth.d.ts +0 -20
  29. package/dist/auth.js +0 -41
  30. package/dist/durable/channel.d.ts +0 -115
  31. package/dist/durable/channel.js +0 -301
  32. package/dist/durable/connection.d.ts +0 -125
  33. package/dist/durable/connection.js +0 -370
  34. package/dist/durable/reconnection.d.ts +0 -90
  35. package/dist/durable/reconnection.js +0 -127
  36. package/dist/durable/service.d.ts +0 -103
  37. package/dist/durable/service.js +0 -264
  38. package/dist/durable/types.d.ts +0 -149
  39. package/dist/durable/types.js +0 -28
  40. package/dist/event-emitter.d.ts +0 -54
  41. package/dist/event-emitter.js +0 -102
  42. package/dist/offer-pool.d.ts +0 -86
  43. package/dist/offer-pool.js +0 -145
  44. package/dist/offers.d.ts +0 -101
  45. package/dist/offers.js +0 -202
  46. package/dist/peer/answering-state.d.ts +0 -11
  47. package/dist/peer/answering-state.js +0 -39
  48. package/dist/peer/closed-state.d.ts +0 -8
  49. package/dist/peer/closed-state.js +0 -10
  50. package/dist/peer/connected-state.d.ts +0 -8
  51. package/dist/peer/connected-state.js +0 -11
  52. package/dist/peer/creating-offer-state.d.ts +0 -12
  53. package/dist/peer/creating-offer-state.js +0 -45
  54. package/dist/peer/exchanging-ice-state.d.ts +0 -17
  55. package/dist/peer/exchanging-ice-state.js +0 -64
  56. package/dist/peer/failed-state.d.ts +0 -10
  57. package/dist/peer/failed-state.js +0 -16
  58. package/dist/peer/idle-state.d.ts +0 -7
  59. package/dist/peer/idle-state.js +0 -14
  60. package/dist/peer/index.d.ts +0 -71
  61. package/dist/peer/index.js +0 -176
  62. package/dist/peer/state.d.ts +0 -23
  63. package/dist/peer/state.js +0 -63
  64. package/dist/peer/types.d.ts +0 -43
  65. package/dist/peer/types.js +0 -1
  66. package/dist/peer/waiting-for-answer-state.d.ts +0 -17
  67. package/dist/peer/waiting-for-answer-state.js +0 -60
  68. package/dist/rondevu.d.ts +0 -184
  69. package/dist/rondevu.js +0 -171
  70. package/dist/service-pool.d.ts +0 -123
  71. package/dist/service-pool.js +0 -488
  72. package/dist/usernames.d.ts +0 -79
  73. package/dist/usernames.js +0 -153
@@ -0,0 +1,103 @@
1
+ import { WebRTCRondevuConnection } from './connection.js';
2
+ import { RondevuService } from './rondevu-service.js';
3
+ import { EventBus } from './event-bus.js';
4
+ import { ConnectionInterface } from './types.js';
5
+ export interface ServiceHostOptions {
6
+ service: string;
7
+ rondevuService: RondevuService;
8
+ maxPeers?: number;
9
+ ttl?: number;
10
+ isPublic?: boolean;
11
+ metadata?: Record<string, any>;
12
+ rtcConfiguration?: RTCConfiguration;
13
+ }
14
+ export interface ServiceHostEvents {
15
+ connection: ConnectionInterface;
16
+ 'connection-closed': {
17
+ connectionId: string;
18
+ reason: string;
19
+ };
20
+ error: Error;
21
+ }
22
+ /**
23
+ * ServiceHost - Manages a pool of WebRTC offers for a service
24
+ *
25
+ * Maintains up to maxPeers concurrent offers, automatically replacing
26
+ * them when connections are established or expire.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const rondevuService = new RondevuService({
31
+ * apiUrl: 'https://signal.example.com',
32
+ * username: 'myusername',
33
+ * })
34
+ *
35
+ * await rondevuService.initialize()
36
+ * await rondevuService.claimUsername()
37
+ *
38
+ * const host = new ServiceHost({
39
+ * service: 'chat.app@1.0.0',
40
+ * rondevuService,
41
+ * maxPeers: 5,
42
+ * })
43
+ *
44
+ * await host.start()
45
+ *
46
+ * host.events.on('connection', (conn) => {
47
+ * console.log('New connection:', conn.id)
48
+ * conn.events.on('message', (msg) => {
49
+ * console.log('Message:', msg)
50
+ * })
51
+ * })
52
+ * ```
53
+ */
54
+ export declare class ServiceHost {
55
+ private connections;
56
+ private readonly service;
57
+ private readonly rondevuService;
58
+ private readonly maxPeers;
59
+ private readonly ttl;
60
+ private readonly isPublic;
61
+ private readonly metadata?;
62
+ private readonly rtcConfiguration?;
63
+ private readonly bin;
64
+ private isStarted;
65
+ readonly events: EventBus<ServiceHostEvents>;
66
+ constructor(options: ServiceHostOptions);
67
+ /**
68
+ * Start hosting the service - creates initial pool of offers
69
+ */
70
+ start(): Promise<void>;
71
+ /**
72
+ * Stop hosting - closes all connections and cleans up
73
+ */
74
+ stop(): void;
75
+ /**
76
+ * Get current number of active connections
77
+ */
78
+ getConnectionCount(): number;
79
+ /**
80
+ * Get current number of pending offers
81
+ */
82
+ getPendingOfferCount(): number;
83
+ /**
84
+ * Fill the offer pool up to maxPeers
85
+ */
86
+ private fillOfferPool;
87
+ /**
88
+ * Create a single offer and publish it
89
+ */
90
+ private createOffer;
91
+ /**
92
+ * Handle connection state changes
93
+ */
94
+ private handleConnectionStateChange;
95
+ /**
96
+ * Get all active connections
97
+ */
98
+ getConnections(): WebRTCRondevuConnection[];
99
+ /**
100
+ * Get a specific connection by ID
101
+ */
102
+ getConnection(connectionId: string): WebRTCRondevuConnection | undefined;
103
+ }
@@ -0,0 +1,186 @@
1
+ import { WebRTCRondevuConnection } from './connection.js';
2
+ import { WebRTCContext } from './webrtc-context.js';
3
+ import { RondevuSignaler } from './signaler.js';
4
+ import { NoOpSignaler } from './noop-signaler.js';
5
+ import { EventBus } from './event-bus.js';
6
+ import { createBin } from './bin.js';
7
+ /**
8
+ * ServiceHost - Manages a pool of WebRTC offers for a service
9
+ *
10
+ * Maintains up to maxPeers concurrent offers, automatically replacing
11
+ * them when connections are established or expire.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const rondevuService = new RondevuService({
16
+ * apiUrl: 'https://signal.example.com',
17
+ * username: 'myusername',
18
+ * })
19
+ *
20
+ * await rondevuService.initialize()
21
+ * await rondevuService.claimUsername()
22
+ *
23
+ * const host = new ServiceHost({
24
+ * service: 'chat.app@1.0.0',
25
+ * rondevuService,
26
+ * maxPeers: 5,
27
+ * })
28
+ *
29
+ * await host.start()
30
+ *
31
+ * host.events.on('connection', (conn) => {
32
+ * console.log('New connection:', conn.id)
33
+ * conn.events.on('message', (msg) => {
34
+ * console.log('Message:', msg)
35
+ * })
36
+ * })
37
+ * ```
38
+ */
39
+ export class ServiceHost {
40
+ constructor(options) {
41
+ this.connections = new Map();
42
+ this.bin = createBin();
43
+ this.isStarted = false;
44
+ this.events = new EventBus();
45
+ this.service = options.service;
46
+ this.rondevuService = options.rondevuService;
47
+ this.maxPeers = options.maxPeers || 20;
48
+ this.ttl = options.ttl || 300000;
49
+ this.isPublic = options.isPublic !== false;
50
+ this.metadata = options.metadata;
51
+ this.rtcConfiguration = options.rtcConfiguration;
52
+ }
53
+ /**
54
+ * Start hosting the service - creates initial pool of offers
55
+ */
56
+ async start() {
57
+ if (this.isStarted) {
58
+ throw new Error('ServiceHost already started');
59
+ }
60
+ this.isStarted = true;
61
+ await this.fillOfferPool();
62
+ }
63
+ /**
64
+ * Stop hosting - closes all connections and cleans up
65
+ */
66
+ stop() {
67
+ this.isStarted = false;
68
+ this.connections.forEach(conn => conn.disconnect());
69
+ this.connections.clear();
70
+ this.bin.clean();
71
+ }
72
+ /**
73
+ * Get current number of active connections
74
+ */
75
+ getConnectionCount() {
76
+ return Array.from(this.connections.values()).filter(conn => conn.state === 'connected')
77
+ .length;
78
+ }
79
+ /**
80
+ * Get current number of pending offers
81
+ */
82
+ getPendingOfferCount() {
83
+ return Array.from(this.connections.values()).filter(conn => conn.state === 'connecting')
84
+ .length;
85
+ }
86
+ /**
87
+ * Fill the offer pool up to maxPeers
88
+ */
89
+ async fillOfferPool() {
90
+ const currentOffers = this.connections.size;
91
+ const needed = this.maxPeers - currentOffers;
92
+ if (needed <= 0) {
93
+ return;
94
+ }
95
+ // Create multiple offers in parallel
96
+ const offerPromises = [];
97
+ for (let i = 0; i < needed; i++) {
98
+ offerPromises.push(this.createOffer());
99
+ }
100
+ await Promise.allSettled(offerPromises);
101
+ }
102
+ /**
103
+ * Create a single offer and publish it
104
+ */
105
+ async createOffer() {
106
+ try {
107
+ // Create temporary context with NoOp signaler
108
+ const tempContext = new WebRTCContext(new NoOpSignaler(), this.rtcConfiguration);
109
+ // Create connection (offerer role)
110
+ const conn = new WebRTCRondevuConnection({
111
+ id: `${this.service}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
112
+ service: this.service,
113
+ offer: null,
114
+ context: tempContext,
115
+ });
116
+ // Wait for offer to be created
117
+ await conn.ready;
118
+ // Get offer SDP
119
+ if (!conn.connection?.localDescription?.sdp) {
120
+ throw new Error('Failed to create offer SDP');
121
+ }
122
+ const sdp = conn.connection.localDescription.sdp;
123
+ // Publish service offer
124
+ const service = await this.rondevuService.publishService({
125
+ serviceFqn: this.service,
126
+ sdp,
127
+ ttl: this.ttl,
128
+ isPublic: this.isPublic,
129
+ metadata: this.metadata,
130
+ });
131
+ // Replace with real signaler now that we have offerId
132
+ const realSignaler = new RondevuSignaler(this.rondevuService.getAPI(), service.offerId);
133
+ tempContext.signaler = realSignaler;
134
+ // Track connection
135
+ this.connections.set(conn.id, conn);
136
+ // Listen for state changes
137
+ const cleanup = conn.events.on('state-change', state => {
138
+ this.handleConnectionStateChange(conn, state);
139
+ });
140
+ this.bin(cleanup);
141
+ }
142
+ catch (error) {
143
+ this.events.emit('error', error);
144
+ }
145
+ }
146
+ /**
147
+ * Handle connection state changes
148
+ */
149
+ handleConnectionStateChange(conn, state) {
150
+ if (state === 'connected') {
151
+ // Connection established - emit event
152
+ this.events.emit('connection', conn);
153
+ // Create new offer to replace this one
154
+ if (this.isStarted) {
155
+ this.fillOfferPool().catch(error => {
156
+ this.events.emit('error', error);
157
+ });
158
+ }
159
+ }
160
+ else if (state === 'disconnected') {
161
+ // Connection closed - remove and create new offer
162
+ this.connections.delete(conn.id);
163
+ this.events.emit('connection-closed', {
164
+ connectionId: conn.id,
165
+ reason: state,
166
+ });
167
+ if (this.isStarted) {
168
+ this.fillOfferPool().catch(error => {
169
+ this.events.emit('error', error);
170
+ });
171
+ }
172
+ }
173
+ }
174
+ /**
175
+ * Get all active connections
176
+ */
177
+ getConnections() {
178
+ return Array.from(this.connections.values());
179
+ }
180
+ /**
181
+ * Get a specific connection by ID
182
+ */
183
+ getConnection(connectionId) {
184
+ return this.connections.get(connectionId);
185
+ }
186
+ }
@@ -0,0 +1,25 @@
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
+ }
@@ -0,0 +1,89 @@
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
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Core connection types
3
+ */
4
+ import { EventBus } from './event-bus.js';
5
+ import { Binnable } from './bin.js';
6
+ export type Message = string | ArrayBuffer;
7
+ export interface QueueMessageOptions {
8
+ expiresAt?: number;
9
+ }
10
+ export interface ConnectionEvents {
11
+ 'state-change': ConnectionInterface['state'];
12
+ message: Message;
13
+ }
14
+ export declare const ConnectionStates: readonly ["connected", "disconnected", "connecting"];
15
+ export declare const isConnectionState: (state: string) => state is (typeof ConnectionStates)[number];
16
+ export interface ConnectionInterface {
17
+ id: string;
18
+ service: string;
19
+ state: (typeof ConnectionStates)[number];
20
+ lastActive: number;
21
+ expiresAt?: number;
22
+ events: EventBus<ConnectionEvents>;
23
+ queueMessage(message: Message, options?: QueueMessageOptions): Promise<void>;
24
+ sendMessage(message: Message): Promise<boolean>;
25
+ }
26
+ export interface Signaler {
27
+ addIceCandidate(candidate: RTCIceCandidate): Promise<void> | void;
28
+ addListener(callback: (candidate: RTCIceCandidate) => void): Binnable;
29
+ addOfferListener(callback: (offer: RTCSessionDescriptionInit) => void): Binnable;
30
+ addAnswerListener(callback: (answer: RTCSessionDescriptionInit) => void): Binnable;
31
+ setOffer(offer: RTCSessionDescriptionInit): Promise<void>;
32
+ setAnswer(answer: RTCSessionDescriptionInit): Promise<void>;
33
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export const ConnectionStates = ['connected', 'disconnected', 'connecting'];
2
+ export const isConnectionState = (state) => ConnectionStates.includes(state);
@@ -0,0 +1,7 @@
1
+ import { Signaler } from './types';
2
+ export declare class WebRTCContext {
3
+ readonly signaler: Signaler;
4
+ private readonly config?;
5
+ constructor(signaler: Signaler, config?: RTCConfiguration | undefined);
6
+ createPeerConnection(): RTCPeerConnection;
7
+ }
@@ -0,0 +1,36 @@
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(signaler, config) {
30
+ this.signaler = signaler;
31
+ this.config = config;
32
+ }
33
+ createPeerConnection() {
34
+ return new RTCPeerConnection(this.config || DEFAULT_RTC_CONFIGURATION);
35
+ }
36
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtr-dev/rondevu-client",
3
- "version": "0.9.2",
3
+ "version": "0.10.1",
4
4
  "description": "TypeScript client for Rondevu with durable WebRTC connections, automatic reconnection, and message queuing",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -8,6 +8,10 @@
8
8
  "scripts": {
9
9
  "build": "tsc",
10
10
  "typecheck": "tsc --noEmit",
11
+ "dev": "vite",
12
+ "lint": "eslint src demo --ext .ts,.tsx,.js",
13
+ "lint:fix": "eslint src demo --ext .ts,.tsx,.js --fix",
14
+ "format": "prettier --write \"src/**/*.{ts,tsx,js}\" \"demo/**/*.{ts,tsx,js,html}\"",
11
15
  "prepublishOnly": "npm run build"
12
16
  },
13
17
  "keywords": [
@@ -20,7 +24,17 @@
20
24
  "author": "",
21
25
  "license": "MIT",
22
26
  "devDependencies": {
23
- "typescript": "^5.9.3"
27
+ "@eslint/js": "^9.39.1",
28
+ "@typescript-eslint/eslint-plugin": "^8.48.1",
29
+ "@typescript-eslint/parser": "^8.48.1",
30
+ "eslint": "^9.39.1",
31
+ "eslint-config-prettier": "^10.1.8",
32
+ "eslint-plugin-prettier": "^5.5.4",
33
+ "eslint-plugin-unicorn": "^62.0.0",
34
+ "globals": "^16.5.0",
35
+ "prettier": "^3.7.4",
36
+ "typescript": "^5.9.3",
37
+ "vite": "^7.2.6"
24
38
  },
25
39
  "files": [
26
40
  "dist",
package/dist/auth.d.ts DELETED
@@ -1,20 +0,0 @@
1
- export interface Credentials {
2
- peerId: string;
3
- secret: string;
4
- }
5
- export type FetchFunction = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
6
- export declare class RondevuAuth {
7
- private baseUrl;
8
- private fetchFn;
9
- constructor(baseUrl: string, fetchFn?: FetchFunction);
10
- /**
11
- * Register a new peer and receive credentials
12
- * Generates a cryptographically random peer ID (128-bit)
13
- * @throws Error if registration fails
14
- */
15
- register(): Promise<Credentials>;
16
- /**
17
- * Create Authorization header value
18
- */
19
- static createAuthHeader(credentials: Credentials): string;
20
- }
package/dist/auth.js DELETED
@@ -1,41 +0,0 @@
1
- export class RondevuAuth {
2
- constructor(baseUrl, fetchFn) {
3
- this.baseUrl = baseUrl;
4
- // Use provided fetch or fall back to global fetch
5
- this.fetchFn = fetchFn || ((...args) => {
6
- if (typeof globalThis.fetch === 'function') {
7
- return globalThis.fetch(...args);
8
- }
9
- throw new Error('fetch is not available. Please provide a fetch implementation in the constructor options.');
10
- });
11
- }
12
- /**
13
- * Register a new peer and receive credentials
14
- * Generates a cryptographically random peer ID (128-bit)
15
- * @throws Error if registration fails
16
- */
17
- async register() {
18
- const response = await this.fetchFn(`${this.baseUrl}/register`, {
19
- method: 'POST',
20
- headers: {
21
- 'Content-Type': 'application/json',
22
- },
23
- body: JSON.stringify({}),
24
- });
25
- if (!response.ok) {
26
- const error = await response.json().catch(() => ({ error: 'Unknown error' }));
27
- throw new Error(`Registration failed: ${error.error || response.statusText}`);
28
- }
29
- const data = await response.json();
30
- return {
31
- peerId: data.peerId,
32
- secret: data.secret,
33
- };
34
- }
35
- /**
36
- * Create Authorization header value
37
- */
38
- static createAuthHeader(credentials) {
39
- return `Bearer ${credentials.peerId}:${credentials.secret}`;
40
- }
41
- }