@xtr-dev/rondevu-client 0.9.1 → 0.10.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.
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 +92 -0
  18. package/dist/service-client.js +185 -0
  19. package/dist/service-host.d.ts +101 -0
  20. package/dist/service-host.js +185 -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 +6 -0
  26. package/dist/webrtc-context.js +34 -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 -417
  72. package/dist/usernames.d.ts +0 -79
  73. package/dist/usernames.js +0 -153
@@ -0,0 +1,101 @@
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
+ }
13
+ export interface ServiceHostEvents {
14
+ connection: ConnectionInterface;
15
+ 'connection-closed': {
16
+ connectionId: string;
17
+ reason: string;
18
+ };
19
+ error: Error;
20
+ }
21
+ /**
22
+ * ServiceHost - Manages a pool of WebRTC offers for a service
23
+ *
24
+ * Maintains up to maxPeers concurrent offers, automatically replacing
25
+ * them when connections are established or expire.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const rondevuService = new RondevuService({
30
+ * apiUrl: 'https://signal.example.com',
31
+ * username: 'myusername',
32
+ * })
33
+ *
34
+ * await rondevuService.initialize()
35
+ * await rondevuService.claimUsername()
36
+ *
37
+ * const host = new ServiceHost({
38
+ * service: 'chat.app@1.0.0',
39
+ * rondevuService,
40
+ * maxPeers: 5,
41
+ * })
42
+ *
43
+ * await host.start()
44
+ *
45
+ * host.events.on('connection', (conn) => {
46
+ * console.log('New connection:', conn.id)
47
+ * conn.events.on('message', (msg) => {
48
+ * console.log('Message:', msg)
49
+ * })
50
+ * })
51
+ * ```
52
+ */
53
+ export declare class ServiceHost {
54
+ private connections;
55
+ private readonly service;
56
+ private readonly rondevuService;
57
+ private readonly maxPeers;
58
+ private readonly ttl;
59
+ private readonly isPublic;
60
+ private readonly metadata?;
61
+ private readonly bin;
62
+ private isStarted;
63
+ readonly events: EventBus<ServiceHostEvents>;
64
+ constructor(options: ServiceHostOptions);
65
+ /**
66
+ * Start hosting the service - creates initial pool of offers
67
+ */
68
+ start(): Promise<void>;
69
+ /**
70
+ * Stop hosting - closes all connections and cleans up
71
+ */
72
+ stop(): void;
73
+ /**
74
+ * Get current number of active connections
75
+ */
76
+ getConnectionCount(): number;
77
+ /**
78
+ * Get current number of pending offers
79
+ */
80
+ getPendingOfferCount(): number;
81
+ /**
82
+ * Fill the offer pool up to maxPeers
83
+ */
84
+ private fillOfferPool;
85
+ /**
86
+ * Create a single offer and publish it
87
+ */
88
+ private createOffer;
89
+ /**
90
+ * Handle connection state changes
91
+ */
92
+ private handleConnectionStateChange;
93
+ /**
94
+ * Get all active connections
95
+ */
96
+ getConnections(): WebRTCRondevuConnection[];
97
+ /**
98
+ * Get a specific connection by ID
99
+ */
100
+ getConnection(connectionId: string): WebRTCRondevuConnection | undefined;
101
+ }
@@ -0,0 +1,185 @@
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
+ }
52
+ /**
53
+ * Start hosting the service - creates initial pool of offers
54
+ */
55
+ async start() {
56
+ if (this.isStarted) {
57
+ throw new Error('ServiceHost already started');
58
+ }
59
+ this.isStarted = true;
60
+ await this.fillOfferPool();
61
+ }
62
+ /**
63
+ * Stop hosting - closes all connections and cleans up
64
+ */
65
+ stop() {
66
+ this.isStarted = false;
67
+ this.connections.forEach(conn => conn.disconnect());
68
+ this.connections.clear();
69
+ this.bin.clean();
70
+ }
71
+ /**
72
+ * Get current number of active connections
73
+ */
74
+ getConnectionCount() {
75
+ return Array.from(this.connections.values()).filter(conn => conn.state === 'connected')
76
+ .length;
77
+ }
78
+ /**
79
+ * Get current number of pending offers
80
+ */
81
+ getPendingOfferCount() {
82
+ return Array.from(this.connections.values()).filter(conn => conn.state === 'connecting')
83
+ .length;
84
+ }
85
+ /**
86
+ * Fill the offer pool up to maxPeers
87
+ */
88
+ async fillOfferPool() {
89
+ const currentOffers = this.connections.size;
90
+ const needed = this.maxPeers - currentOffers;
91
+ if (needed <= 0) {
92
+ return;
93
+ }
94
+ // Create multiple offers in parallel
95
+ const offerPromises = [];
96
+ for (let i = 0; i < needed; i++) {
97
+ offerPromises.push(this.createOffer());
98
+ }
99
+ await Promise.allSettled(offerPromises);
100
+ }
101
+ /**
102
+ * Create a single offer and publish it
103
+ */
104
+ async createOffer() {
105
+ try {
106
+ // Create temporary context with NoOp signaler
107
+ const tempContext = new WebRTCContext(new NoOpSignaler());
108
+ // Create connection (offerer role)
109
+ const conn = new WebRTCRondevuConnection({
110
+ id: `${this.service}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
111
+ service: this.service,
112
+ offer: null,
113
+ context: tempContext,
114
+ });
115
+ // Wait for offer to be created
116
+ await conn.ready;
117
+ // Get offer SDP
118
+ if (!conn.connection?.localDescription?.sdp) {
119
+ throw new Error('Failed to create offer SDP');
120
+ }
121
+ const sdp = conn.connection.localDescription.sdp;
122
+ // Publish service offer
123
+ const service = await this.rondevuService.publishService({
124
+ serviceFqn: this.service,
125
+ sdp,
126
+ ttl: this.ttl,
127
+ isPublic: this.isPublic,
128
+ metadata: this.metadata,
129
+ });
130
+ // Replace with real signaler now that we have offerId
131
+ const realSignaler = new RondevuSignaler(this.rondevuService.getAPI(), service.offerId);
132
+ tempContext.signaler = realSignaler;
133
+ // Track connection
134
+ this.connections.set(conn.id, conn);
135
+ // Listen for state changes
136
+ const cleanup = conn.events.on('state-change', state => {
137
+ this.handleConnectionStateChange(conn, state);
138
+ });
139
+ this.bin(cleanup);
140
+ }
141
+ catch (error) {
142
+ this.events.emit('error', error);
143
+ }
144
+ }
145
+ /**
146
+ * Handle connection state changes
147
+ */
148
+ handleConnectionStateChange(conn, state) {
149
+ if (state === 'connected') {
150
+ // Connection established - emit event
151
+ this.events.emit('connection', conn);
152
+ // Create new offer to replace this one
153
+ if (this.isStarted) {
154
+ this.fillOfferPool().catch(error => {
155
+ this.events.emit('error', error);
156
+ });
157
+ }
158
+ }
159
+ else if (state === 'disconnected') {
160
+ // Connection closed - remove and create new offer
161
+ this.connections.delete(conn.id);
162
+ this.events.emit('connection-closed', {
163
+ connectionId: conn.id,
164
+ reason: state,
165
+ });
166
+ if (this.isStarted) {
167
+ this.fillOfferPool().catch(error => {
168
+ this.events.emit('error', error);
169
+ });
170
+ }
171
+ }
172
+ }
173
+ /**
174
+ * Get all active connections
175
+ */
176
+ getConnections() {
177
+ return Array.from(this.connections.values());
178
+ }
179
+ /**
180
+ * Get a specific connection by ID
181
+ */
182
+ getConnection(connectionId) {
183
+ return this.connections.get(connectionId);
184
+ }
185
+ }
@@ -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,6 @@
1
+ import { Signaler } from './types';
2
+ export declare class WebRTCContext {
3
+ readonly signaler: Signaler;
4
+ constructor(signaler: Signaler);
5
+ createPeerConnection(): RTCPeerConnection;
6
+ }
@@ -0,0 +1,34 @@
1
+ export class WebRTCContext {
2
+ constructor(signaler) {
3
+ this.signaler = signaler;
4
+ }
5
+ createPeerConnection() {
6
+ return new RTCPeerConnection({
7
+ iceServers: [
8
+ {
9
+ urls: 'stun:stun.relay.metered.ca:80',
10
+ },
11
+ {
12
+ urls: 'turn:standard.relay.metered.ca:80',
13
+ username: 'c53a9c971da5e6f3bc959d8d',
14
+ credential: 'QaccPqtPPaxyokXp',
15
+ },
16
+ {
17
+ urls: 'turn:standard.relay.metered.ca:80?transport=tcp',
18
+ username: 'c53a9c971da5e6f3bc959d8d',
19
+ credential: 'QaccPqtPPaxyokXp',
20
+ },
21
+ {
22
+ urls: 'turn:standard.relay.metered.ca:443',
23
+ username: 'c53a9c971da5e6f3bc959d8d',
24
+ credential: 'QaccPqtPPaxyokXp',
25
+ },
26
+ {
27
+ urls: 'turns:standard.relay.metered.ca:443?transport=tcp',
28
+ username: 'c53a9c971da5e6f3bc959d8d',
29
+ credential: 'QaccPqtPPaxyokXp',
30
+ },
31
+ ],
32
+ });
33
+ }
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtr-dev/rondevu-client",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
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
- }