@xtr-dev/rondevu-webtorrent 0.0.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/LICENSE +21 -0
- package/README.md +300 -0
- package/dist/RondevuConnectionManager.d.ts +104 -0
- package/dist/RondevuConnectionManager.js +266 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +40 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 XTR Development
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# @xtr-dev/rondevu-webtorrent
|
|
2
|
+
|
|
3
|
+
> **⚠️ EARLY DEVELOPMENT WARNING**: This package is in early development (version < 1.0.0). The API is subject to change, and there may be bugs or incomplete features. Use at your own risk in production environments.
|
|
4
|
+
|
|
5
|
+
WebTorrent peer discovery plugin using [Rondevu](https://github.com/xtr-dev/rondevu) WebRTC signaling for peer connectivity.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
`@xtr-dev/rondevu-webtorrent` acts as a plugin for [WebTorrent](https://webtorrent.io/), providing automatic peer discovery through the Rondevu WebRTC signaling service. It complements traditional BitTorrent peer discovery methods (DHT, trackers) by using WebRTC data channels for direct peer-to-peer connections.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Automatic Peer Discovery**: Automatically discovers and connects to peers for each torrent
|
|
14
|
+
- **WebRTC Signaling**: Uses Rondevu for WebRTC offer/answer exchange and ICE candidate signaling
|
|
15
|
+
- **Topic-Based Discovery**: Each torrent's `infoHash` is used as a topic for peer discovery
|
|
16
|
+
- **Bloom Filter Optimization**: Uses bloom filters to avoid rediscovering peers, reducing bandwidth and API calls
|
|
17
|
+
- **Configurable**: Control max peers per torrent, refresh intervals, and WebRTC configuration
|
|
18
|
+
- **Credential Persistence**: Save and reuse Rondevu credentials across sessions
|
|
19
|
+
- **Debug Mode**: Enable detailed logging to monitor peer discovery and connections
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @xtr-dev/rondevu-webtorrent
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Demo
|
|
28
|
+
|
|
29
|
+
A complete working demo is available in the [`demo/`](./demo) folder. The demo includes:
|
|
30
|
+
- **Seeder script** - Creates and seeds a torrent
|
|
31
|
+
- **Leecher script** - Downloads the torrent
|
|
32
|
+
- Step-by-step instructions
|
|
33
|
+
|
|
34
|
+
See the [demo README](./demo/README.md) for details.
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
### Browser
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import WebTorrent from 'webtorrent';
|
|
42
|
+
import { RondevuConnectionManager } from '@xtr-dev/rondevu-webtorrent';
|
|
43
|
+
|
|
44
|
+
// Create WebTorrent client
|
|
45
|
+
const client = new WebTorrent();
|
|
46
|
+
|
|
47
|
+
// Initialize Rondevu connection manager
|
|
48
|
+
const connectionManager = new RondevuConnectionManager(client, {
|
|
49
|
+
rondevuServer: 'https://api.ronde.vu', // Optional: defaults to this
|
|
50
|
+
maxPeersPerTorrent: 50,
|
|
51
|
+
debug: true,
|
|
52
|
+
refreshInterval: 30000, // 30 seconds
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Add a torrent
|
|
56
|
+
const magnetURI = 'magnet:?xt=urn:btih:...';
|
|
57
|
+
client.add(magnetURI, (torrent) => {
|
|
58
|
+
console.log(`Torrent added: ${torrent.name}`);
|
|
59
|
+
|
|
60
|
+
torrent.on('download', () => {
|
|
61
|
+
console.log(`Progress: ${(torrent.progress * 100).toFixed(2)}%`);
|
|
62
|
+
console.log(`Peers: ${torrent.numPeers}`);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
torrent.on('done', () => {
|
|
66
|
+
console.log('Download complete!');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Node.js (with WebRTC polyfill)
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import WebTorrent from 'webtorrent';
|
|
75
|
+
import { RondevuConnectionManager } from '@xtr-dev/rondevu-webtorrent';
|
|
76
|
+
import wrtc from '@roamhq/wrtc';
|
|
77
|
+
|
|
78
|
+
const client = new WebTorrent();
|
|
79
|
+
|
|
80
|
+
// Initialize with wrtc polyfill for Node.js
|
|
81
|
+
const connectionManager = new RondevuConnectionManager(client, {
|
|
82
|
+
rondevuServer: 'https://api.ronde.vu',
|
|
83
|
+
maxPeersPerTorrent: 50,
|
|
84
|
+
debug: true,
|
|
85
|
+
wrtc: wrtc, // Required for WebRTC in Node.js
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Rest is the same as browser...
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## API
|
|
92
|
+
|
|
93
|
+
### `RondevuConnectionManager`
|
|
94
|
+
|
|
95
|
+
The main class that manages peer discovery for WebTorrent.
|
|
96
|
+
|
|
97
|
+
#### Constructor
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
new RondevuConnectionManager(client: WebTorrent.Instance, options?: RondevuConnectionManagerOptions)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### Options
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
interface RondevuConnectionManagerOptions {
|
|
107
|
+
/**
|
|
108
|
+
* Rondevu server base URL
|
|
109
|
+
* @default 'https://api.ronde.vu'
|
|
110
|
+
*/
|
|
111
|
+
rondevuServer?: string;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Maximum number of peer connections per torrent
|
|
115
|
+
* @default 50
|
|
116
|
+
*/
|
|
117
|
+
maxPeersPerTorrent?: number;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Enable debug logging
|
|
121
|
+
* @default false
|
|
122
|
+
*/
|
|
123
|
+
debug?: boolean;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Interval in milliseconds to refresh peer discovery
|
|
127
|
+
* @default 30000 (30 seconds)
|
|
128
|
+
*/
|
|
129
|
+
refreshInterval?: number;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Custom RTCConfiguration for WebRTC peer connections
|
|
133
|
+
*/
|
|
134
|
+
rtcConfig?: RTCConfiguration;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Existing Rondevu credentials to reuse
|
|
138
|
+
*/
|
|
139
|
+
credentials?: { peerId: string; secret: string };
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* WebRTC polyfill for Node.js (e.g., @roamhq/wrtc)
|
|
143
|
+
* Required for WebRTC functionality in Node.js environments
|
|
144
|
+
*/
|
|
145
|
+
wrtc?: {
|
|
146
|
+
RTCPeerConnection: typeof RTCPeerConnection;
|
|
147
|
+
RTCSessionDescription: typeof RTCSessionDescription;
|
|
148
|
+
RTCIceCandidate: typeof RTCIceCandidate;
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### Methods
|
|
154
|
+
|
|
155
|
+
##### `discoverPeers(infoHash: string): Promise<void>`
|
|
156
|
+
|
|
157
|
+
Manually trigger peer discovery for a specific torrent.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
await connectionManager.discoverPeers(torrent.infoHash);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
##### `getStats()`
|
|
164
|
+
|
|
165
|
+
Get statistics about the connection manager.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const stats = connectionManager.getStats();
|
|
169
|
+
console.log(stats);
|
|
170
|
+
// {
|
|
171
|
+
// activeTorrents: 1,
|
|
172
|
+
// peerId: 'abc123...',
|
|
173
|
+
// rondevuServer: 'https://api.ronde.vu',
|
|
174
|
+
// torrents: [
|
|
175
|
+
// { infoHash: '...', peerCount: 5 }
|
|
176
|
+
// ]
|
|
177
|
+
// }
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
##### `getCredentials(): Credentials | undefined`
|
|
181
|
+
|
|
182
|
+
Get the current Rondevu credentials for persistence across sessions.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
const credentials = connectionManager.getCredentials();
|
|
186
|
+
// Save credentials to storage
|
|
187
|
+
localStorage.setItem('rondevu-credentials', JSON.stringify(credentials));
|
|
188
|
+
|
|
189
|
+
// Later, reuse credentials
|
|
190
|
+
const savedCredentials = JSON.parse(localStorage.getItem('rondevu-credentials'));
|
|
191
|
+
const newManager = new RondevuConnectionManager(client, {
|
|
192
|
+
credentials: savedCredentials
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
##### `destroy(): void`
|
|
197
|
+
|
|
198
|
+
Clean up all resources and disconnect from Rondevu.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
connectionManager.destroy();
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## How It Works
|
|
205
|
+
|
|
206
|
+
1. **Initialization**: When you create a `RondevuConnectionManager`, it registers with the Rondevu signaling server
|
|
207
|
+
2. **Torrent Added**: When a torrent is added to WebTorrent:
|
|
208
|
+
- The manager creates a bloom filter to track seen peers
|
|
209
|
+
- The manager creates a WebRTC offer and publishes it to Rondevu with the torrent's `infoHash` as the topic
|
|
210
|
+
- The manager queries Rondevu for other peers offering the same `infoHash`, passing the bloom filter to exclude already-seen peers
|
|
211
|
+
- WebRTC connections are established with discovered peers
|
|
212
|
+
- Each discovered peer ID is added to the bloom filter
|
|
213
|
+
3. **Peer Connection**: Once WebRTC connections are established, the peer connections are added to the WebTorrent instance
|
|
214
|
+
4. **Periodic Refresh**: The manager periodically refreshes peer discovery to find new peers, using the bloom filter to avoid reconnecting to already-seen peers
|
|
215
|
+
5. **Cleanup**: When a torrent is removed, all associated peer connections, offers, and bloom filters are cleaned up
|
|
216
|
+
|
|
217
|
+
## Advanced Usage
|
|
218
|
+
|
|
219
|
+
### Custom RTCConfiguration
|
|
220
|
+
|
|
221
|
+
Provide custom STUN/TURN servers for WebRTC connections:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
const connectionManager = new RondevuConnectionManager(client, {
|
|
225
|
+
rtcConfig: {
|
|
226
|
+
iceServers: [
|
|
227
|
+
{ urls: 'stun:stun.l.google.com:19302' },
|
|
228
|
+
{
|
|
229
|
+
urls: 'turn:turn.example.com:3478',
|
|
230
|
+
username: 'user',
|
|
231
|
+
credential: 'pass'
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Persistent Credentials
|
|
239
|
+
|
|
240
|
+
Save and reuse credentials to maintain the same peer ID:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
// First time
|
|
244
|
+
const manager = new RondevuConnectionManager(client, { debug: true });
|
|
245
|
+
|
|
246
|
+
// Save credentials after initialization
|
|
247
|
+
setTimeout(() => {
|
|
248
|
+
const credentials = manager.getCredentials();
|
|
249
|
+
fs.writeFileSync('credentials.json', JSON.stringify(credentials));
|
|
250
|
+
}, 1000);
|
|
251
|
+
|
|
252
|
+
// Next time
|
|
253
|
+
const savedCredentials = JSON.parse(fs.readFileSync('credentials.json', 'utf8'));
|
|
254
|
+
const manager = new RondevuConnectionManager(client, {
|
|
255
|
+
credentials: savedCredentials
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Graceful Shutdown
|
|
260
|
+
|
|
261
|
+
Always clean up when your application exits:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
process.on('SIGINT', () => {
|
|
265
|
+
console.log('Shutting down...');
|
|
266
|
+
connectionManager.destroy();
|
|
267
|
+
client.destroy(() => {
|
|
268
|
+
console.log('WebTorrent client destroyed');
|
|
269
|
+
process.exit(0);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Limitations
|
|
275
|
+
|
|
276
|
+
- **Node.js WebRTC Support**: Node.js doesn't have native WebRTC support. To enable WebRTC functionality in Node.js, you need to install and pass a WebRTC polyfill like `@roamhq/wrtc` via the `wrtc` option (see the Node.js Quick Start example above). The polyfill requires native compilation during installation. Without the polyfill, the package will still work in Node.js using WebTorrent's traditional peer discovery methods (DHT, trackers), but Rondevu WebRTC peer discovery will not be available.
|
|
277
|
+
- **Browser Support**: WebRTC works natively in modern browsers, making this the ideal environment for full Rondevu WebRTC functionality without requiring any polyfills
|
|
278
|
+
- **Network Requirements**: The Rondevu signaling server must be accessible to all peers
|
|
279
|
+
- **Restrictive Networks**: WebRTC connections may not work in restrictive network environments without TURN servers
|
|
280
|
+
|
|
281
|
+
## Roadmap
|
|
282
|
+
|
|
283
|
+
- [x] Bloom filter support for efficient peer discovery
|
|
284
|
+
- [ ] Better error handling and recovery
|
|
285
|
+
- [ ] Metrics and monitoring
|
|
286
|
+
- [ ] Connection pooling and optimization
|
|
287
|
+
- [ ] Automated testing suite
|
|
288
|
+
|
|
289
|
+
## License
|
|
290
|
+
|
|
291
|
+
MIT
|
|
292
|
+
|
|
293
|
+
## Contributing
|
|
294
|
+
|
|
295
|
+
Contributions are welcome! Please open an issue or pull request on GitHub.
|
|
296
|
+
|
|
297
|
+
## Related Projects
|
|
298
|
+
|
|
299
|
+
- [WebTorrent](https://webtorrent.io/) - Streaming torrent client for Node.js and the browser
|
|
300
|
+
- [Rondevu](https://github.com/xtr-dev/rondevu) - WebRTC signaling and peer discovery service
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import WebTorrent from 'webtorrent';
|
|
2
|
+
import { Credentials } from '@xtr-dev/rondevu-client';
|
|
3
|
+
/**
|
|
4
|
+
* WebRTC polyfill interface for Node.js environments
|
|
5
|
+
*/
|
|
6
|
+
export interface WebRTCPolyfill {
|
|
7
|
+
RTCPeerConnection: typeof RTCPeerConnection;
|
|
8
|
+
RTCSessionDescription: typeof RTCSessionDescription;
|
|
9
|
+
RTCIceCandidate: typeof RTCIceCandidate;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Options for configuring the RondevuConnectionManager
|
|
13
|
+
*/
|
|
14
|
+
export interface RondevuConnectionManagerOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Rondevu server base URL (default: 'https://api.ronde.vu')
|
|
17
|
+
*/
|
|
18
|
+
rondevuServer?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Maximum number of peer connections to establish per torrent (default: 50)
|
|
21
|
+
*/
|
|
22
|
+
maxPeersPerTorrent?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Enable debug logging
|
|
25
|
+
*/
|
|
26
|
+
debug?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Interval in milliseconds to refresh peer discovery (default: 30000ms)
|
|
29
|
+
*/
|
|
30
|
+
refreshInterval?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Custom RTCConfiguration for WebRTC peer connections
|
|
33
|
+
*/
|
|
34
|
+
rtcConfig?: RTCConfiguration;
|
|
35
|
+
/**
|
|
36
|
+
* Existing rondevu credentials to reuse
|
|
37
|
+
*/
|
|
38
|
+
credentials?: Credentials;
|
|
39
|
+
/**
|
|
40
|
+
* WebRTC polyfill for Node.js (e.g., @roamhq/wrtc)
|
|
41
|
+
* Required for WebRTC functionality in Node.js environments
|
|
42
|
+
*/
|
|
43
|
+
wrtc?: WebRTCPolyfill;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Connection manager that uses Rondevu for WebTorrent peer discovery via WebRTC.
|
|
47
|
+
* This class acts as a plugin for WebTorrent, automatically discovering
|
|
48
|
+
* and connecting to peers through rondevu WebRTC signaling.
|
|
49
|
+
*/
|
|
50
|
+
export declare class RondevuConnectionManager {
|
|
51
|
+
private client;
|
|
52
|
+
private rondevu;
|
|
53
|
+
private torrentPeers;
|
|
54
|
+
private torrentOffers;
|
|
55
|
+
private torrentCleanupHandlers;
|
|
56
|
+
private refreshTimers;
|
|
57
|
+
private torrentBloomFilters;
|
|
58
|
+
private options;
|
|
59
|
+
constructor(client: WebTorrent.Instance, options?: RondevuConnectionManagerOptions);
|
|
60
|
+
/**
|
|
61
|
+
* Initialize the connection manager by setting up event listeners and registering with rondevu
|
|
62
|
+
*/
|
|
63
|
+
private initialize;
|
|
64
|
+
/**
|
|
65
|
+
* Handle a torrent being added to the WebTorrent client
|
|
66
|
+
*/
|
|
67
|
+
private handleTorrentAdded;
|
|
68
|
+
/**
|
|
69
|
+
* Discover peers for a torrent using rondevu
|
|
70
|
+
*/
|
|
71
|
+
private discoverPeersForTorrent;
|
|
72
|
+
/**
|
|
73
|
+
* Set up event handlers for a rondevu peer
|
|
74
|
+
*/
|
|
75
|
+
private setupPeerHandlers;
|
|
76
|
+
/**
|
|
77
|
+
* Log a message if debug mode is enabled
|
|
78
|
+
*/
|
|
79
|
+
private log;
|
|
80
|
+
/**
|
|
81
|
+
* Manually trigger peer discovery for a specific torrent
|
|
82
|
+
*/
|
|
83
|
+
discoverPeers(infoHash: string): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Get statistics about the connection manager
|
|
86
|
+
*/
|
|
87
|
+
getStats(): {
|
|
88
|
+
activeTorrents: number;
|
|
89
|
+
peerId: string | undefined;
|
|
90
|
+
rondevuServer: string;
|
|
91
|
+
torrents: {
|
|
92
|
+
infoHash: string;
|
|
93
|
+
peerCount: number;
|
|
94
|
+
}[];
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Get the rondevu credentials for persistence
|
|
98
|
+
*/
|
|
99
|
+
getCredentials(): Credentials | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Clean up all resources and disconnect from rondevu
|
|
102
|
+
*/
|
|
103
|
+
destroy(): void;
|
|
104
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { Rondevu, BloomFilter } from '@xtr-dev/rondevu-client';
|
|
2
|
+
/**
|
|
3
|
+
* Connection manager that uses Rondevu for WebTorrent peer discovery via WebRTC.
|
|
4
|
+
* This class acts as a plugin for WebTorrent, automatically discovering
|
|
5
|
+
* and connecting to peers through rondevu WebRTC signaling.
|
|
6
|
+
*/
|
|
7
|
+
export class RondevuConnectionManager {
|
|
8
|
+
constructor(client, options = {}) {
|
|
9
|
+
this.torrentPeers = new Map();
|
|
10
|
+
this.torrentOffers = new Map();
|
|
11
|
+
this.torrentCleanupHandlers = new Map();
|
|
12
|
+
this.refreshTimers = new Map();
|
|
13
|
+
this.torrentBloomFilters = new Map();
|
|
14
|
+
this.client = client;
|
|
15
|
+
this.options = {
|
|
16
|
+
rondevuServer: options.rondevuServer,
|
|
17
|
+
maxPeersPerTorrent: options.maxPeersPerTorrent ?? 50,
|
|
18
|
+
debug: options.debug ?? false,
|
|
19
|
+
refreshInterval: options.refreshInterval ?? 30000,
|
|
20
|
+
rtcConfig: options.rtcConfig,
|
|
21
|
+
wrtc: options.wrtc,
|
|
22
|
+
};
|
|
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
|
+
this.initialize();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Initialize the connection manager by setting up event listeners and registering with rondevu
|
|
34
|
+
*/
|
|
35
|
+
async initialize() {
|
|
36
|
+
this.log('Initializing RondevuConnectionManager');
|
|
37
|
+
try {
|
|
38
|
+
if (!this.rondevu.isAuthenticated()) {
|
|
39
|
+
const credentials = await this.rondevu.register();
|
|
40
|
+
this.log(`Registered with rondevu: ${credentials.peerId}`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
this.log(`Using existing credentials: ${this.rondevu.getCredentials()?.peerId}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
this.log(`Failed to register with rondevu: ${error}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Listen for new torrents being added
|
|
51
|
+
this.client.on('torrent', (torrent) => {
|
|
52
|
+
this.handleTorrentAdded(torrent);
|
|
53
|
+
});
|
|
54
|
+
// Handle existing torrents if any
|
|
55
|
+
this.client.torrents.forEach((torrent) => {
|
|
56
|
+
this.handleTorrentAdded(torrent);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Handle a torrent being added to the WebTorrent client
|
|
61
|
+
*/
|
|
62
|
+
async handleTorrentAdded(torrent) {
|
|
63
|
+
const infoHash = torrent.infoHash;
|
|
64
|
+
this.log(`Torrent added: ${infoHash}`);
|
|
65
|
+
// Initialize tracking for this torrent
|
|
66
|
+
this.torrentPeers.set(infoHash, new Set());
|
|
67
|
+
this.torrentOffers.set(infoHash, []);
|
|
68
|
+
this.torrentBloomFilters.set(infoHash, new BloomFilter(1024, 3));
|
|
69
|
+
// Start discovering peers and creating offers
|
|
70
|
+
await this.discoverPeersForTorrent(torrent);
|
|
71
|
+
// Set up periodic peer refresh
|
|
72
|
+
const refreshTimer = setInterval(() => {
|
|
73
|
+
this.discoverPeersForTorrent(torrent);
|
|
74
|
+
}, this.options.refreshInterval);
|
|
75
|
+
this.refreshTimers.set(infoHash, refreshTimer);
|
|
76
|
+
// Set up cleanup handler for when torrent is destroyed
|
|
77
|
+
const cleanup = () => {
|
|
78
|
+
this.log(`Cleaning up torrent ${infoHash}`);
|
|
79
|
+
const timer = this.refreshTimers.get(infoHash);
|
|
80
|
+
if (timer) {
|
|
81
|
+
clearInterval(timer);
|
|
82
|
+
this.refreshTimers.delete(infoHash);
|
|
83
|
+
}
|
|
84
|
+
// Close all peer connections for this torrent
|
|
85
|
+
const peers = this.torrentPeers.get(infoHash);
|
|
86
|
+
if (peers) {
|
|
87
|
+
peers.forEach((peer) => {
|
|
88
|
+
try {
|
|
89
|
+
peer.close();
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
this.log(`Error closing peer: ${error}`);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
this.torrentPeers.delete(infoHash);
|
|
96
|
+
}
|
|
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
|
+
this.torrentCleanupHandlers.delete(infoHash);
|
|
113
|
+
};
|
|
114
|
+
torrent.on('done', () => {
|
|
115
|
+
this.log(`Torrent ${infoHash} completed`);
|
|
116
|
+
});
|
|
117
|
+
// Clean up when torrent is destroyed
|
|
118
|
+
torrent.once('destroyed', cleanup);
|
|
119
|
+
this.torrentCleanupHandlers.set(infoHash, cleanup);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Discover peers for a torrent using rondevu
|
|
123
|
+
*/
|
|
124
|
+
async discoverPeersForTorrent(torrent) {
|
|
125
|
+
const infoHash = torrent.infoHash;
|
|
126
|
+
const currentPeerCount = this.torrentPeers.get(infoHash)?.size ?? 0;
|
|
127
|
+
// Check if we already have enough peers
|
|
128
|
+
if (currentPeerCount >= this.options.maxPeersPerTorrent) {
|
|
129
|
+
this.log(`Max peers reached for ${infoHash}`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
// Create our own offer for this torrent using rondevu.createPeer() to get WebRTC polyfills
|
|
134
|
+
const peer = this.rondevu.createPeer(this.options.rtcConfig);
|
|
135
|
+
this.log(`Creating offer for torrent ${infoHash}`);
|
|
136
|
+
const offerId = await peer.createOffer({
|
|
137
|
+
topics: [infoHash],
|
|
138
|
+
ttl: 300000, // 5 minutes in milliseconds
|
|
139
|
+
createDataChannel: true,
|
|
140
|
+
dataChannelLabel: 'webtorrent',
|
|
141
|
+
});
|
|
142
|
+
this.torrentOffers.get(infoHash)?.push(offerId);
|
|
143
|
+
// Set up peer connection handlers
|
|
144
|
+
this.setupPeerHandlers(peer, torrent, true);
|
|
145
|
+
// Discover other peers' offers for this torrent
|
|
146
|
+
this.log(`Discovering peers for torrent ${infoHash}`);
|
|
147
|
+
const bloomFilter = this.torrentBloomFilters.get(infoHash);
|
|
148
|
+
const offers = await this.rondevu.offers.findByTopic(infoHash, {
|
|
149
|
+
limit: this.options.maxPeersPerTorrent - currentPeerCount,
|
|
150
|
+
bloomFilter: bloomFilter?.toBytes(),
|
|
151
|
+
});
|
|
152
|
+
this.log(`Found ${offers.length} offers for torrent ${infoHash}`);
|
|
153
|
+
// Connect to discovered peers
|
|
154
|
+
for (const remoteOffer of offers) {
|
|
155
|
+
// Skip our own offers
|
|
156
|
+
if (remoteOffer.peerId === this.rondevu.getCredentials()?.peerId) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// Skip if already answered
|
|
160
|
+
if (remoteOffer.answererPeerId) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
// Add to bloom filter to avoid rediscovering
|
|
164
|
+
bloomFilter?.add(remoteOffer.peerId);
|
|
165
|
+
try {
|
|
166
|
+
// Create peer using rondevu.createPeer() to get WebRTC polyfills
|
|
167
|
+
const answerPeer = this.rondevu.createPeer(this.options.rtcConfig);
|
|
168
|
+
this.log(`Answering offer ${remoteOffer.id} for torrent ${infoHash}`);
|
|
169
|
+
await answerPeer.answer(remoteOffer.id, remoteOffer.sdp, {
|
|
170
|
+
topics: [infoHash],
|
|
171
|
+
});
|
|
172
|
+
// Set up peer connection handlers
|
|
173
|
+
this.setupPeerHandlers(answerPeer, torrent, false);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
this.log(`Failed to answer offer ${remoteOffer.id}: ${error}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
this.log(`Error discovering peers for ${infoHash}: ${error}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
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
|
+
/**
|
|
214
|
+
* Log a message if debug mode is enabled
|
|
215
|
+
*/
|
|
216
|
+
log(message) {
|
|
217
|
+
if (this.options.debug) {
|
|
218
|
+
console.log(`[RondevuConnectionManager] ${message}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Manually trigger peer discovery for a specific torrent
|
|
223
|
+
*/
|
|
224
|
+
async discoverPeers(infoHash) {
|
|
225
|
+
const torrent = this.client.torrents.find((t) => t.infoHash === infoHash);
|
|
226
|
+
if (!torrent) {
|
|
227
|
+
this.log(`Torrent ${infoHash} not found`);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
await this.discoverPeersForTorrent(torrent);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Get statistics about the connection manager
|
|
234
|
+
*/
|
|
235
|
+
getStats() {
|
|
236
|
+
const torrentStats = Array.from(this.torrentPeers.entries()).map(([infoHash, peers]) => ({
|
|
237
|
+
infoHash,
|
|
238
|
+
peerCount: peers.size,
|
|
239
|
+
}));
|
|
240
|
+
return {
|
|
241
|
+
activeTorrents: this.client.torrents.length,
|
|
242
|
+
peerId: this.rondevu.getCredentials()?.peerId,
|
|
243
|
+
rondevuServer: this.options.rondevuServer ?? 'https://api.ronde.vu',
|
|
244
|
+
torrents: torrentStats,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Get the rondevu credentials for persistence
|
|
249
|
+
*/
|
|
250
|
+
getCredentials() {
|
|
251
|
+
return this.rondevu.getCredentials();
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Clean up all resources and disconnect from rondevu
|
|
255
|
+
*/
|
|
256
|
+
destroy() {
|
|
257
|
+
this.log('Destroying RondevuConnectionManager');
|
|
258
|
+
// Run all cleanup handlers
|
|
259
|
+
this.torrentCleanupHandlers.forEach((cleanup) => cleanup());
|
|
260
|
+
this.torrentCleanupHandlers.clear();
|
|
261
|
+
this.torrentPeers.clear();
|
|
262
|
+
this.torrentOffers.clear();
|
|
263
|
+
this.refreshTimers.clear();
|
|
264
|
+
this.torrentBloomFilters.clear();
|
|
265
|
+
}
|
|
266
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { RondevuConnectionManager, RondevuConnectionManagerOptions } from './RondevuConnectionManager.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import WebTorrent from 'webtorrent';
|
|
2
|
+
import { RondevuConnectionManager } from './RondevuConnectionManager.js';
|
|
3
|
+
export { RondevuConnectionManager } from './RondevuConnectionManager.js';
|
|
4
|
+
// Example usage
|
|
5
|
+
// Check if this file is being run directly (not imported)
|
|
6
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}`;
|
|
7
|
+
if (isMainModule) {
|
|
8
|
+
const client = new WebTorrent();
|
|
9
|
+
// Initialize the Rondevu connection manager
|
|
10
|
+
const connectionManager = new RondevuConnectionManager(client, {
|
|
11
|
+
rondevuServer: 'https://api.ronde.vu', // Optional: defaults to this
|
|
12
|
+
maxPeersPerTorrent: 50,
|
|
13
|
+
debug: true,
|
|
14
|
+
refreshInterval: 30000, // 30 seconds
|
|
15
|
+
});
|
|
16
|
+
// Add a torrent (example with a magnet link)
|
|
17
|
+
const magnetURI = 'magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10';
|
|
18
|
+
client.add(magnetURI, (torrent) => {
|
|
19
|
+
console.log(`Torrent added: ${torrent.name}`);
|
|
20
|
+
console.log(`Info hash: ${torrent.infoHash}`);
|
|
21
|
+
torrent.on('download', () => {
|
|
22
|
+
console.log(`Progress: ${(torrent.progress * 100).toFixed(2)}%`);
|
|
23
|
+
console.log(`Peers: ${torrent.numPeers}`);
|
|
24
|
+
console.log(`Download speed: ${(torrent.downloadSpeed / 1024).toFixed(2)} KB/s`);
|
|
25
|
+
});
|
|
26
|
+
torrent.on('done', () => {
|
|
27
|
+
console.log('Download complete!');
|
|
28
|
+
console.log('Stats:', connectionManager.getStats());
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
// Graceful shutdown
|
|
32
|
+
process.on('SIGINT', () => {
|
|
33
|
+
console.log('\nShutting down...');
|
|
34
|
+
connectionManager.destroy();
|
|
35
|
+
client.destroy(() => {
|
|
36
|
+
console.log('WebTorrent client destroyed');
|
|
37
|
+
process.exit(0);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xtr-dev/rondevu-webtorrent",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "WebTorrent peer discovery plugin using Rondevu WebRTC signaling",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"prepublishOnly": "npm run build",
|
|
16
|
+
"demo:seed": "npm run build && node demo/seed.mjs",
|
|
17
|
+
"demo:leech": "npm run build && node demo/leech.mjs"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"webtorrent",
|
|
21
|
+
"rondevu",
|
|
22
|
+
"webrtc",
|
|
23
|
+
"peer-discovery",
|
|
24
|
+
"bittorrent",
|
|
25
|
+
"p2p"
|
|
26
|
+
],
|
|
27
|
+
"author": "XTR Development",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/xtr-dev/rondevu-webtorrent.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/xtr-dev/rondevu-webtorrent/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/xtr-dev/rondevu-webtorrent#readme",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@roamhq/wrtc": "^0.9.1",
|
|
39
|
+
"@xtr-dev/rondevu-client": "^0.7.4",
|
|
40
|
+
"webtorrent": "^2.8.4"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/webtorrent": "^0.110.1",
|
|
44
|
+
"typescript": "^5.5.3"
|
|
45
|
+
}
|
|
46
|
+
}
|