@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.
- package/dist/api.d.ts +147 -0
- package/dist/api.js +307 -0
- package/dist/bin.d.ts +35 -0
- package/dist/bin.js +35 -0
- package/dist/connection-manager.d.ts +104 -0
- package/dist/connection-manager.js +324 -0
- package/dist/connection.d.ts +112 -0
- package/dist/connection.js +194 -0
- package/dist/event-bus.d.ts +52 -0
- package/dist/event-bus.js +84 -0
- package/dist/index.d.ts +15 -11
- package/dist/index.js +9 -11
- package/dist/noop-signaler.d.ts +14 -0
- package/dist/noop-signaler.js +27 -0
- package/dist/rondevu-service.d.ts +81 -0
- package/dist/rondevu-service.js +131 -0
- package/dist/service-client.d.ts +94 -0
- package/dist/service-client.js +186 -0
- package/dist/service-host.d.ts +103 -0
- package/dist/service-host.js +186 -0
- package/dist/signaler.d.ts +25 -0
- package/dist/signaler.js +89 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.js +2 -0
- package/dist/webrtc-context.d.ts +7 -0
- package/dist/webrtc-context.js +36 -0
- package/package.json +16 -2
- package/dist/auth.d.ts +0 -20
- package/dist/auth.js +0 -41
- package/dist/durable/channel.d.ts +0 -115
- package/dist/durable/channel.js +0 -301
- package/dist/durable/connection.d.ts +0 -125
- package/dist/durable/connection.js +0 -370
- package/dist/durable/reconnection.d.ts +0 -90
- package/dist/durable/reconnection.js +0 -127
- package/dist/durable/service.d.ts +0 -103
- package/dist/durable/service.js +0 -264
- package/dist/durable/types.d.ts +0 -149
- package/dist/durable/types.js +0 -28
- package/dist/event-emitter.d.ts +0 -54
- package/dist/event-emitter.js +0 -102
- package/dist/offer-pool.d.ts +0 -86
- package/dist/offer-pool.js +0 -145
- package/dist/offers.d.ts +0 -101
- package/dist/offers.js +0 -202
- package/dist/peer/answering-state.d.ts +0 -11
- package/dist/peer/answering-state.js +0 -39
- package/dist/peer/closed-state.d.ts +0 -8
- package/dist/peer/closed-state.js +0 -10
- package/dist/peer/connected-state.d.ts +0 -8
- package/dist/peer/connected-state.js +0 -11
- package/dist/peer/creating-offer-state.d.ts +0 -12
- package/dist/peer/creating-offer-state.js +0 -45
- package/dist/peer/exchanging-ice-state.d.ts +0 -17
- package/dist/peer/exchanging-ice-state.js +0 -64
- package/dist/peer/failed-state.d.ts +0 -10
- package/dist/peer/failed-state.js +0 -16
- package/dist/peer/idle-state.d.ts +0 -7
- package/dist/peer/idle-state.js +0 -14
- package/dist/peer/index.d.ts +0 -71
- package/dist/peer/index.js +0 -176
- package/dist/peer/state.d.ts +0 -23
- package/dist/peer/state.js +0 -63
- package/dist/peer/types.d.ts +0 -43
- package/dist/peer/types.js +0 -1
- package/dist/peer/waiting-for-answer-state.d.ts +0 -17
- package/dist/peer/waiting-for-answer-state.js +0 -60
- package/dist/rondevu.d.ts +0 -184
- package/dist/rondevu.js +0 -171
- package/dist/service-pool.d.ts +0 -123
- package/dist/service-pool.js +0 -488
- package/dist/usernames.d.ts +0 -79
- 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
|
+
}
|
package/dist/signaler.js
ADDED
|
@@ -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
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -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,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.
|
|
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
|
-
"
|
|
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
|
-
}
|