@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
package/dist/offer-pool.js
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
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.peerConnections = new Map();
|
|
14
|
-
this.dataChannels = new Map();
|
|
15
|
-
this.polling = false;
|
|
16
|
-
this.lastPollTime = 0;
|
|
17
|
-
this.pollingInterval = options.pollingInterval || 2000;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Add offers to the pool with their peer connections and data channels
|
|
21
|
-
*/
|
|
22
|
-
async addOffers(offers, peerConnections, dataChannels) {
|
|
23
|
-
for (let i = 0; i < offers.length; i++) {
|
|
24
|
-
const offer = offers[i];
|
|
25
|
-
this.offers.set(offer.id, offer);
|
|
26
|
-
if (peerConnections && peerConnections[i]) {
|
|
27
|
-
this.peerConnections.set(offer.id, peerConnections[i]);
|
|
28
|
-
}
|
|
29
|
-
if (dataChannels && dataChannels[i]) {
|
|
30
|
-
this.dataChannels.set(offer.id, dataChannels[i]);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Start polling for answers
|
|
36
|
-
*/
|
|
37
|
-
async start() {
|
|
38
|
-
if (this.polling) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
this.polling = true;
|
|
42
|
-
// Do an immediate poll
|
|
43
|
-
await this.poll().catch((error) => {
|
|
44
|
-
this.options.onError(error, 'initial-poll');
|
|
45
|
-
});
|
|
46
|
-
// Start polling interval
|
|
47
|
-
this.pollingTimer = setInterval(async () => {
|
|
48
|
-
if (this.polling) {
|
|
49
|
-
await this.poll().catch((error) => {
|
|
50
|
-
this.options.onError(error, 'poll');
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}, this.pollingInterval);
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Stop polling for answers
|
|
57
|
-
*/
|
|
58
|
-
async stop() {
|
|
59
|
-
this.polling = false;
|
|
60
|
-
if (this.pollingTimer) {
|
|
61
|
-
clearInterval(this.pollingTimer);
|
|
62
|
-
this.pollingTimer = undefined;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Poll for answers and refill the pool if needed
|
|
67
|
-
*/
|
|
68
|
-
async poll() {
|
|
69
|
-
try {
|
|
70
|
-
// Get all answers from server
|
|
71
|
-
const answers = await this.offersApi.getAnswers();
|
|
72
|
-
// Filter for our pool's offers
|
|
73
|
-
const myAnswers = answers.filter(a => this.offers.has(a.offerId));
|
|
74
|
-
// Process each answer
|
|
75
|
-
for (const answer of myAnswers) {
|
|
76
|
-
// Get the original offer, peer connection, and data channel
|
|
77
|
-
const offer = this.offers.get(answer.offerId);
|
|
78
|
-
const pc = this.peerConnections.get(answer.offerId);
|
|
79
|
-
const channel = this.dataChannels.get(answer.offerId);
|
|
80
|
-
if (!offer || !pc) {
|
|
81
|
-
continue; // Offer or peer connection already consumed, skip
|
|
82
|
-
}
|
|
83
|
-
// Remove from pool BEFORE processing to prevent duplicate processing
|
|
84
|
-
this.offers.delete(answer.offerId);
|
|
85
|
-
this.peerConnections.delete(answer.offerId);
|
|
86
|
-
this.dataChannels.delete(answer.offerId);
|
|
87
|
-
// Notify ServicePool with answer, original peer connection, and data channel
|
|
88
|
-
await this.options.onAnswered({
|
|
89
|
-
offerId: answer.offerId,
|
|
90
|
-
answererId: answer.answererId,
|
|
91
|
-
sdp: answer.sdp,
|
|
92
|
-
peerConnection: pc,
|
|
93
|
-
dataChannel: channel,
|
|
94
|
-
answeredAt: answer.answeredAt
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
// Immediate refill if below pool size
|
|
98
|
-
if (this.offers.size < this.options.poolSize) {
|
|
99
|
-
const needed = this.options.poolSize - this.offers.size;
|
|
100
|
-
try {
|
|
101
|
-
const result = await this.options.onRefill(needed);
|
|
102
|
-
await this.addOffers(result.offers, result.peerConnections, result.dataChannels);
|
|
103
|
-
}
|
|
104
|
-
catch (refillError) {
|
|
105
|
-
this.options.onError(refillError, 'refill');
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
this.lastPollTime = Date.now();
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
// Don't crash the pool on errors - let error handler deal with it
|
|
112
|
-
this.options.onError(error, 'poll');
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Get the current number of active offers in the pool
|
|
117
|
-
*/
|
|
118
|
-
getActiveOfferCount() {
|
|
119
|
-
return this.offers.size;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Get all active offer IDs
|
|
123
|
-
*/
|
|
124
|
-
getActiveOfferIds() {
|
|
125
|
-
return Array.from(this.offers.keys());
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Get all active peer connections
|
|
129
|
-
*/
|
|
130
|
-
getActivePeerConnections() {
|
|
131
|
-
return Array.from(this.peerConnections.values());
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Get the last poll timestamp
|
|
135
|
-
*/
|
|
136
|
-
getLastPollTime() {
|
|
137
|
-
return this.lastPollTime;
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Check if the pool is currently polling
|
|
141
|
-
*/
|
|
142
|
-
isPolling() {
|
|
143
|
-
return this.polling;
|
|
144
|
-
}
|
|
145
|
-
}
|
package/dist/offers.d.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { Credentials, FetchFunction } from './auth.js';
|
|
2
|
-
export interface CreateOfferRequest {
|
|
3
|
-
sdp: string;
|
|
4
|
-
topics: string[];
|
|
5
|
-
ttl?: number;
|
|
6
|
-
secret?: string;
|
|
7
|
-
info?: string;
|
|
8
|
-
}
|
|
9
|
-
export interface Offer {
|
|
10
|
-
id: string;
|
|
11
|
-
peerId: string;
|
|
12
|
-
sdp: string;
|
|
13
|
-
topics: string[];
|
|
14
|
-
createdAt?: number;
|
|
15
|
-
expiresAt: number;
|
|
16
|
-
lastSeen: number;
|
|
17
|
-
secret?: string;
|
|
18
|
-
hasSecret?: boolean;
|
|
19
|
-
info?: string;
|
|
20
|
-
answererPeerId?: string;
|
|
21
|
-
answerSdp?: string;
|
|
22
|
-
answeredAt?: number;
|
|
23
|
-
}
|
|
24
|
-
export interface IceCandidate {
|
|
25
|
-
candidate: any;
|
|
26
|
-
peerId: string;
|
|
27
|
-
role: 'offerer' | 'answerer';
|
|
28
|
-
createdAt: number;
|
|
29
|
-
}
|
|
30
|
-
export interface TopicInfo {
|
|
31
|
-
topic: string;
|
|
32
|
-
activePeers: number;
|
|
33
|
-
}
|
|
34
|
-
export declare class RondevuOffers {
|
|
35
|
-
private baseUrl;
|
|
36
|
-
private credentials;
|
|
37
|
-
private fetchFn;
|
|
38
|
-
constructor(baseUrl: string, credentials: Credentials, fetchFn?: FetchFunction);
|
|
39
|
-
/**
|
|
40
|
-
* Create one or more offers
|
|
41
|
-
*/
|
|
42
|
-
create(offers: CreateOfferRequest[]): Promise<Offer[]>;
|
|
43
|
-
/**
|
|
44
|
-
* Find offers by topic with optional bloom filter
|
|
45
|
-
*/
|
|
46
|
-
findByTopic(topic: string, options?: {
|
|
47
|
-
bloomFilter?: Uint8Array;
|
|
48
|
-
limit?: number;
|
|
49
|
-
}): Promise<Offer[]>;
|
|
50
|
-
/**
|
|
51
|
-
* Get all offers from a specific peer
|
|
52
|
-
*/
|
|
53
|
-
getByPeerId(peerId: string): Promise<{
|
|
54
|
-
offers: Offer[];
|
|
55
|
-
topics: string[];
|
|
56
|
-
}>;
|
|
57
|
-
/**
|
|
58
|
-
* Get topics with active peer counts (paginated)
|
|
59
|
-
*/
|
|
60
|
-
getTopics(options?: {
|
|
61
|
-
limit?: number;
|
|
62
|
-
offset?: number;
|
|
63
|
-
startsWith?: string;
|
|
64
|
-
}): Promise<{
|
|
65
|
-
topics: TopicInfo[];
|
|
66
|
-
total: number;
|
|
67
|
-
limit: number;
|
|
68
|
-
offset: number;
|
|
69
|
-
startsWith?: string;
|
|
70
|
-
}>;
|
|
71
|
-
/**
|
|
72
|
-
* Get own offers
|
|
73
|
-
*/
|
|
74
|
-
getMine(): Promise<Offer[]>;
|
|
75
|
-
/**
|
|
76
|
-
* Delete an offer
|
|
77
|
-
*/
|
|
78
|
-
delete(offerId: string): Promise<void>;
|
|
79
|
-
/**
|
|
80
|
-
* Answer an offer
|
|
81
|
-
*/
|
|
82
|
-
answer(offerId: string, sdp: string, secret?: string): Promise<void>;
|
|
83
|
-
/**
|
|
84
|
-
* Get answers to your offers
|
|
85
|
-
*/
|
|
86
|
-
getAnswers(): Promise<Array<{
|
|
87
|
-
offerId: string;
|
|
88
|
-
answererId: string;
|
|
89
|
-
sdp: string;
|
|
90
|
-
answeredAt: number;
|
|
91
|
-
topics: string[];
|
|
92
|
-
}>>;
|
|
93
|
-
/**
|
|
94
|
-
* Post ICE candidates for an offer
|
|
95
|
-
*/
|
|
96
|
-
addIceCandidates(offerId: string, candidates: any[]): Promise<void>;
|
|
97
|
-
/**
|
|
98
|
-
* Get ICE candidates for an offer
|
|
99
|
-
*/
|
|
100
|
-
getIceCandidates(offerId: string, since?: number): Promise<IceCandidate[]>;
|
|
101
|
-
}
|
package/dist/offers.js
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { RondevuAuth } from './auth.js';
|
|
2
|
-
export class RondevuOffers {
|
|
3
|
-
constructor(baseUrl, credentials, fetchFn) {
|
|
4
|
-
this.baseUrl = baseUrl;
|
|
5
|
-
this.credentials = credentials;
|
|
6
|
-
// Use provided fetch or fall back to global fetch
|
|
7
|
-
this.fetchFn = fetchFn || ((...args) => {
|
|
8
|
-
if (typeof globalThis.fetch === 'function') {
|
|
9
|
-
return globalThis.fetch(...args);
|
|
10
|
-
}
|
|
11
|
-
throw new Error('fetch is not available. Please provide a fetch implementation in the constructor options.');
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Create one or more offers
|
|
16
|
-
*/
|
|
17
|
-
async create(offers) {
|
|
18
|
-
const response = await this.fetchFn(`${this.baseUrl}/offers`, {
|
|
19
|
-
method: 'POST',
|
|
20
|
-
headers: {
|
|
21
|
-
'Content-Type': 'application/json',
|
|
22
|
-
Authorization: RondevuAuth.createAuthHeader(this.credentials),
|
|
23
|
-
},
|
|
24
|
-
body: JSON.stringify({ offers }),
|
|
25
|
-
});
|
|
26
|
-
if (!response.ok) {
|
|
27
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
28
|
-
throw new Error(`Failed to create offers: ${error.error || response.statusText}`);
|
|
29
|
-
}
|
|
30
|
-
const data = await response.json();
|
|
31
|
-
return data.offers;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Find offers by topic with optional bloom filter
|
|
35
|
-
*/
|
|
36
|
-
async findByTopic(topic, options) {
|
|
37
|
-
const params = new URLSearchParams();
|
|
38
|
-
if (options?.bloomFilter) {
|
|
39
|
-
// Convert to base64
|
|
40
|
-
const binaryString = String.fromCharCode(...Array.from(options.bloomFilter));
|
|
41
|
-
const base64 = typeof btoa !== 'undefined'
|
|
42
|
-
? btoa(binaryString)
|
|
43
|
-
: (typeof Buffer !== 'undefined' ? Buffer.from(options.bloomFilter).toString('base64') : '');
|
|
44
|
-
params.set('bloom', base64);
|
|
45
|
-
}
|
|
46
|
-
if (options?.limit) {
|
|
47
|
-
params.set('limit', options.limit.toString());
|
|
48
|
-
}
|
|
49
|
-
const url = `${this.baseUrl}/offers/by-topic/${encodeURIComponent(topic)}${params.toString() ? '?' + params.toString() : ''}`;
|
|
50
|
-
const response = await this.fetchFn(url, {
|
|
51
|
-
method: 'GET',
|
|
52
|
-
});
|
|
53
|
-
if (!response.ok) {
|
|
54
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
55
|
-
throw new Error(`Failed to find offers: ${error.error || response.statusText}`);
|
|
56
|
-
}
|
|
57
|
-
const data = await response.json();
|
|
58
|
-
return data.offers;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Get all offers from a specific peer
|
|
62
|
-
*/
|
|
63
|
-
async getByPeerId(peerId) {
|
|
64
|
-
const response = await this.fetchFn(`${this.baseUrl}/peers/${encodeURIComponent(peerId)}/offers`, {
|
|
65
|
-
method: 'GET',
|
|
66
|
-
});
|
|
67
|
-
if (!response.ok) {
|
|
68
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
69
|
-
throw new Error(`Failed to get peer offers: ${error.error || response.statusText}`);
|
|
70
|
-
}
|
|
71
|
-
return await response.json();
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Get topics with active peer counts (paginated)
|
|
75
|
-
*/
|
|
76
|
-
async getTopics(options) {
|
|
77
|
-
const params = new URLSearchParams();
|
|
78
|
-
if (options?.limit) {
|
|
79
|
-
params.set('limit', options.limit.toString());
|
|
80
|
-
}
|
|
81
|
-
if (options?.offset) {
|
|
82
|
-
params.set('offset', options.offset.toString());
|
|
83
|
-
}
|
|
84
|
-
if (options?.startsWith) {
|
|
85
|
-
params.set('startsWith', options.startsWith);
|
|
86
|
-
}
|
|
87
|
-
const url = `${this.baseUrl}/topics${params.toString() ? '?' + params.toString() : ''}`;
|
|
88
|
-
const response = await this.fetchFn(url, {
|
|
89
|
-
method: 'GET',
|
|
90
|
-
});
|
|
91
|
-
if (!response.ok) {
|
|
92
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
93
|
-
throw new Error(`Failed to get topics: ${error.error || response.statusText}`);
|
|
94
|
-
}
|
|
95
|
-
return await response.json();
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Get own offers
|
|
99
|
-
*/
|
|
100
|
-
async getMine() {
|
|
101
|
-
const response = await this.fetchFn(`${this.baseUrl}/offers/mine`, {
|
|
102
|
-
method: 'GET',
|
|
103
|
-
headers: {
|
|
104
|
-
Authorization: RondevuAuth.createAuthHeader(this.credentials),
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
if (!response.ok) {
|
|
108
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
109
|
-
throw new Error(`Failed to get own offers: ${error.error || response.statusText}`);
|
|
110
|
-
}
|
|
111
|
-
const data = await response.json();
|
|
112
|
-
return data.offers;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Delete an offer
|
|
116
|
-
*/
|
|
117
|
-
async delete(offerId) {
|
|
118
|
-
const response = await this.fetchFn(`${this.baseUrl}/offers/${encodeURIComponent(offerId)}`, {
|
|
119
|
-
method: 'DELETE',
|
|
120
|
-
headers: {
|
|
121
|
-
Authorization: RondevuAuth.createAuthHeader(this.credentials),
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
if (!response.ok) {
|
|
125
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
126
|
-
throw new Error(`Failed to delete offer: ${error.error || response.statusText}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Answer an offer
|
|
131
|
-
*/
|
|
132
|
-
async answer(offerId, sdp, secret) {
|
|
133
|
-
const response = await this.fetchFn(`${this.baseUrl}/offers/${encodeURIComponent(offerId)}/answer`, {
|
|
134
|
-
method: 'POST',
|
|
135
|
-
headers: {
|
|
136
|
-
'Content-Type': 'application/json',
|
|
137
|
-
Authorization: RondevuAuth.createAuthHeader(this.credentials),
|
|
138
|
-
},
|
|
139
|
-
body: JSON.stringify({ sdp, secret }),
|
|
140
|
-
});
|
|
141
|
-
if (!response.ok) {
|
|
142
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
143
|
-
throw new Error(`Failed to answer offer: ${error.error || response.statusText}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Get answers to your offers
|
|
148
|
-
*/
|
|
149
|
-
async getAnswers() {
|
|
150
|
-
const response = await this.fetchFn(`${this.baseUrl}/offers/answers`, {
|
|
151
|
-
method: 'GET',
|
|
152
|
-
headers: {
|
|
153
|
-
Authorization: RondevuAuth.createAuthHeader(this.credentials),
|
|
154
|
-
},
|
|
155
|
-
});
|
|
156
|
-
if (!response.ok) {
|
|
157
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
158
|
-
throw new Error(`Failed to get answers: ${error.error || response.statusText}`);
|
|
159
|
-
}
|
|
160
|
-
const data = await response.json();
|
|
161
|
-
return data.answers;
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Post ICE candidates for an offer
|
|
165
|
-
*/
|
|
166
|
-
async addIceCandidates(offerId, candidates) {
|
|
167
|
-
const response = await this.fetchFn(`${this.baseUrl}/offers/${encodeURIComponent(offerId)}/ice-candidates`, {
|
|
168
|
-
method: 'POST',
|
|
169
|
-
headers: {
|
|
170
|
-
'Content-Type': 'application/json',
|
|
171
|
-
Authorization: RondevuAuth.createAuthHeader(this.credentials),
|
|
172
|
-
},
|
|
173
|
-
body: JSON.stringify({ candidates }),
|
|
174
|
-
});
|
|
175
|
-
if (!response.ok) {
|
|
176
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
177
|
-
throw new Error(`Failed to add ICE candidates: ${error.error || response.statusText}`);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Get ICE candidates for an offer
|
|
182
|
-
*/
|
|
183
|
-
async getIceCandidates(offerId, since) {
|
|
184
|
-
const params = new URLSearchParams();
|
|
185
|
-
if (since !== undefined) {
|
|
186
|
-
params.set('since', since.toString());
|
|
187
|
-
}
|
|
188
|
-
const url = `${this.baseUrl}/offers/${encodeURIComponent(offerId)}/ice-candidates${params.toString() ? '?' + params.toString() : ''}`;
|
|
189
|
-
const response = await this.fetchFn(url, {
|
|
190
|
-
method: 'GET',
|
|
191
|
-
headers: {
|
|
192
|
-
Authorization: RondevuAuth.createAuthHeader(this.credentials),
|
|
193
|
-
},
|
|
194
|
-
});
|
|
195
|
-
if (!response.ok) {
|
|
196
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
197
|
-
throw new Error(`Failed to get ICE candidates: ${error.error || response.statusText}`);
|
|
198
|
-
}
|
|
199
|
-
const data = await response.json();
|
|
200
|
-
return data.candidates;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { PeerState } from './state.js';
|
|
2
|
-
import type { PeerOptions } from './types.js';
|
|
3
|
-
import type RondevuPeer from './index.js';
|
|
4
|
-
/**
|
|
5
|
-
* Answering an offer and sending to server
|
|
6
|
-
*/
|
|
7
|
-
export declare class AnsweringState extends PeerState {
|
|
8
|
-
constructor(peer: RondevuPeer);
|
|
9
|
-
get name(): string;
|
|
10
|
-
answer(offerId: string, offerSdp: string, options: PeerOptions): Promise<void>;
|
|
11
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { PeerState } from './state.js';
|
|
2
|
-
/**
|
|
3
|
-
* Answering an offer and sending to server
|
|
4
|
-
*/
|
|
5
|
-
export class AnsweringState extends PeerState {
|
|
6
|
-
constructor(peer) {
|
|
7
|
-
super(peer);
|
|
8
|
-
}
|
|
9
|
-
get name() { return 'answering'; }
|
|
10
|
-
async answer(offerId, offerSdp, options) {
|
|
11
|
-
try {
|
|
12
|
-
this.peer.role = 'answerer';
|
|
13
|
-
this.peer.offerId = offerId;
|
|
14
|
-
// Set remote description
|
|
15
|
-
await this.peer.pc.setRemoteDescription({
|
|
16
|
-
type: 'offer',
|
|
17
|
-
sdp: offerSdp
|
|
18
|
-
});
|
|
19
|
-
// Create answer
|
|
20
|
-
const answer = await this.peer.pc.createAnswer();
|
|
21
|
-
// Send answer to server BEFORE setLocalDescription
|
|
22
|
-
// This registers us as the answerer so ICE candidates will be accepted
|
|
23
|
-
await this.peer.offersApi.answer(offerId, answer.sdp, options.secret);
|
|
24
|
-
// Enable trickle ICE - set up handler before ICE gathering starts
|
|
25
|
-
this.setupIceCandidateHandler();
|
|
26
|
-
// Set local description - ICE gathering starts here
|
|
27
|
-
// Server already knows we're the answerer, so candidates will be accepted
|
|
28
|
-
await this.peer.pc.setLocalDescription(answer);
|
|
29
|
-
// Transition to exchanging ICE
|
|
30
|
-
const { ExchangingIceState } = await import('./exchanging-ice-state.js');
|
|
31
|
-
this.peer.setState(new ExchangingIceState(this.peer, offerId, options));
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
const { FailedState } = await import('./failed-state.js');
|
|
35
|
-
this.peer.setState(new FailedState(this.peer, error));
|
|
36
|
-
throw error;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { PeerState } from './state.js';
|
|
2
|
-
/**
|
|
3
|
-
* Connected state - peer connection is established
|
|
4
|
-
*/
|
|
5
|
-
export class ConnectedState extends PeerState {
|
|
6
|
-
get name() { return 'connected'; }
|
|
7
|
-
cleanup() {
|
|
8
|
-
// Keep connection alive, but stop any polling
|
|
9
|
-
// The peer connection will handle disconnects via onconnectionstatechange
|
|
10
|
-
}
|
|
11
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { PeerState } from './state.js';
|
|
2
|
-
import type { PeerOptions } from './types.js';
|
|
3
|
-
import type RondevuPeer from './index.js';
|
|
4
|
-
/**
|
|
5
|
-
* Creating offer and sending to server
|
|
6
|
-
*/
|
|
7
|
-
export declare class CreatingOfferState extends PeerState {
|
|
8
|
-
private options;
|
|
9
|
-
constructor(peer: RondevuPeer, options: PeerOptions);
|
|
10
|
-
get name(): string;
|
|
11
|
-
createOffer(options: PeerOptions): Promise<string>;
|
|
12
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { PeerState } from './state.js';
|
|
2
|
-
/**
|
|
3
|
-
* Creating offer and sending to server
|
|
4
|
-
*/
|
|
5
|
-
export class CreatingOfferState extends PeerState {
|
|
6
|
-
constructor(peer, options) {
|
|
7
|
-
super(peer);
|
|
8
|
-
this.options = options;
|
|
9
|
-
}
|
|
10
|
-
get name() { return 'creating-offer'; }
|
|
11
|
-
async createOffer(options) {
|
|
12
|
-
try {
|
|
13
|
-
this.peer.role = 'offerer';
|
|
14
|
-
// Create data channel if requested
|
|
15
|
-
if (options.createDataChannel !== false) {
|
|
16
|
-
const channel = this.peer.pc.createDataChannel(options.dataChannelLabel || 'data');
|
|
17
|
-
this.peer.emitEvent('datachannel', channel);
|
|
18
|
-
}
|
|
19
|
-
// Enable trickle ICE - set up handler before ICE gathering starts
|
|
20
|
-
// Handler will check this.peer.offerId before sending
|
|
21
|
-
this.setupIceCandidateHandler();
|
|
22
|
-
// Create WebRTC offer
|
|
23
|
-
const offer = await this.peer.pc.createOffer();
|
|
24
|
-
await this.peer.pc.setLocalDescription(offer); // ICE gathering starts here
|
|
25
|
-
// Send offer to server immediately (don't wait for ICE)
|
|
26
|
-
const offers = await this.peer.offersApi.create([{
|
|
27
|
-
sdp: offer.sdp,
|
|
28
|
-
topics: options.topics,
|
|
29
|
-
ttl: options.ttl || 300000,
|
|
30
|
-
secret: options.secret
|
|
31
|
-
}]);
|
|
32
|
-
const offerId = offers[0].id;
|
|
33
|
-
this.peer.offerId = offerId; // Now handler can send candidates
|
|
34
|
-
// Transition to waiting for answer
|
|
35
|
-
const { WaitingForAnswerState } = await import('./waiting-for-answer-state.js');
|
|
36
|
-
this.peer.setState(new WaitingForAnswerState(this.peer, offerId, options));
|
|
37
|
-
return offerId;
|
|
38
|
-
}
|
|
39
|
-
catch (error) {
|
|
40
|
-
const { FailedState } = await import('./failed-state.js');
|
|
41
|
-
this.peer.setState(new FailedState(this.peer, error));
|
|
42
|
-
throw error;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { PeerState } from './state.js';
|
|
2
|
-
import type { PeerOptions } from './types.js';
|
|
3
|
-
import type RondevuPeer from './index.js';
|
|
4
|
-
/**
|
|
5
|
-
* Exchanging ICE candidates and waiting for connection
|
|
6
|
-
*/
|
|
7
|
-
export declare class ExchangingIceState extends PeerState {
|
|
8
|
-
private offerId;
|
|
9
|
-
private options;
|
|
10
|
-
private pollingInterval?;
|
|
11
|
-
private timeout?;
|
|
12
|
-
private lastIceTimestamp;
|
|
13
|
-
constructor(peer: RondevuPeer, offerId: string, options: PeerOptions);
|
|
14
|
-
get name(): string;
|
|
15
|
-
private startPolling;
|
|
16
|
-
cleanup(): void;
|
|
17
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { PeerState } from './state.js';
|
|
2
|
-
/**
|
|
3
|
-
* Exchanging ICE candidates and waiting for connection
|
|
4
|
-
*/
|
|
5
|
-
export class ExchangingIceState extends PeerState {
|
|
6
|
-
constructor(peer, offerId, options) {
|
|
7
|
-
super(peer);
|
|
8
|
-
this.offerId = offerId;
|
|
9
|
-
this.options = options;
|
|
10
|
-
this.lastIceTimestamp = 0;
|
|
11
|
-
this.startPolling();
|
|
12
|
-
}
|
|
13
|
-
get name() { return 'exchanging-ice'; }
|
|
14
|
-
startPolling() {
|
|
15
|
-
const connectionTimeout = this.options.timeouts?.iceConnection || 30000;
|
|
16
|
-
this.timeout = setTimeout(async () => {
|
|
17
|
-
this.cleanup();
|
|
18
|
-
const { FailedState } = await import('./failed-state.js');
|
|
19
|
-
this.peer.setState(new FailedState(this.peer, new Error('ICE connection timeout')));
|
|
20
|
-
}, connectionTimeout);
|
|
21
|
-
this.pollingInterval = setInterval(async () => {
|
|
22
|
-
try {
|
|
23
|
-
const candidates = await this.peer.offersApi.getIceCandidates(this.offerId, this.lastIceTimestamp);
|
|
24
|
-
if (candidates.length > 0) {
|
|
25
|
-
console.log(`📥 Received ${candidates.length} remote ICE candidate(s)`);
|
|
26
|
-
}
|
|
27
|
-
for (const cand of candidates) {
|
|
28
|
-
if (cand.candidate && cand.candidate.candidate && cand.candidate.candidate !== '') {
|
|
29
|
-
const type = cand.candidate.candidate.includes('typ host') ? 'host' :
|
|
30
|
-
cand.candidate.candidate.includes('typ srflx') ? 'srflx' :
|
|
31
|
-
cand.candidate.candidate.includes('typ relay') ? 'relay' : 'unknown';
|
|
32
|
-
console.log(`🧊 Adding remote ${type} ICE candidate:`, cand.candidate.candidate);
|
|
33
|
-
try {
|
|
34
|
-
await this.peer.pc.addIceCandidate(new this.peer.RTCIceCandidate(cand.candidate));
|
|
35
|
-
console.log(`✅ Added remote ${type} ICE candidate`);
|
|
36
|
-
this.lastIceTimestamp = cand.createdAt;
|
|
37
|
-
}
|
|
38
|
-
catch (err) {
|
|
39
|
-
console.warn(`⚠️ Failed to add remote ${type} ICE candidate:`, err);
|
|
40
|
-
this.lastIceTimestamp = cand.createdAt;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
this.lastIceTimestamp = cand.createdAt;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
catch (err) {
|
|
49
|
-
console.error('❌ Error polling for ICE candidates:', err);
|
|
50
|
-
if (err instanceof Error && err.message.includes('not found')) {
|
|
51
|
-
this.cleanup();
|
|
52
|
-
const { FailedState } = await import('./failed-state.js');
|
|
53
|
-
this.peer.setState(new FailedState(this.peer, new Error('Offer expired or not found')));
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}, 1000);
|
|
57
|
-
}
|
|
58
|
-
cleanup() {
|
|
59
|
-
if (this.pollingInterval)
|
|
60
|
-
clearInterval(this.pollingInterval);
|
|
61
|
-
if (this.timeout)
|
|
62
|
-
clearTimeout(this.timeout);
|
|
63
|
-
}
|
|
64
|
-
}
|