@xtr-dev/rondevu-webtorrent 0.0.1 → 0.0.3
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/RondevuConnectionManager.d.ts +13 -13
- package/dist/RondevuConnectionManager.js +114 -102
- package/dist/index.d.ts +1 -1
- package/package.json +2 -2
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import WebTorrent from 'webtorrent';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Credential type for rondevu authentication
|
|
4
|
+
*/
|
|
5
|
+
export interface Credential {
|
|
6
|
+
name: string;
|
|
7
|
+
secret: string;
|
|
8
|
+
}
|
|
3
9
|
/**
|
|
4
10
|
* WebRTC polyfill interface for Node.js environments
|
|
5
11
|
*/
|
|
6
12
|
export interface WebRTCPolyfill {
|
|
7
13
|
RTCPeerConnection: typeof RTCPeerConnection;
|
|
8
|
-
RTCSessionDescription: typeof RTCSessionDescription;
|
|
9
14
|
RTCIceCandidate: typeof RTCIceCandidate;
|
|
10
15
|
}
|
|
11
16
|
/**
|
|
@@ -35,7 +40,7 @@ export interface RondevuConnectionManagerOptions {
|
|
|
35
40
|
/**
|
|
36
41
|
* Existing rondevu credentials to reuse
|
|
37
42
|
*/
|
|
38
|
-
|
|
43
|
+
credential?: Credential;
|
|
39
44
|
/**
|
|
40
45
|
* WebRTC polyfill for Node.js (e.g., @roamhq/wrtc)
|
|
41
46
|
* Required for WebRTC functionality in Node.js environments
|
|
@@ -50,15 +55,14 @@ export interface RondevuConnectionManagerOptions {
|
|
|
50
55
|
export declare class RondevuConnectionManager {
|
|
51
56
|
private client;
|
|
52
57
|
private rondevu;
|
|
58
|
+
private torrentOfferHandles;
|
|
53
59
|
private torrentPeers;
|
|
54
|
-
private torrentOffers;
|
|
55
60
|
private torrentCleanupHandlers;
|
|
56
61
|
private refreshTimers;
|
|
57
|
-
private torrentBloomFilters;
|
|
58
62
|
private options;
|
|
59
63
|
constructor(client: WebTorrent.Instance, options?: RondevuConnectionManagerOptions);
|
|
60
64
|
/**
|
|
61
|
-
* Initialize the connection manager by setting up event listeners and
|
|
65
|
+
* Initialize the connection manager by setting up event listeners and connecting to rondevu
|
|
62
66
|
*/
|
|
63
67
|
private initialize;
|
|
64
68
|
/**
|
|
@@ -69,10 +73,6 @@ export declare class RondevuConnectionManager {
|
|
|
69
73
|
* Discover peers for a torrent using rondevu
|
|
70
74
|
*/
|
|
71
75
|
private discoverPeersForTorrent;
|
|
72
|
-
/**
|
|
73
|
-
* Set up event handlers for a rondevu peer
|
|
74
|
-
*/
|
|
75
|
-
private setupPeerHandlers;
|
|
76
76
|
/**
|
|
77
77
|
* Log a message if debug mode is enabled
|
|
78
78
|
*/
|
|
@@ -86,7 +86,7 @@ export declare class RondevuConnectionManager {
|
|
|
86
86
|
*/
|
|
87
87
|
getStats(): {
|
|
88
88
|
activeTorrents: number;
|
|
89
|
-
|
|
89
|
+
username: string | undefined;
|
|
90
90
|
rondevuServer: string;
|
|
91
91
|
torrents: {
|
|
92
92
|
infoHash: string;
|
|
@@ -94,9 +94,9 @@ export declare class RondevuConnectionManager {
|
|
|
94
94
|
}[];
|
|
95
95
|
};
|
|
96
96
|
/**
|
|
97
|
-
* Get the rondevu
|
|
97
|
+
* Get the rondevu credential for persistence
|
|
98
98
|
*/
|
|
99
|
-
|
|
99
|
+
getCredential(): Credential | undefined;
|
|
100
100
|
/**
|
|
101
101
|
* Clean up all resources and disconnect from rondevu
|
|
102
102
|
*/
|
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
import { Rondevu
|
|
1
|
+
import { Rondevu } from '@xtr-dev/rondevu-client';
|
|
2
|
+
/**
|
|
3
|
+
* Simple WebRTC adapter for Node.js using wrtc polyfill
|
|
4
|
+
*/
|
|
5
|
+
class NodeWebRTCAdapter {
|
|
6
|
+
constructor(polyfills) {
|
|
7
|
+
this.polyfills = polyfills;
|
|
8
|
+
}
|
|
9
|
+
createPeerConnection(config) {
|
|
10
|
+
return new this.polyfills.RTCPeerConnection(config);
|
|
11
|
+
}
|
|
12
|
+
createIceCandidate(candidateInit) {
|
|
13
|
+
return new this.polyfills.RTCIceCandidate(candidateInit);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
2
16
|
/**
|
|
3
17
|
* Connection manager that uses Rondevu for WebTorrent peer discovery via WebRTC.
|
|
4
18
|
* This class acts as a plugin for WebTorrent, automatically discovering
|
|
@@ -6,11 +20,11 @@ import { Rondevu, BloomFilter } from '@xtr-dev/rondevu-client';
|
|
|
6
20
|
*/
|
|
7
21
|
export class RondevuConnectionManager {
|
|
8
22
|
constructor(client, options = {}) {
|
|
23
|
+
this.rondevu = null;
|
|
24
|
+
this.torrentOfferHandles = new Map();
|
|
9
25
|
this.torrentPeers = new Map();
|
|
10
|
-
this.torrentOffers = new Map();
|
|
11
26
|
this.torrentCleanupHandlers = new Map();
|
|
12
27
|
this.refreshTimers = new Map();
|
|
13
|
-
this.torrentBloomFilters = new Map();
|
|
14
28
|
this.client = client;
|
|
15
29
|
this.options = {
|
|
16
30
|
rondevuServer: options.rondevuServer,
|
|
@@ -19,32 +33,36 @@ export class RondevuConnectionManager {
|
|
|
19
33
|
refreshInterval: options.refreshInterval ?? 30000,
|
|
20
34
|
rtcConfig: options.rtcConfig,
|
|
21
35
|
wrtc: options.wrtc,
|
|
36
|
+
credential: options.credential,
|
|
22
37
|
};
|
|
23
|
-
this.rondevu = new Rondevu({
|
|
24
|
-
baseUrl: options.rondevuServer,
|
|
25
|
-
credentials: options.credentials,
|
|
26
|
-
RTCPeerConnection: options.wrtc?.RTCPeerConnection,
|
|
27
|
-
RTCSessionDescription: options.wrtc?.RTCSessionDescription,
|
|
28
|
-
RTCIceCandidate: options.wrtc?.RTCIceCandidate,
|
|
29
|
-
});
|
|
30
38
|
this.initialize();
|
|
31
39
|
}
|
|
32
40
|
/**
|
|
33
|
-
* Initialize the connection manager by setting up event listeners and
|
|
41
|
+
* Initialize the connection manager by setting up event listeners and connecting to rondevu
|
|
34
42
|
*/
|
|
35
43
|
async initialize() {
|
|
36
44
|
this.log('Initializing RondevuConnectionManager');
|
|
37
45
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
// Build connection options
|
|
47
|
+
const connectOptions = {
|
|
48
|
+
apiUrl: this.options.rondevuServer,
|
|
49
|
+
credential: this.options.credential,
|
|
50
|
+
debug: this.options.debug,
|
|
51
|
+
};
|
|
52
|
+
// Add WebRTC adapter if wrtc polyfill provided
|
|
53
|
+
if (this.options.wrtc) {
|
|
54
|
+
connectOptions.webrtcAdapter = new NodeWebRTCAdapter(this.options.wrtc);
|
|
41
55
|
}
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
// Add ICE servers if custom config provided
|
|
57
|
+
if (this.options.rtcConfig?.iceServers) {
|
|
58
|
+
connectOptions.iceServers = this.options.rtcConfig.iceServers;
|
|
44
59
|
}
|
|
60
|
+
// Connect to rondevu (auto-generates credentials if not provided)
|
|
61
|
+
this.rondevu = await Rondevu.connect(connectOptions);
|
|
62
|
+
this.log(`Connected to rondevu as: ${this.rondevu.getName()}`);
|
|
45
63
|
}
|
|
46
64
|
catch (error) {
|
|
47
|
-
this.log(`Failed to
|
|
65
|
+
this.log(`Failed to connect to rondevu: ${error}`);
|
|
48
66
|
return;
|
|
49
67
|
}
|
|
50
68
|
// Listen for new torrents being added
|
|
@@ -60,12 +78,14 @@ export class RondevuConnectionManager {
|
|
|
60
78
|
* Handle a torrent being added to the WebTorrent client
|
|
61
79
|
*/
|
|
62
80
|
async handleTorrentAdded(torrent) {
|
|
81
|
+
if (!this.rondevu) {
|
|
82
|
+
this.log('Rondevu not initialized, skipping torrent');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
63
85
|
const infoHash = torrent.infoHash;
|
|
64
86
|
this.log(`Torrent added: ${infoHash}`);
|
|
65
87
|
// Initialize tracking for this torrent
|
|
66
88
|
this.torrentPeers.set(infoHash, new Set());
|
|
67
|
-
this.torrentOffers.set(infoHash, []);
|
|
68
|
-
this.torrentBloomFilters.set(infoHash, new BloomFilter(1024, 3));
|
|
69
89
|
// Start discovering peers and creating offers
|
|
70
90
|
await this.discoverPeersForTorrent(torrent);
|
|
71
91
|
// Set up periodic peer refresh
|
|
@@ -81,6 +101,12 @@ export class RondevuConnectionManager {
|
|
|
81
101
|
clearInterval(timer);
|
|
82
102
|
this.refreshTimers.delete(infoHash);
|
|
83
103
|
}
|
|
104
|
+
// Cancel our offer for this torrent
|
|
105
|
+
const offerHandle = this.torrentOfferHandles.get(infoHash);
|
|
106
|
+
if (offerHandle) {
|
|
107
|
+
offerHandle.cancel();
|
|
108
|
+
this.torrentOfferHandles.delete(infoHash);
|
|
109
|
+
}
|
|
84
110
|
// Close all peer connections for this torrent
|
|
85
111
|
const peers = this.torrentPeers.get(infoHash);
|
|
86
112
|
if (peers) {
|
|
@@ -94,21 +120,6 @@ export class RondevuConnectionManager {
|
|
|
94
120
|
});
|
|
95
121
|
this.torrentPeers.delete(infoHash);
|
|
96
122
|
}
|
|
97
|
-
// Delete our offers for this torrent
|
|
98
|
-
const offerIds = this.torrentOffers.get(infoHash);
|
|
99
|
-
if (offerIds) {
|
|
100
|
-
offerIds.forEach(async (offerId) => {
|
|
101
|
-
try {
|
|
102
|
-
await this.rondevu.offers.delete(offerId);
|
|
103
|
-
}
|
|
104
|
-
catch (error) {
|
|
105
|
-
this.log(`Error deleting offer: ${error}`);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
this.torrentOffers.delete(infoHash);
|
|
109
|
-
}
|
|
110
|
-
// Clean up bloom filter
|
|
111
|
-
this.torrentBloomFilters.delete(infoHash);
|
|
112
123
|
this.torrentCleanupHandlers.delete(infoHash);
|
|
113
124
|
};
|
|
114
125
|
torrent.on('done', () => {
|
|
@@ -122,6 +133,8 @@ export class RondevuConnectionManager {
|
|
|
122
133
|
* Discover peers for a torrent using rondevu
|
|
123
134
|
*/
|
|
124
135
|
async discoverPeersForTorrent(torrent) {
|
|
136
|
+
if (!this.rondevu)
|
|
137
|
+
return;
|
|
125
138
|
const infoHash = torrent.infoHash;
|
|
126
139
|
const currentPeerCount = this.torrentPeers.get(infoHash)?.size ?? 0;
|
|
127
140
|
// Check if we already have enough peers
|
|
@@ -130,50 +143,79 @@ export class RondevuConnectionManager {
|
|
|
130
143
|
return;
|
|
131
144
|
}
|
|
132
145
|
try {
|
|
133
|
-
// Create
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
146
|
+
// Create offers for this torrent if we haven't already
|
|
147
|
+
if (!this.torrentOfferHandles.has(infoHash)) {
|
|
148
|
+
this.log(`Creating offers for torrent ${infoHash}`);
|
|
149
|
+
const offerHandle = await this.rondevu.offer({
|
|
150
|
+
tags: [infoHash],
|
|
151
|
+
maxOffers: 3, // Maintain pool of 3 offers
|
|
152
|
+
ttl: 300000, // 5 minutes
|
|
153
|
+
autoStart: true,
|
|
154
|
+
});
|
|
155
|
+
this.torrentOfferHandles.set(infoHash, offerHandle);
|
|
156
|
+
// Listen for incoming connections on our offers
|
|
157
|
+
this.rondevu.on('connection:opened', (_offerId, connection) => {
|
|
158
|
+
this.log(`Incoming connection for torrent ${infoHash}`);
|
|
159
|
+
connection.on('connected', () => {
|
|
160
|
+
// Add the WebRTC peer connection to the torrent
|
|
161
|
+
try {
|
|
162
|
+
const pc = connection.getPeerConnection();
|
|
163
|
+
if (pc) {
|
|
164
|
+
torrent.addPeer(pc);
|
|
165
|
+
this.log(`Added incoming peer to torrent ${infoHash}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
this.log(`Failed to add incoming peer: ${error}`);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
145
174
|
// Discover other peers' offers for this torrent
|
|
146
175
|
this.log(`Discovering peers for torrent ${infoHash}`);
|
|
147
|
-
const
|
|
148
|
-
const offers = await this.rondevu.offers.findByTopic(infoHash, {
|
|
176
|
+
const result = await this.rondevu.discover([infoHash], {
|
|
149
177
|
limit: this.options.maxPeersPerTorrent - currentPeerCount,
|
|
150
|
-
bloomFilter: bloomFilter?.toBytes(),
|
|
151
178
|
});
|
|
152
|
-
this.log(`Found ${offers.length} offers for torrent ${infoHash}`);
|
|
153
|
-
// Connect to discovered peers
|
|
154
|
-
for (const remoteOffer of offers) {
|
|
179
|
+
this.log(`Found ${result.offers.length} offers for torrent ${infoHash}`);
|
|
180
|
+
// Connect to discovered peers using the simplified peer() API
|
|
181
|
+
for (const remoteOffer of result.offers) {
|
|
155
182
|
// Skip our own offers
|
|
156
|
-
if (remoteOffer.
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
// Skip if already answered
|
|
160
|
-
if (remoteOffer.answererPeerId) {
|
|
183
|
+
if (remoteOffer.username === this.rondevu.getName()) {
|
|
161
184
|
continue;
|
|
162
185
|
}
|
|
163
|
-
// Add to bloom filter to avoid rediscovering
|
|
164
|
-
bloomFilter?.add(remoteOffer.peerId);
|
|
165
186
|
try {
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
187
|
+
this.log(`Connecting to peer ${remoteOffer.username} for torrent ${infoHash}`);
|
|
188
|
+
const peer = await this.rondevu.peer({
|
|
189
|
+
tags: [infoHash],
|
|
190
|
+
username: remoteOffer.username,
|
|
191
|
+
});
|
|
192
|
+
// Track this peer
|
|
193
|
+
this.torrentPeers.get(infoHash)?.add(peer);
|
|
194
|
+
// Set up peer handlers
|
|
195
|
+
peer.on('open', () => {
|
|
196
|
+
this.log(`Connected to peer ${peer.peerUsername} for torrent ${infoHash}`);
|
|
197
|
+
// Add the WebRTC peer connection to the torrent
|
|
198
|
+
try {
|
|
199
|
+
const pc = peer.peerConnection;
|
|
200
|
+
if (pc) {
|
|
201
|
+
torrent.addPeer(pc);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
this.log(`Failed to add WebRTC peer to torrent: ${error}`);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
peer.on('close', () => {
|
|
209
|
+
this.log(`Peer disconnected for torrent ${infoHash}`);
|
|
210
|
+
this.torrentPeers.get(infoHash)?.delete(peer);
|
|
211
|
+
});
|
|
212
|
+
peer.on('error', (error) => {
|
|
213
|
+
this.log(`Peer error for torrent ${infoHash}: ${error.message}`);
|
|
214
|
+
this.torrentPeers.get(infoHash)?.delete(peer);
|
|
171
215
|
});
|
|
172
|
-
// Set up peer connection handlers
|
|
173
|
-
this.setupPeerHandlers(answerPeer, torrent, false);
|
|
174
216
|
}
|
|
175
217
|
catch (error) {
|
|
176
|
-
this.log(`Failed to
|
|
218
|
+
this.log(`Failed to connect to peer ${remoteOffer.username}: ${error}`);
|
|
177
219
|
}
|
|
178
220
|
}
|
|
179
221
|
}
|
|
@@ -181,35 +223,6 @@ export class RondevuConnectionManager {
|
|
|
181
223
|
this.log(`Error discovering peers for ${infoHash}: ${error}`);
|
|
182
224
|
}
|
|
183
225
|
}
|
|
184
|
-
/**
|
|
185
|
-
* Set up event handlers for a rondevu peer
|
|
186
|
-
*/
|
|
187
|
-
setupPeerHandlers(peer, torrent, isOfferer) {
|
|
188
|
-
const infoHash = torrent.infoHash;
|
|
189
|
-
peer.on('connected', () => {
|
|
190
|
-
this.log(`Peer connected for torrent ${infoHash}`);
|
|
191
|
-
this.torrentPeers.get(infoHash)?.add(peer);
|
|
192
|
-
// Add the WebRTC peer connection to the torrent
|
|
193
|
-
// WebTorrent can use WebRTC peer connections directly
|
|
194
|
-
try {
|
|
195
|
-
torrent.addPeer(peer.pc);
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
this.log(`Failed to add WebRTC peer to torrent: ${error}`);
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
peer.on('disconnected', () => {
|
|
202
|
-
this.log(`Peer disconnected for torrent ${infoHash}`);
|
|
203
|
-
this.torrentPeers.get(infoHash)?.delete(peer);
|
|
204
|
-
});
|
|
205
|
-
peer.on('failed', (error) => {
|
|
206
|
-
this.log(`Peer failed for torrent ${infoHash}: ${error.message}`);
|
|
207
|
-
this.torrentPeers.get(infoHash)?.delete(peer);
|
|
208
|
-
});
|
|
209
|
-
peer.on('datachannel', (channel) => {
|
|
210
|
-
this.log(`Data channel opened for torrent ${infoHash}: ${channel.label}`);
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
226
|
/**
|
|
214
227
|
* Log a message if debug mode is enabled
|
|
215
228
|
*/
|
|
@@ -239,16 +252,16 @@ export class RondevuConnectionManager {
|
|
|
239
252
|
}));
|
|
240
253
|
return {
|
|
241
254
|
activeTorrents: this.client.torrents.length,
|
|
242
|
-
|
|
255
|
+
username: this.rondevu?.getName(),
|
|
243
256
|
rondevuServer: this.options.rondevuServer ?? 'https://api.ronde.vu',
|
|
244
257
|
torrents: torrentStats,
|
|
245
258
|
};
|
|
246
259
|
}
|
|
247
260
|
/**
|
|
248
|
-
* Get the rondevu
|
|
261
|
+
* Get the rondevu credential for persistence
|
|
249
262
|
*/
|
|
250
|
-
|
|
251
|
-
return this.rondevu
|
|
263
|
+
getCredential() {
|
|
264
|
+
return this.rondevu?.getCredential();
|
|
252
265
|
}
|
|
253
266
|
/**
|
|
254
267
|
* Clean up all resources and disconnect from rondevu
|
|
@@ -259,8 +272,7 @@ export class RondevuConnectionManager {
|
|
|
259
272
|
this.torrentCleanupHandlers.forEach((cleanup) => cleanup());
|
|
260
273
|
this.torrentCleanupHandlers.clear();
|
|
261
274
|
this.torrentPeers.clear();
|
|
262
|
-
this.
|
|
275
|
+
this.torrentOfferHandles.clear();
|
|
263
276
|
this.refreshTimers.clear();
|
|
264
|
-
this.torrentBloomFilters.clear();
|
|
265
277
|
}
|
|
266
278
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { RondevuConnectionManager, RondevuConnectionManagerOptions } from './RondevuConnectionManager.js';
|
|
1
|
+
export { RondevuConnectionManager, RondevuConnectionManagerOptions, Credential, WebRTCPolyfill } from './RondevuConnectionManager.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xtr-dev/rondevu-webtorrent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "WebTorrent peer discovery plugin using Rondevu WebRTC signaling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"homepage": "https://github.com/xtr-dev/rondevu-webtorrent#readme",
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@roamhq/wrtc": "^0.9.1",
|
|
39
|
-
"@xtr-dev/rondevu-client": "^0.
|
|
39
|
+
"@xtr-dev/rondevu-client": "^0.21.3",
|
|
40
40
|
"webtorrent": "^2.8.4"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|