bun-torrent 0.0.1-beta → 0.0.3-beta
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/README.md +234 -0
- package/dist/client.d.ts +50 -0
- package/dist/client.error.d.ts +8 -0
- package/dist/client.error.js +12 -0
- package/dist/client.error.js.map +1 -0
- package/dist/client.js +112 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/peer/availability.d.ts +50 -0
- package/{src/peer/availability.ts → dist/peer/availability.js} +14 -31
- package/dist/peer/availability.js.map +1 -0
- package/dist/peer/consts.d.ts +8 -0
- package/{src/peer/consts.ts → dist/peer/consts.js} +1 -3
- package/dist/peer/consts.js.map +1 -0
- package/dist/peer/handshake/handshake.error.d.ts +11 -0
- package/dist/peer/handshake/handshake.error.js +15 -0
- package/dist/peer/handshake/handshake.error.js.map +1 -0
- package/dist/peer/handshake/index.d.ts +7 -0
- package/{src/peer/handshake/index.ts → dist/peer/handshake/index.js} +8 -42
- package/dist/peer/handshake/index.js.map +1 -0
- package/{src/peer/index.ts → dist/peer/index.d.ts} +2 -21
- package/dist/peer/index.js +8 -0
- package/dist/peer/index.js.map +1 -0
- package/dist/peer/messages/error.d.ts +12 -0
- package/dist/peer/messages/error.js +16 -0
- package/dist/peer/messages/error.js.map +1 -0
- package/dist/peer/messages/helpers.d.ts +5 -0
- package/{src/peer/messages/helpers.ts → dist/peer/messages/helpers.js} +7 -18
- package/dist/peer/messages/helpers.js.map +1 -0
- package/dist/peer/messages/index.d.ts +6 -0
- package/{src/peer/messages/index.ts → dist/peer/messages/index.js} +30 -80
- package/dist/peer/messages/index.js.map +1 -0
- package/{src/peer/messages/types.ts → dist/peer/messages/types.d.ts} +3 -25
- package/dist/peer/messages/types.js +14 -0
- package/dist/peer/messages/types.js.map +1 -0
- package/dist/peer/peer-id.d.ts +1 -0
- package/{src/peer/peer-id.ts → dist/peer/peer-id.js} +2 -3
- package/dist/peer/peer-id.js.map +1 -0
- package/dist/peer/pool/index.d.ts +65 -0
- package/dist/peer/pool/index.js +207 -0
- package/dist/peer/pool/index.js.map +1 -0
- package/dist/peer/pool/pool.error.d.ts +10 -0
- package/dist/peer/pool/pool.error.js +15 -0
- package/dist/peer/pool/pool.error.js.map +1 -0
- package/dist/peer/session/index.d.ts +35 -0
- package/dist/peer/session/index.js +195 -0
- package/dist/peer/session/index.js.map +1 -0
- package/dist/peer/session/session.error.d.ts +12 -0
- package/dist/peer/session/session.error.js +17 -0
- package/dist/peer/session/session.error.js.map +1 -0
- package/dist/peer/types.js +2 -0
- package/dist/peer/types.js.map +1 -0
- package/dist/torrent/bencode/decoder.d.ts +2 -0
- package/dist/torrent/bencode/decoder.error.d.ts +24 -0
- package/dist/torrent/bencode/decoder.error.js +34 -0
- package/dist/torrent/bencode/decoder.error.js.map +1 -0
- package/{src/torrent/bencode/decoder.ts → dist/torrent/bencode/decoder.js} +44 -92
- package/dist/torrent/bencode/decoder.js.map +1 -0
- package/dist/torrent/bencode/encoder.d.ts +2 -0
- package/dist/torrent/bencode/encoder.error.d.ts +10 -0
- package/dist/torrent/bencode/encoder.error.js +14 -0
- package/dist/torrent/bencode/encoder.error.js.map +1 -0
- package/{src/torrent/bencode/encoder.ts → dist/torrent/bencode/encoder.js} +24 -38
- package/dist/torrent/bencode/encoder.js.map +1 -0
- package/{src/torrent/bencode/index.ts → dist/torrent/bencode/index.d.ts} +0 -2
- package/dist/torrent/bencode/index.js +6 -0
- package/dist/torrent/bencode/index.js.map +1 -0
- package/{src/torrent/bencode/types.ts → dist/torrent/bencode/types.d.ts} +7 -9
- package/dist/torrent/bencode/types.js +10 -0
- package/dist/torrent/bencode/types.js.map +1 -0
- package/dist/torrent/bencode/utils.d.ts +5 -0
- package/dist/torrent/bencode/utils.js +12 -0
- package/dist/torrent/bencode/utils.js.map +1 -0
- package/dist/torrent/download/index.d.ts +2 -0
- package/dist/torrent/download/index.js +2 -0
- package/dist/torrent/download/index.js.map +1 -0
- package/dist/torrent/download/manager.d.ts +79 -0
- package/dist/torrent/download/manager.js +270 -0
- package/dist/torrent/download/manager.js.map +1 -0
- package/dist/torrent/file-selection.d.ts +6 -0
- package/dist/torrent/file-selection.js +32 -0
- package/dist/torrent/file-selection.js.map +1 -0
- package/dist/torrent/index.d.ts +17 -0
- package/dist/torrent/index.js +11 -0
- package/dist/torrent/index.js.map +1 -0
- package/dist/torrent/parser/helpers.d.ts +8 -0
- package/{src/torrent/parser/helpers.ts → dist/torrent/parser/helpers.js} +13 -24
- package/dist/torrent/parser/helpers.js.map +1 -0
- package/dist/torrent/parser/index.d.ts +2 -0
- package/{src/torrent/parser/index.ts → dist/torrent/parser/index.js} +25 -103
- package/dist/torrent/parser/index.js.map +1 -0
- package/dist/torrent/parser/info-hash.d.ts +2 -0
- package/dist/torrent/parser/info-hash.js +6 -0
- package/dist/torrent/parser/info-hash.js.map +1 -0
- package/dist/torrent/parser/parser.error.d.ts +14 -0
- package/dist/torrent/parser/parser.error.js +19 -0
- package/dist/torrent/parser/parser.error.js.map +1 -0
- package/dist/torrent/pieces/DefaultPlanner.d.ts +98 -0
- package/{src/torrent/pieces/DefaultPlanner.ts → dist/torrent/pieces/DefaultPlanner.js} +49 -93
- package/dist/torrent/pieces/DefaultPlanner.js.map +1 -0
- package/dist/torrent/pieces/index.d.ts +5 -0
- package/dist/torrent/pieces/index.js +5 -0
- package/dist/torrent/pieces/index.js.map +1 -0
- package/{src/torrent/pieces/planner.ts → dist/torrent/pieces/planner.d.ts} +2 -7
- package/dist/torrent/pieces/planner.error.d.ts +11 -0
- package/dist/torrent/pieces/planner.error.js +16 -0
- package/dist/torrent/pieces/planner.error.js.map +1 -0
- package/dist/torrent/pieces/planner.js +11 -0
- package/dist/torrent/pieces/planner.js.map +1 -0
- package/{src/torrent/pieces/types.ts → dist/torrent/pieces/types.d.ts} +1 -13
- package/dist/torrent/pieces/types.js +2 -0
- package/dist/torrent/pieces/types.js.map +1 -0
- package/dist/torrent/pieces/utils.d.ts +34 -0
- package/{src/torrent/pieces/utils.ts → dist/torrent/pieces/utils.js} +8 -37
- package/dist/torrent/pieces/utils.js.map +1 -0
- package/dist/torrent/pieces/validation.d.ts +14 -0
- package/{src/torrent/pieces/validation.ts → dist/torrent/pieces/validation.js} +4 -13
- package/dist/torrent/pieces/validation.js.map +1 -0
- package/dist/torrent/session/index.d.ts +74 -0
- package/dist/torrent/session/index.js +114 -0
- package/dist/torrent/session/index.js.map +1 -0
- package/dist/torrent/storage/index.d.ts +35 -0
- package/{src/torrent/storage/index.ts → dist/torrent/storage/index.js} +40 -87
- package/dist/torrent/storage/index.js.map +1 -0
- package/dist/torrent/storage/storage.error.d.ts +11 -0
- package/dist/torrent/storage/storage.error.js +16 -0
- package/dist/torrent/storage/storage.error.js.map +1 -0
- package/{src/torrent/storage/types.ts → dist/torrent/storage/types.d.ts} +0 -2
- package/dist/torrent/storage/types.js +2 -0
- package/dist/torrent/storage/types.js.map +1 -0
- package/{src/torrent/types.ts → dist/torrent/types.d.ts} +0 -1
- package/dist/torrent/types.js +2 -0
- package/dist/torrent/types.js.map +1 -0
- package/dist/tracker/http.d.ts +4 -0
- package/{src/tracker/http.ts → dist/tracker/http.js} +24 -74
- package/dist/tracker/http.js.map +1 -0
- package/dist/tracker/index.d.ts +12 -0
- package/dist/tracker/index.js +49 -0
- package/dist/tracker/index.js.map +1 -0
- package/dist/tracker/tracker.error.d.ts +19 -0
- package/dist/tracker/tracker.error.js +24 -0
- package/dist/tracker/tracker.error.js.map +1 -0
- package/dist/tracker/types.d.ts +11 -0
- package/dist/tracker/types.js +2 -0
- package/dist/tracker/types.js.map +1 -0
- package/dist/tracker/udp.d.ts +3 -0
- package/{src/tracker/udp.ts → dist/tracker/udp.js} +27 -96
- package/dist/tracker/udp.js.map +1 -0
- package/dist/utils/buffers.d.ts +5 -0
- package/{src/utils/buffers.ts → dist/utils/buffers.js} +7 -14
- package/dist/utils/buffers.js.map +1 -0
- package/dist/utils/errors.d.ts +4 -0
- package/{src/utils/errors.ts → dist/utils/errors.js} +4 -4
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/formats.d.ts +1 -0
- package/{src/utils/formats.ts → dist/utils/formats.js} +4 -3
- package/dist/utils/formats.js.map +1 -0
- package/dist/utils/sha1.d.ts +1 -0
- package/dist/utils/sha1.js +3 -0
- package/dist/utils/sha1.js.map +1 -0
- package/package.json +21 -6
- package/src/app.ts +0 -65
- package/src/client.error.ts +0 -12
- package/src/client.test.ts +0 -117
- package/src/client.ts +0 -185
- package/src/index.test.ts +0 -7
- package/src/index.ts +0 -76
- package/src/peer/__tests__/peer-id.test.ts +0 -24
- package/src/peer/availability.test.ts +0 -50
- package/src/peer/handshake/handshake.error.ts +0 -15
- package/src/peer/handshake/handshake.test.ts +0 -125
- package/src/peer/messages/error.ts +0 -16
- package/src/peer/messages/messages.test.ts +0 -231
- package/src/peer/pool/index.ts +0 -301
- package/src/peer/pool/pool.error.ts +0 -17
- package/src/peer/pool/pool.test.ts +0 -305
- package/src/peer/session/index.ts +0 -276
- package/src/peer/session/session.error.ts +0 -19
- package/src/peer/session/session.test.ts +0 -110
- package/src/torrent/bencode/__tests__/decoder.test.ts +0 -212
- package/src/torrent/bencode/__tests__/encoder.test.ts +0 -138
- package/src/torrent/bencode/__tests__/encoder.unit.test.ts +0 -24
- package/src/torrent/bencode/__tests__/integration.test.ts +0 -64
- package/src/torrent/bencode/decoder.error.ts +0 -36
- package/src/torrent/bencode/encoder.error.ts +0 -14
- package/src/torrent/bencode/utils.ts +0 -17
- package/src/torrent/download/index.ts +0 -9
- package/src/torrent/download/manager.test.ts +0 -393
- package/src/torrent/download/manager.ts +0 -376
- package/src/torrent/file-selection.ts +0 -51
- package/src/torrent/index.ts +0 -61
- package/src/torrent/parser/info-hash.test.ts +0 -39
- package/src/torrent/parser/info-hash.ts +0 -7
- package/src/torrent/parser/parser.error.ts +0 -21
- package/src/torrent/parser/parser.test.ts +0 -286
- package/src/torrent/pieces/index.ts +0 -20
- package/src/torrent/pieces/planner.error.ts +0 -18
- package/src/torrent/pieces/planner.test.ts +0 -303
- package/src/torrent/pieces/validation.test.ts +0 -32
- package/src/torrent/session/index.ts +0 -195
- package/src/torrent/session/session.test.ts +0 -279
- package/src/torrent/storage/storage.error.ts +0 -18
- package/src/torrent/storage/storage.test.ts +0 -326
- package/src/tracker/http.test.ts +0 -66
- package/src/tracker/index.ts +0 -93
- package/src/tracker/tracker.error.ts +0 -26
- package/src/tracker/types.ts +0 -17
- package/src/tracker/udp.test.ts +0 -155
- package/src/utils/__tests__/sha1.test.ts +0 -16
- package/src/utils/sha1.ts +0 -4
- /package/{src/peer/types.ts → dist/peer/types.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1 +1,235 @@
|
|
|
1
1
|
# bun-torrent
|
|
2
|
+
|
|
3
|
+
A minimal Bun-native BitTorrent download-only client written in TypeScript.
|
|
4
|
+
|
|
5
|
+
`bun-torrent` can parse `.torrent` files, announce to HTTP and UDP trackers, connect to peers, download pieces, validate piece hashes, and write the downloaded files to disk. The public API is intentionally small: create a `Client`, inspect a torrent when you need metadata, then call `download()`.
|
|
6
|
+
|
|
7
|
+
It has no runtime dependencies.
|
|
8
|
+
|
|
9
|
+
> This package is currently beta software. The API can still change before a stable release.
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- Bun `>= 1.3.0`
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun add bun-torrent@beta
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
or:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install bun-torrent@beta
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Basic Usage
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { Client } from 'bun-torrent';
|
|
31
|
+
|
|
32
|
+
const client = new Client({
|
|
33
|
+
outputDirectory: './downloads',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const torrent = await client.download({
|
|
37
|
+
torrentFile: './example.torrent',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
torrent.on('progress', (progress) => {
|
|
41
|
+
console.log({
|
|
42
|
+
percent: `${(progress.percent * 100).toFixed(2)}%`,
|
|
43
|
+
downloaded: progress.downloadedBytes,
|
|
44
|
+
received: progress.receivedBytes,
|
|
45
|
+
speed: progress.speed,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
torrent.on('done', () => {
|
|
50
|
+
console.log('Download complete');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
torrent.on('error', (error) => {
|
|
54
|
+
console.error('Download failed', error);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await torrent.done;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`download()` returns a `Torrent` instance and starts the download immediately.
|
|
61
|
+
|
|
62
|
+
## Client Setup
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { Client } from 'bun-torrent';
|
|
66
|
+
|
|
67
|
+
const client = new Client({
|
|
68
|
+
outputDirectory: './downloads',
|
|
69
|
+
maxInFlightRequestsPerPeer: 20,
|
|
70
|
+
requestTimeoutMs: 15_000,
|
|
71
|
+
progressEvents: 'piece',
|
|
72
|
+
speedSampleIntervalMs: 500,
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Client options:
|
|
77
|
+
|
|
78
|
+
- `outputDirectory`: directory where downloaded files are written. Defaults to `process.cwd()`.
|
|
79
|
+
- `files`: optional default file selection for downloads.
|
|
80
|
+
- `maxInFlightRequestsPerPeer`: maximum active block requests per peer. Defaults to `20`.
|
|
81
|
+
- `requestTimeoutMs`: timeout for an individual block request. Defaults to `15000`.
|
|
82
|
+
- `progressEvents`: `'piece'` emits progress when a piece completes, `'block'` emits for every received block. Defaults to `'piece'`.
|
|
83
|
+
- `speedSampleIntervalMs`: minimum interval used to refresh speed calculations. Defaults to `500`.
|
|
84
|
+
|
|
85
|
+
Options passed to `download()` override the client defaults for that download.
|
|
86
|
+
|
|
87
|
+
## Inspecting a Torrent
|
|
88
|
+
|
|
89
|
+
Use `inspect()` when you want metadata before starting a download, for example to show the file list or choose only some files.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const metadata = await client.inspect({
|
|
93
|
+
torrentFile: './example.torrent',
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
console.log(metadata.name);
|
|
97
|
+
console.log(metadata.length);
|
|
98
|
+
console.log(metadata.files);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Torrent file input can be a file path, `Uint8Array`, or `ArrayBuffer`.
|
|
102
|
+
|
|
103
|
+
## Download Options
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const torrent = await client.download(
|
|
107
|
+
{
|
|
108
|
+
torrentFile: './example.torrent',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
outputDirectory: './downloads',
|
|
112
|
+
minConnections: 5,
|
|
113
|
+
announcePort: 6881,
|
|
114
|
+
progressEvents: 'block',
|
|
115
|
+
onChangeState: (state) => {
|
|
116
|
+
console.log('client state:', state);
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Download options:
|
|
123
|
+
|
|
124
|
+
- `outputDirectory`: override the output directory for this download.
|
|
125
|
+
- `files`: download only selected files.
|
|
126
|
+
- `minConnections`: minimum connectable peer count requested before downloading starts.
|
|
127
|
+
- `announcePort`: port sent to trackers in announce requests.
|
|
128
|
+
- `maxInFlightRequestsPerPeer`: override request concurrency per peer.
|
|
129
|
+
- `requestTimeoutMs`: override block request timeout.
|
|
130
|
+
- `progressEvents`: `'piece'` or `'block'`.
|
|
131
|
+
- `speedSampleIntervalMs`: override speed sample interval.
|
|
132
|
+
- `onChangeState`: receives client setup states: `parsing`, `tracking`, `connecting`, `downloading`.
|
|
133
|
+
|
|
134
|
+
Tracker announce failures are treated as non-fatal. If no tracker responds, the client can still continue with an empty peer list instead of throwing during tracking.
|
|
135
|
+
|
|
136
|
+
## Selecting Files
|
|
137
|
+
|
|
138
|
+
For multi-file torrents, pass `files` to download only specific files. A file can be selected by its slash-joined path string or by its torrent path array.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
const metadata = await client.inspect({
|
|
142
|
+
torrentFile: './big-buck-bunny.torrent',
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
console.log(metadata.files);
|
|
146
|
+
|
|
147
|
+
const torrent = await client.download(
|
|
148
|
+
{
|
|
149
|
+
torrentFile: './big-buck-bunny.torrent',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
outputDirectory: './downloads',
|
|
153
|
+
files: ['Big Buck Bunny.mp4'],
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
console.log(torrent.files);
|
|
158
|
+
await torrent.done;
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`torrent.files` separates the selected files from the skipped files:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
{
|
|
165
|
+
included: [
|
|
166
|
+
{ path: ['Big Buck Bunny.mp4'], length: 276134947, offset: 140 },
|
|
167
|
+
],
|
|
168
|
+
excluded: [
|
|
169
|
+
{ path: ['Big Buck Bunny.en.srt'], length: 140, offset: 0 },
|
|
170
|
+
{ path: ['poster.jpg'], length: 310380, offset: 276135087 },
|
|
171
|
+
],
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Passing `files: null` or omitting `files` downloads everything.
|
|
176
|
+
|
|
177
|
+
## Torrent Events
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
torrent.on('state', ({ previous, state }) => {
|
|
181
|
+
console.log(previous, '->', state);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
torrent.on('progress', (progress) => {
|
|
185
|
+
console.log(progress.percent, progress.speed);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
torrent.on('peer', (peer) => {
|
|
189
|
+
console.log('peer connected', peer);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
torrent.on('done', () => {
|
|
193
|
+
console.log('done');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
torrent.on('error', (error) => {
|
|
197
|
+
console.error(error);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
torrent.on('close', () => {
|
|
201
|
+
console.log('closed');
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The current torrent state is also available through `torrent.state`. Possible states are:
|
|
206
|
+
|
|
207
|
+
- `downloading`
|
|
208
|
+
- `completed`
|
|
209
|
+
- `failed`
|
|
210
|
+
- `closed`
|
|
211
|
+
|
|
212
|
+
Call `torrent.close()` to stop the download and close peer connections.
|
|
213
|
+
|
|
214
|
+
## Progress Shape
|
|
215
|
+
|
|
216
|
+
Progress events and `torrent.progress` expose:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
{
|
|
220
|
+
totalBytes: number;
|
|
221
|
+
receivedBytes: number;
|
|
222
|
+
downloadedBytes: number;
|
|
223
|
+
totalPieces: number;
|
|
224
|
+
completedPieces: number;
|
|
225
|
+
percent: number;
|
|
226
|
+
speedBytesPerSecond: number;
|
|
227
|
+
speed: string;
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`receivedBytes` counts received piece data, while `downloadedBytes` counts completed and hash-validated pieces.
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type DownloadProgressEventMode } from './torrent/download';
|
|
2
|
+
import { Torrent } from './torrent/session/index';
|
|
3
|
+
import type { TorrentMetadata } from './torrent/types';
|
|
4
|
+
import type { TorrentFileSelection } from './torrent/file-selection';
|
|
5
|
+
export declare enum DownloadState {
|
|
6
|
+
PARSING = "parsing",
|
|
7
|
+
TRACKING = "tracking",
|
|
8
|
+
CONNECTING = "connecting",
|
|
9
|
+
DOWNLOADING = "downloading"
|
|
10
|
+
}
|
|
11
|
+
export type ClientConfig = {
|
|
12
|
+
files?: TorrentFileSelection;
|
|
13
|
+
maxInFlightRequestsPerPeer?: number;
|
|
14
|
+
outputDirectory?: string;
|
|
15
|
+
progressEvents?: DownloadProgressEventMode;
|
|
16
|
+
requestTimeoutMs?: number;
|
|
17
|
+
speedSampleIntervalMs?: number;
|
|
18
|
+
};
|
|
19
|
+
export declare const DEFAULT_CLIENT_CONFIG: {
|
|
20
|
+
readonly maxInFlightRequestsPerPeer: 20;
|
|
21
|
+
readonly progressEvents: "piece";
|
|
22
|
+
readonly requestTimeoutMs: 15000;
|
|
23
|
+
readonly speedSampleIntervalMs: 500;
|
|
24
|
+
};
|
|
25
|
+
export declare class Client {
|
|
26
|
+
private readonly config;
|
|
27
|
+
private readonly peerId;
|
|
28
|
+
constructor(config?: ClientConfig);
|
|
29
|
+
download(input: {
|
|
30
|
+
torrentFile: string | Uint8Array | ArrayBuffer;
|
|
31
|
+
}, options?: DownloadOptions): Promise<Torrent>;
|
|
32
|
+
inspect(input: {
|
|
33
|
+
torrentFile: string | Uint8Array | ArrayBuffer;
|
|
34
|
+
}): Promise<TorrentMetadata>;
|
|
35
|
+
private trackPeers;
|
|
36
|
+
}
|
|
37
|
+
export type DownloadOptions = {
|
|
38
|
+
announcePort?: number;
|
|
39
|
+
files?: TorrentFileSelection;
|
|
40
|
+
maxInFlightRequestsPerPeer?: number;
|
|
41
|
+
minConnections?: number;
|
|
42
|
+
onChangeState?: (state: DownloadState) => unknown;
|
|
43
|
+
outputDirectory?: string;
|
|
44
|
+
progressEvents?: DownloadProgressEventMode;
|
|
45
|
+
requestTimeoutMs?: number;
|
|
46
|
+
speedSampleIntervalMs?: number;
|
|
47
|
+
};
|
|
48
|
+
export type TorrentFileInput = string | Uint8Array | ArrayBuffer;
|
|
49
|
+
export declare const readTorrentFile: (input: TorrentFileInput) => Promise<Uint8Array>;
|
|
50
|
+
export { Client as TorrentClient };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BunTorrentError } from './utils/errors';
|
|
2
|
+
export declare enum ClientErrorCode {
|
|
3
|
+
INVALID_FILE_SELECTION = "CLIENT_INVALID_FILE_SELECTION",
|
|
4
|
+
UNSUPPORTED_TORRENT_FILE_INPUT = "CLIENT_UNSUPPORTED_TORRENT_FILE_INPUT"
|
|
5
|
+
}
|
|
6
|
+
export declare class ClientError extends BunTorrentError {
|
|
7
|
+
constructor(code: ClientErrorCode, message: string);
|
|
8
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BunTorrentError } from './utils/errors';
|
|
2
|
+
export var ClientErrorCode;
|
|
3
|
+
(function (ClientErrorCode) {
|
|
4
|
+
ClientErrorCode["INVALID_FILE_SELECTION"] = "CLIENT_INVALID_FILE_SELECTION";
|
|
5
|
+
ClientErrorCode["UNSUPPORTED_TORRENT_FILE_INPUT"] = "CLIENT_UNSUPPORTED_TORRENT_FILE_INPUT";
|
|
6
|
+
})(ClientErrorCode || (ClientErrorCode = {}));
|
|
7
|
+
export class ClientError extends BunTorrentError {
|
|
8
|
+
constructor(code, message) {
|
|
9
|
+
super(message, code);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=client.error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.error.js","sourceRoot":"","sources":["../src/client.error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,MAAM,CAAN,IAAY,eAGX;AAHD,WAAY,eAAe;IACvB,2EAAwD,CAAA;IACxD,2FAAwE,CAAA;AAC5E,CAAC,EAHW,eAAe,KAAf,eAAe,QAG1B;AAED,MAAM,OAAO,WAAY,SAAQ,eAAe;IAC5C,YAAY,IAAqB,EAAE,OAAe;QAC9C,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACzB,CAAC;CACJ"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createPeerId } from './peer/peer-id';
|
|
2
|
+
import { openPeerPool } from './peer/pool';
|
|
3
|
+
import { DownloadManager } from './torrent/download';
|
|
4
|
+
import { parseTorrent } from './torrent/parser';
|
|
5
|
+
import { Torrent } from './torrent/session/index';
|
|
6
|
+
import { ClientError, ClientErrorCode } from './client.error';
|
|
7
|
+
import { trackPeers, TrackerError, TrackerErrorCode } from './tracker';
|
|
8
|
+
import { getUnknownSelectedFiles, normalizeTorrentFileSelection } from './torrent/file-selection';
|
|
9
|
+
export var DownloadState;
|
|
10
|
+
(function (DownloadState) {
|
|
11
|
+
DownloadState["PARSING"] = "parsing";
|
|
12
|
+
DownloadState["TRACKING"] = "tracking";
|
|
13
|
+
DownloadState["CONNECTING"] = "connecting";
|
|
14
|
+
DownloadState["DOWNLOADING"] = "downloading";
|
|
15
|
+
})(DownloadState || (DownloadState = {}));
|
|
16
|
+
export const DEFAULT_CLIENT_CONFIG = {
|
|
17
|
+
maxInFlightRequestsPerPeer: 20,
|
|
18
|
+
progressEvents: 'piece',
|
|
19
|
+
requestTimeoutMs: 15_000,
|
|
20
|
+
speedSampleIntervalMs: 500,
|
|
21
|
+
};
|
|
22
|
+
export class Client {
|
|
23
|
+
config;
|
|
24
|
+
peerId;
|
|
25
|
+
constructor(config = {}) {
|
|
26
|
+
this.config = config;
|
|
27
|
+
this.peerId = createPeerId();
|
|
28
|
+
}
|
|
29
|
+
async download(input, options = {}) {
|
|
30
|
+
options.onChangeState?.(DownloadState.PARSING);
|
|
31
|
+
const meta = await this.inspect(input);
|
|
32
|
+
const downloadConfig = resolveDownloadConfig(this.config, options);
|
|
33
|
+
assertValidFileSelection(meta, downloadConfig.files);
|
|
34
|
+
options.onChangeState?.(DownloadState.TRACKING);
|
|
35
|
+
const peers = await this.trackPeers(meta, options);
|
|
36
|
+
options.onChangeState?.(DownloadState.CONNECTING);
|
|
37
|
+
const pool = await openPeerPool(peers, {
|
|
38
|
+
infoHash: meta.infoHash,
|
|
39
|
+
peerId: this.peerId,
|
|
40
|
+
targetConnections: 20,
|
|
41
|
+
totalPieces: meta.pieces.length,
|
|
42
|
+
minConnections: options.minConnections ?? 0,
|
|
43
|
+
maxConnecting: 30,
|
|
44
|
+
timeoutMs: 5_000,
|
|
45
|
+
});
|
|
46
|
+
options.onChangeState?.(DownloadState.DOWNLOADING);
|
|
47
|
+
return new Torrent(meta, pool, new DownloadManager({
|
|
48
|
+
metadata: meta,
|
|
49
|
+
files: downloadConfig.files,
|
|
50
|
+
outputDirectory: downloadConfig.outputDirectory,
|
|
51
|
+
peerPool: pool,
|
|
52
|
+
maxInFlightRequestsPerPeer: downloadConfig.maxInFlightRequestsPerPeer,
|
|
53
|
+
progressEvents: downloadConfig.progressEvents,
|
|
54
|
+
requestTimeoutMs: downloadConfig.requestTimeoutMs,
|
|
55
|
+
speedSampleIntervalMs: downloadConfig.speedSampleIntervalMs,
|
|
56
|
+
}), normalizeTorrentFileSelection(downloadConfig.files));
|
|
57
|
+
}
|
|
58
|
+
async inspect(input) {
|
|
59
|
+
const bytes = await readTorrentFile(input.torrentFile);
|
|
60
|
+
return parseTorrent(bytes);
|
|
61
|
+
}
|
|
62
|
+
async trackPeers(meta, options) {
|
|
63
|
+
try {
|
|
64
|
+
return await trackPeers({
|
|
65
|
+
meta,
|
|
66
|
+
peerId: this.peerId,
|
|
67
|
+
announcePort: options.announcePort,
|
|
68
|
+
timeoutMs: 5_000,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (isNonFatalTrackerError(error))
|
|
73
|
+
return [];
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const isNonFatalTrackerError = (error) => error instanceof TrackerError &&
|
|
79
|
+
(error.code === TrackerErrorCode.ANNOUNCE_FAILED ||
|
|
80
|
+
error.code === TrackerErrorCode.NO_PEERS ||
|
|
81
|
+
error.code === TrackerErrorCode.NO_SUPPORTED_TRACKERS);
|
|
82
|
+
const resolveDownloadConfig = (config, options) => ({
|
|
83
|
+
files: options.files ?? config.files ?? null,
|
|
84
|
+
maxInFlightRequestsPerPeer: options.maxInFlightRequestsPerPeer ??
|
|
85
|
+
config.maxInFlightRequestsPerPeer ??
|
|
86
|
+
DEFAULT_CLIENT_CONFIG.maxInFlightRequestsPerPeer,
|
|
87
|
+
outputDirectory: options.outputDirectory ?? config.outputDirectory ?? process.cwd(),
|
|
88
|
+
progressEvents: options.progressEvents ?? config.progressEvents ?? DEFAULT_CLIENT_CONFIG.progressEvents,
|
|
89
|
+
requestTimeoutMs: options.requestTimeoutMs ??
|
|
90
|
+
config.requestTimeoutMs ??
|
|
91
|
+
DEFAULT_CLIENT_CONFIG.requestTimeoutMs,
|
|
92
|
+
speedSampleIntervalMs: options.speedSampleIntervalMs ??
|
|
93
|
+
config.speedSampleIntervalMs ??
|
|
94
|
+
DEFAULT_CLIENT_CONFIG.speedSampleIntervalMs,
|
|
95
|
+
});
|
|
96
|
+
const assertValidFileSelection = (metadata, files) => {
|
|
97
|
+
const unknownFiles = getUnknownSelectedFiles(metadata, files);
|
|
98
|
+
if (unknownFiles.length === 0)
|
|
99
|
+
return;
|
|
100
|
+
throw new ClientError(ClientErrorCode.INVALID_FILE_SELECTION, `Unknown torrent file selection: ${unknownFiles.join(', ')}`);
|
|
101
|
+
};
|
|
102
|
+
export const readTorrentFile = async (input) => {
|
|
103
|
+
if (typeof input === 'string')
|
|
104
|
+
return await Bun.file(input).bytes();
|
|
105
|
+
if (input instanceof Uint8Array)
|
|
106
|
+
return input;
|
|
107
|
+
if (input instanceof ArrayBuffer)
|
|
108
|
+
return new Uint8Array(input);
|
|
109
|
+
throw new ClientError(ClientErrorCode.UNSUPPORTED_TORRENT_FILE_INPUT, 'Unsupported torrent file input');
|
|
110
|
+
};
|
|
111
|
+
export { Client as TorrentClient };
|
|
112
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 Nikola Nedeljkovic
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
export * from './client';
|
|
6
|
+
export { ClientError, ClientErrorCode } from './client.error';
|
|
7
|
+
export { BencodeDecodeError, BencodeDecodeErrorCode, BencodeEncodeError, BencodeEncodeErrorCode, computeInfoHash, decodeBencode, encodeBencode, parseTorrent, Torrent, TorrentState, toBValue, TorrentParseError, TorrentParseErrorCode, type TorrentFileSelection, type TorrentMetadata, } from './torrent/index';
|
|
8
|
+
export { connectToPeers, createPeerId, decodeHandshake, decodePeerMessage, encodePeerMessage, encodeHandshake, HandshakeErrorCode, PeerMessageError, PeerMessageErrorCode, PeerMessageId, PeerHandshakeError, PeerPoolError, PeerPoolErrorCode, PeerPool, PeerSession, PeerSessionError, PeerSessionErrorCode, openPeerPool, type PeerHandshake, } from './peer/index';
|
|
9
|
+
export { TrackerError, TrackerErrorCode } from './tracker';
|
|
10
|
+
export type { BitfieldMessage, CancelMessage, ChokeMessage, HaveMessage, InterestedMessage, KeepAliveMessage, NotInterestedMessage, PeerMessage, PieceMessage, RequestMessage, UnchokeMessage, PeerConnectionSession, PeerPoolOptions, PeerSessionConnectOptions, } from './peer/index';
|
|
11
|
+
export { BunTorrentError } from './utils/errors';
|
|
12
|
+
export type { BBytes, BDict, BInteger, BList, BValue, BencodeInput, TorrentFiles, TorrentStats, TorrentStateChange, } from './torrent/index';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 Nikola Nedeljkovic
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
export * from './client';
|
|
6
|
+
export { ClientError, ClientErrorCode } from './client.error';
|
|
7
|
+
export { BencodeDecodeError, BencodeDecodeErrorCode, BencodeEncodeError, BencodeEncodeErrorCode, computeInfoHash, decodeBencode, encodeBencode, parseTorrent, Torrent, TorrentState, toBValue, TorrentParseError, TorrentParseErrorCode, } from './torrent/index';
|
|
8
|
+
export { connectToPeers, createPeerId, decodeHandshake, decodePeerMessage, encodePeerMessage, encodeHandshake, HandshakeErrorCode, PeerMessageError, PeerMessageErrorCode, PeerMessageId, PeerHandshakeError, PeerPoolError, PeerPoolErrorCode, PeerPool, PeerSession, PeerSessionError, PeerSessionErrorCode, openPeerPool, } from './peer/index';
|
|
9
|
+
export { TrackerError, TrackerErrorCode } from './tracker';
|
|
10
|
+
export { BunTorrentError } from './utils/errors';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAE9D,OAAO,EACH,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,sBAAsB,EACtB,eAAe,EACf,aAAa,EACb,aAAa,EACb,YAAY,EACZ,OAAO,EACP,YAAY,EACZ,QAAQ,EACR,iBAAiB,EACjB,qBAAqB,GAGxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACH,cAAc,EACd,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,QAAQ,EACR,WAAW,EACX,gBAAgB,EAChB,oBAAoB,EACpB,YAAY,GAEf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAkB3D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type PeerPieceAvailabilityOptions = {
|
|
2
|
+
bitfield?: Uint8Array;
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* Mutable view of which torrent pieces a peer claims to have.
|
|
6
|
+
*/
|
|
7
|
+
export declare class PeerPieceAvailability {
|
|
8
|
+
readonly totalPieces: number;
|
|
9
|
+
private readonly pieces;
|
|
10
|
+
/**
|
|
11
|
+
* Create availability state from an optional peer bitfield.
|
|
12
|
+
*
|
|
13
|
+
* @param totalPieces - Number of pieces in the torrent.
|
|
14
|
+
* @param options - Optional initial bitfield from the peer.
|
|
15
|
+
*/
|
|
16
|
+
constructor(totalPieces: number, options?: PeerPieceAvailabilityOptions);
|
|
17
|
+
/**
|
|
18
|
+
* Return true when the peer claims to have a piece.
|
|
19
|
+
*
|
|
20
|
+
* @param pieceIndex - Zero-based piece index.
|
|
21
|
+
* @returns Whether the peer has the piece. Invalid indexes return false.
|
|
22
|
+
*/
|
|
23
|
+
hasPiece(pieceIndex: number): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Mark one piece as available after receiving a peer `have` message.
|
|
26
|
+
*
|
|
27
|
+
* @param pieceIndex - Zero-based piece index.
|
|
28
|
+
*/
|
|
29
|
+
markHave(pieceIndex: number): void;
|
|
30
|
+
/**
|
|
31
|
+
* Replace availability from a peer `bitfield` message.
|
|
32
|
+
*
|
|
33
|
+
* @param bitfield - Raw bitfield payload from the peer.
|
|
34
|
+
*/
|
|
35
|
+
setBitfield(bitfield: Uint8Array): void;
|
|
36
|
+
/**
|
|
37
|
+
* Return available piece indexes in ascending order.
|
|
38
|
+
*
|
|
39
|
+
* @returns Piece indexes currently marked as available.
|
|
40
|
+
*/
|
|
41
|
+
toPieceIndexes(): number[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create mutable availability state from an optional peer bitfield.
|
|
45
|
+
*
|
|
46
|
+
* @param totalPieces - Number of pieces in the torrent.
|
|
47
|
+
* @param bitfield - Optional raw bitfield payload from the peer.
|
|
48
|
+
* @returns Peer piece availability state.
|
|
49
|
+
*/
|
|
50
|
+
export declare const createPeerPieceAvailability: (totalPieces: number, bitfield?: Uint8Array) => PeerPieceAvailability;
|
|
@@ -1,72 +1,58 @@
|
|
|
1
|
-
export type PeerPieceAvailabilityOptions = {
|
|
2
|
-
bitfield?: Uint8Array;
|
|
3
|
-
};
|
|
4
|
-
|
|
5
1
|
/**
|
|
6
2
|
* Mutable view of which torrent pieces a peer claims to have.
|
|
7
3
|
*/
|
|
8
4
|
export class PeerPieceAvailability {
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
totalPieces;
|
|
6
|
+
pieces;
|
|
11
7
|
/**
|
|
12
8
|
* Create availability state from an optional peer bitfield.
|
|
13
9
|
*
|
|
14
10
|
* @param totalPieces - Number of pieces in the torrent.
|
|
15
11
|
* @param options - Optional initial bitfield from the peer.
|
|
16
12
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
) {
|
|
21
|
-
this.pieces = Array.from({ length: totalPieces }, (_, pieceIndex) =>
|
|
22
|
-
options.bitfield ? hasBitfieldPiece(options.bitfield, pieceIndex) : false,
|
|
23
|
-
);
|
|
13
|
+
constructor(totalPieces, options = {}) {
|
|
14
|
+
this.totalPieces = totalPieces;
|
|
15
|
+
this.pieces = Array.from({ length: totalPieces }, (_, pieceIndex) => options.bitfield ? hasBitfieldPiece(options.bitfield, pieceIndex) : false);
|
|
24
16
|
}
|
|
25
|
-
|
|
26
17
|
/**
|
|
27
18
|
* Return true when the peer claims to have a piece.
|
|
28
19
|
*
|
|
29
20
|
* @param pieceIndex - Zero-based piece index.
|
|
30
21
|
* @returns Whether the peer has the piece. Invalid indexes return false.
|
|
31
22
|
*/
|
|
32
|
-
|
|
23
|
+
hasPiece(pieceIndex) {
|
|
33
24
|
return this.pieces[pieceIndex] ?? false;
|
|
34
25
|
}
|
|
35
|
-
|
|
36
26
|
/**
|
|
37
27
|
* Mark one piece as available after receiving a peer `have` message.
|
|
38
28
|
*
|
|
39
29
|
* @param pieceIndex - Zero-based piece index.
|
|
40
30
|
*/
|
|
41
|
-
|
|
31
|
+
markHave(pieceIndex) {
|
|
42
32
|
if (!Number.isInteger(pieceIndex) || pieceIndex < 0 || pieceIndex >= this.totalPieces) {
|
|
43
33
|
return;
|
|
44
34
|
}
|
|
45
|
-
|
|
46
35
|
this.pieces[pieceIndex] = true;
|
|
47
36
|
}
|
|
48
|
-
|
|
49
37
|
/**
|
|
50
38
|
* Replace availability from a peer `bitfield` message.
|
|
51
39
|
*
|
|
52
40
|
* @param bitfield - Raw bitfield payload from the peer.
|
|
53
41
|
*/
|
|
54
|
-
|
|
42
|
+
setBitfield(bitfield) {
|
|
55
43
|
for (let pieceIndex = 0; pieceIndex < this.totalPieces; pieceIndex += 1) {
|
|
56
44
|
this.pieces[pieceIndex] = hasBitfieldPiece(bitfield, pieceIndex);
|
|
57
45
|
}
|
|
58
46
|
}
|
|
59
|
-
|
|
60
47
|
/**
|
|
61
48
|
* Return available piece indexes in ascending order.
|
|
62
49
|
*
|
|
63
50
|
* @returns Piece indexes currently marked as available.
|
|
64
51
|
*/
|
|
65
|
-
|
|
52
|
+
toPieceIndexes() {
|
|
66
53
|
return this.pieces.flatMap((available, pieceIndex) => (available ? [pieceIndex] : []));
|
|
67
54
|
}
|
|
68
55
|
}
|
|
69
|
-
|
|
70
56
|
/**
|
|
71
57
|
* Create mutable availability state from an optional peer bitfield.
|
|
72
58
|
*
|
|
@@ -74,15 +60,12 @@ export class PeerPieceAvailability {
|
|
|
74
60
|
* @param bitfield - Optional raw bitfield payload from the peer.
|
|
75
61
|
* @returns Peer piece availability state.
|
|
76
62
|
*/
|
|
77
|
-
export const createPeerPieceAvailability = (
|
|
78
|
-
|
|
79
|
-
bitfield?: Uint8Array,
|
|
80
|
-
): PeerPieceAvailability => new PeerPieceAvailability(totalPieces, { bitfield });
|
|
81
|
-
|
|
82
|
-
const hasBitfieldPiece = (bitfield: Uint8Array, pieceIndex: number): boolean => {
|
|
63
|
+
export const createPeerPieceAvailability = (totalPieces, bitfield) => new PeerPieceAvailability(totalPieces, { bitfield });
|
|
64
|
+
const hasBitfieldPiece = (bitfield, pieceIndex) => {
|
|
83
65
|
const byte = bitfield[Math.floor(pieceIndex / 8)];
|
|
84
|
-
if (byte === undefined)
|
|
85
|
-
|
|
66
|
+
if (byte === undefined)
|
|
67
|
+
return false;
|
|
86
68
|
const mask = 0x80 >> (pieceIndex % 8);
|
|
87
69
|
return (byte & mask) !== 0;
|
|
88
70
|
};
|
|
71
|
+
//# sourceMappingURL=availability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"availability.js","sourceRoot":"","sources":["../../src/peer/availability.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,OAAO,qBAAqB;IAUV;IATH,MAAM,CAAY;IAEnC;;;;;OAKG;IACH,YACoB,WAAmB,EACnC,UAAwC,EAAE;QAD1B,gBAAW,GAAX,WAAW,CAAQ;QAGnC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,CAChE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAC5E,CAAC;IACN,CAAC;IAED;;;;;OAKG;IACI,QAAQ,CAAC,UAAkB;QAC9B,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC;IAC5C,CAAC;IAED;;;;OAIG;IACI,QAAQ,CAAC,UAAkB;QAC9B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,UAAU,GAAG,CAAC,IAAI,UAAU,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACpF,OAAO;QACX,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;IACnC,CAAC;IAED;;;;OAIG;IACI,WAAW,CAAC,QAAoB;QACnC,KAAK,IAAI,UAAU,GAAG,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,IAAI,CAAC,EAAE,CAAC;YACtE,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,gBAAgB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QACrE,CAAC;IACL,CAAC;IAED;;;;OAIG;IACI,cAAc;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3F,CAAC;CACJ;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CACvC,WAAmB,EACnB,QAAqB,EACA,EAAE,CAAC,IAAI,qBAAqB,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;AAEjF,MAAM,gBAAgB,GAAG,CAAC,QAAoB,EAAE,UAAkB,EAAW,EAAE;IAC3E,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC;IAClD,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAErC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const PROTOCOL = "BitTorrent protocol";
|
|
2
|
+
export declare const PROTOCOL_BYTES: NodeJS.NonSharedUint8Array;
|
|
3
|
+
export declare const PEER_ID_PREFIX = "-BT0001-";
|
|
4
|
+
export declare const HANDSHAKE_LENGTH = 68;
|
|
5
|
+
export declare const RESERVED_LENGTH = 8;
|
|
6
|
+
export declare const INFO_HASH_LENGTH = 20;
|
|
7
|
+
export declare const PEER_ID_LENGTH = 20;
|
|
8
|
+
export declare const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
export const PROTOCOL = 'BitTorrent protocol';
|
|
2
2
|
export const PROTOCOL_BYTES = new TextEncoder().encode(PROTOCOL);
|
|
3
|
-
|
|
4
3
|
export const PEER_ID_PREFIX = '-BT0001-';
|
|
5
|
-
|
|
6
4
|
export const HANDSHAKE_LENGTH = 68;
|
|
7
5
|
export const RESERVED_LENGTH = 8;
|
|
8
6
|
export const INFO_HASH_LENGTH = 20;
|
|
9
7
|
export const PEER_ID_LENGTH = 20;
|
|
10
|
-
|
|
11
8
|
export const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
9
|
+
//# sourceMappingURL=consts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../../src/peer/consts.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,QAAQ,GAAG,qBAAqB,CAAC;AAC9C,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAEjE,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AAEzC,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AACnC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC;AACjC,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AACnC,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AAEjC,MAAM,CAAC,MAAM,QAAQ,GAAG,gEAAgE,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BunTorrentError } from '../../utils/errors';
|
|
2
|
+
export declare enum HandshakeErrorCode {
|
|
3
|
+
INFOHASH_INVALID_LENGTH = "HANDSHK_INFOHASH_INVALID_LENGTH",
|
|
4
|
+
INVALID_LENGTH = "HANDSHK_INVALID_LENGTH",
|
|
5
|
+
INVALID_PROTOCOL = "HANDSHK_INVALID_PROTOCOL",
|
|
6
|
+
PEERID_INVALID_LENGTH = "HANDSHK_PEERID_INVALID_LENGTH",
|
|
7
|
+
RESERVED_INVALID_LENGTH = "HANDSHK_RESERVED_INVALID_LENGTH"
|
|
8
|
+
}
|
|
9
|
+
export declare class PeerHandshakeError extends BunTorrentError {
|
|
10
|
+
constructor(code: HandshakeErrorCode, message: string);
|
|
11
|
+
}
|