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.
Files changed (213) hide show
  1. package/README.md +234 -0
  2. package/dist/client.d.ts +50 -0
  3. package/dist/client.error.d.ts +8 -0
  4. package/dist/client.error.js +12 -0
  5. package/dist/client.error.js.map +1 -0
  6. package/dist/client.js +112 -0
  7. package/dist/client.js.map +1 -0
  8. package/dist/index.d.ts +12 -0
  9. package/dist/index.js +11 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/peer/availability.d.ts +50 -0
  12. package/{src/peer/availability.ts → dist/peer/availability.js} +14 -31
  13. package/dist/peer/availability.js.map +1 -0
  14. package/dist/peer/consts.d.ts +8 -0
  15. package/{src/peer/consts.ts → dist/peer/consts.js} +1 -3
  16. package/dist/peer/consts.js.map +1 -0
  17. package/dist/peer/handshake/handshake.error.d.ts +11 -0
  18. package/dist/peer/handshake/handshake.error.js +15 -0
  19. package/dist/peer/handshake/handshake.error.js.map +1 -0
  20. package/dist/peer/handshake/index.d.ts +7 -0
  21. package/{src/peer/handshake/index.ts → dist/peer/handshake/index.js} +8 -42
  22. package/dist/peer/handshake/index.js.map +1 -0
  23. package/{src/peer/index.ts → dist/peer/index.d.ts} +2 -21
  24. package/dist/peer/index.js +8 -0
  25. package/dist/peer/index.js.map +1 -0
  26. package/dist/peer/messages/error.d.ts +12 -0
  27. package/dist/peer/messages/error.js +16 -0
  28. package/dist/peer/messages/error.js.map +1 -0
  29. package/dist/peer/messages/helpers.d.ts +5 -0
  30. package/{src/peer/messages/helpers.ts → dist/peer/messages/helpers.js} +7 -18
  31. package/dist/peer/messages/helpers.js.map +1 -0
  32. package/dist/peer/messages/index.d.ts +6 -0
  33. package/{src/peer/messages/index.ts → dist/peer/messages/index.js} +30 -80
  34. package/dist/peer/messages/index.js.map +1 -0
  35. package/{src/peer/messages/types.ts → dist/peer/messages/types.d.ts} +3 -25
  36. package/dist/peer/messages/types.js +14 -0
  37. package/dist/peer/messages/types.js.map +1 -0
  38. package/dist/peer/peer-id.d.ts +1 -0
  39. package/{src/peer/peer-id.ts → dist/peer/peer-id.js} +2 -3
  40. package/dist/peer/peer-id.js.map +1 -0
  41. package/dist/peer/pool/index.d.ts +65 -0
  42. package/dist/peer/pool/index.js +207 -0
  43. package/dist/peer/pool/index.js.map +1 -0
  44. package/dist/peer/pool/pool.error.d.ts +10 -0
  45. package/dist/peer/pool/pool.error.js +15 -0
  46. package/dist/peer/pool/pool.error.js.map +1 -0
  47. package/dist/peer/session/index.d.ts +35 -0
  48. package/dist/peer/session/index.js +195 -0
  49. package/dist/peer/session/index.js.map +1 -0
  50. package/dist/peer/session/session.error.d.ts +12 -0
  51. package/dist/peer/session/session.error.js +17 -0
  52. package/dist/peer/session/session.error.js.map +1 -0
  53. package/dist/peer/types.js +2 -0
  54. package/dist/peer/types.js.map +1 -0
  55. package/dist/torrent/bencode/decoder.d.ts +2 -0
  56. package/dist/torrent/bencode/decoder.error.d.ts +24 -0
  57. package/dist/torrent/bencode/decoder.error.js +34 -0
  58. package/dist/torrent/bencode/decoder.error.js.map +1 -0
  59. package/{src/torrent/bencode/decoder.ts → dist/torrent/bencode/decoder.js} +44 -92
  60. package/dist/torrent/bencode/decoder.js.map +1 -0
  61. package/dist/torrent/bencode/encoder.d.ts +2 -0
  62. package/dist/torrent/bencode/encoder.error.d.ts +10 -0
  63. package/dist/torrent/bencode/encoder.error.js +14 -0
  64. package/dist/torrent/bencode/encoder.error.js.map +1 -0
  65. package/{src/torrent/bencode/encoder.ts → dist/torrent/bencode/encoder.js} +24 -38
  66. package/dist/torrent/bencode/encoder.js.map +1 -0
  67. package/{src/torrent/bencode/index.ts → dist/torrent/bencode/index.d.ts} +0 -2
  68. package/dist/torrent/bencode/index.js +6 -0
  69. package/dist/torrent/bencode/index.js.map +1 -0
  70. package/{src/torrent/bencode/types.ts → dist/torrent/bencode/types.d.ts} +7 -9
  71. package/dist/torrent/bencode/types.js +10 -0
  72. package/dist/torrent/bencode/types.js.map +1 -0
  73. package/dist/torrent/bencode/utils.d.ts +5 -0
  74. package/dist/torrent/bencode/utils.js +12 -0
  75. package/dist/torrent/bencode/utils.js.map +1 -0
  76. package/dist/torrent/download/index.d.ts +2 -0
  77. package/dist/torrent/download/index.js +2 -0
  78. package/dist/torrent/download/index.js.map +1 -0
  79. package/dist/torrent/download/manager.d.ts +79 -0
  80. package/dist/torrent/download/manager.js +270 -0
  81. package/dist/torrent/download/manager.js.map +1 -0
  82. package/dist/torrent/file-selection.d.ts +6 -0
  83. package/dist/torrent/file-selection.js +32 -0
  84. package/dist/torrent/file-selection.js.map +1 -0
  85. package/dist/torrent/index.d.ts +17 -0
  86. package/dist/torrent/index.js +11 -0
  87. package/dist/torrent/index.js.map +1 -0
  88. package/dist/torrent/parser/helpers.d.ts +8 -0
  89. package/{src/torrent/parser/helpers.ts → dist/torrent/parser/helpers.js} +13 -24
  90. package/dist/torrent/parser/helpers.js.map +1 -0
  91. package/dist/torrent/parser/index.d.ts +2 -0
  92. package/{src/torrent/parser/index.ts → dist/torrent/parser/index.js} +25 -103
  93. package/dist/torrent/parser/index.js.map +1 -0
  94. package/dist/torrent/parser/info-hash.d.ts +2 -0
  95. package/dist/torrent/parser/info-hash.js +6 -0
  96. package/dist/torrent/parser/info-hash.js.map +1 -0
  97. package/dist/torrent/parser/parser.error.d.ts +14 -0
  98. package/dist/torrent/parser/parser.error.js +19 -0
  99. package/dist/torrent/parser/parser.error.js.map +1 -0
  100. package/dist/torrent/pieces/DefaultPlanner.d.ts +98 -0
  101. package/{src/torrent/pieces/DefaultPlanner.ts → dist/torrent/pieces/DefaultPlanner.js} +49 -93
  102. package/dist/torrent/pieces/DefaultPlanner.js.map +1 -0
  103. package/dist/torrent/pieces/index.d.ts +5 -0
  104. package/dist/torrent/pieces/index.js +5 -0
  105. package/dist/torrent/pieces/index.js.map +1 -0
  106. package/{src/torrent/pieces/planner.ts → dist/torrent/pieces/planner.d.ts} +2 -7
  107. package/dist/torrent/pieces/planner.error.d.ts +11 -0
  108. package/dist/torrent/pieces/planner.error.js +16 -0
  109. package/dist/torrent/pieces/planner.error.js.map +1 -0
  110. package/dist/torrent/pieces/planner.js +11 -0
  111. package/dist/torrent/pieces/planner.js.map +1 -0
  112. package/{src/torrent/pieces/types.ts → dist/torrent/pieces/types.d.ts} +1 -13
  113. package/dist/torrent/pieces/types.js +2 -0
  114. package/dist/torrent/pieces/types.js.map +1 -0
  115. package/dist/torrent/pieces/utils.d.ts +34 -0
  116. package/{src/torrent/pieces/utils.ts → dist/torrent/pieces/utils.js} +8 -37
  117. package/dist/torrent/pieces/utils.js.map +1 -0
  118. package/dist/torrent/pieces/validation.d.ts +14 -0
  119. package/{src/torrent/pieces/validation.ts → dist/torrent/pieces/validation.js} +4 -13
  120. package/dist/torrent/pieces/validation.js.map +1 -0
  121. package/dist/torrent/session/index.d.ts +74 -0
  122. package/dist/torrent/session/index.js +114 -0
  123. package/dist/torrent/session/index.js.map +1 -0
  124. package/dist/torrent/storage/index.d.ts +35 -0
  125. package/{src/torrent/storage/index.ts → dist/torrent/storage/index.js} +40 -87
  126. package/dist/torrent/storage/index.js.map +1 -0
  127. package/dist/torrent/storage/storage.error.d.ts +11 -0
  128. package/dist/torrent/storage/storage.error.js +16 -0
  129. package/dist/torrent/storage/storage.error.js.map +1 -0
  130. package/{src/torrent/storage/types.ts → dist/torrent/storage/types.d.ts} +0 -2
  131. package/dist/torrent/storage/types.js +2 -0
  132. package/dist/torrent/storage/types.js.map +1 -0
  133. package/{src/torrent/types.ts → dist/torrent/types.d.ts} +0 -1
  134. package/dist/torrent/types.js +2 -0
  135. package/dist/torrent/types.js.map +1 -0
  136. package/dist/tracker/http.d.ts +4 -0
  137. package/{src/tracker/http.ts → dist/tracker/http.js} +24 -74
  138. package/dist/tracker/http.js.map +1 -0
  139. package/dist/tracker/index.d.ts +12 -0
  140. package/dist/tracker/index.js +49 -0
  141. package/dist/tracker/index.js.map +1 -0
  142. package/dist/tracker/tracker.error.d.ts +19 -0
  143. package/dist/tracker/tracker.error.js +24 -0
  144. package/dist/tracker/tracker.error.js.map +1 -0
  145. package/dist/tracker/types.d.ts +11 -0
  146. package/dist/tracker/types.js +2 -0
  147. package/dist/tracker/types.js.map +1 -0
  148. package/dist/tracker/udp.d.ts +3 -0
  149. package/{src/tracker/udp.ts → dist/tracker/udp.js} +27 -96
  150. package/dist/tracker/udp.js.map +1 -0
  151. package/dist/utils/buffers.d.ts +5 -0
  152. package/{src/utils/buffers.ts → dist/utils/buffers.js} +7 -14
  153. package/dist/utils/buffers.js.map +1 -0
  154. package/dist/utils/errors.d.ts +4 -0
  155. package/{src/utils/errors.ts → dist/utils/errors.js} +4 -4
  156. package/dist/utils/errors.js.map +1 -0
  157. package/dist/utils/formats.d.ts +1 -0
  158. package/{src/utils/formats.ts → dist/utils/formats.js} +4 -3
  159. package/dist/utils/formats.js.map +1 -0
  160. package/dist/utils/sha1.d.ts +1 -0
  161. package/dist/utils/sha1.js +3 -0
  162. package/dist/utils/sha1.js.map +1 -0
  163. package/package.json +21 -6
  164. package/src/app.ts +0 -65
  165. package/src/client.error.ts +0 -12
  166. package/src/client.test.ts +0 -117
  167. package/src/client.ts +0 -185
  168. package/src/index.test.ts +0 -7
  169. package/src/index.ts +0 -76
  170. package/src/peer/__tests__/peer-id.test.ts +0 -24
  171. package/src/peer/availability.test.ts +0 -50
  172. package/src/peer/handshake/handshake.error.ts +0 -15
  173. package/src/peer/handshake/handshake.test.ts +0 -125
  174. package/src/peer/messages/error.ts +0 -16
  175. package/src/peer/messages/messages.test.ts +0 -231
  176. package/src/peer/pool/index.ts +0 -301
  177. package/src/peer/pool/pool.error.ts +0 -17
  178. package/src/peer/pool/pool.test.ts +0 -305
  179. package/src/peer/session/index.ts +0 -276
  180. package/src/peer/session/session.error.ts +0 -19
  181. package/src/peer/session/session.test.ts +0 -110
  182. package/src/torrent/bencode/__tests__/decoder.test.ts +0 -212
  183. package/src/torrent/bencode/__tests__/encoder.test.ts +0 -138
  184. package/src/torrent/bencode/__tests__/encoder.unit.test.ts +0 -24
  185. package/src/torrent/bencode/__tests__/integration.test.ts +0 -64
  186. package/src/torrent/bencode/decoder.error.ts +0 -36
  187. package/src/torrent/bencode/encoder.error.ts +0 -14
  188. package/src/torrent/bencode/utils.ts +0 -17
  189. package/src/torrent/download/index.ts +0 -9
  190. package/src/torrent/download/manager.test.ts +0 -393
  191. package/src/torrent/download/manager.ts +0 -376
  192. package/src/torrent/file-selection.ts +0 -51
  193. package/src/torrent/index.ts +0 -61
  194. package/src/torrent/parser/info-hash.test.ts +0 -39
  195. package/src/torrent/parser/info-hash.ts +0 -7
  196. package/src/torrent/parser/parser.error.ts +0 -21
  197. package/src/torrent/parser/parser.test.ts +0 -286
  198. package/src/torrent/pieces/index.ts +0 -20
  199. package/src/torrent/pieces/planner.error.ts +0 -18
  200. package/src/torrent/pieces/planner.test.ts +0 -303
  201. package/src/torrent/pieces/validation.test.ts +0 -32
  202. package/src/torrent/session/index.ts +0 -195
  203. package/src/torrent/session/session.test.ts +0 -279
  204. package/src/torrent/storage/storage.error.ts +0 -18
  205. package/src/torrent/storage/storage.test.ts +0 -326
  206. package/src/tracker/http.test.ts +0 -66
  207. package/src/tracker/index.ts +0 -93
  208. package/src/tracker/tracker.error.ts +0 -26
  209. package/src/tracker/types.ts +0 -17
  210. package/src/tracker/udp.test.ts +0 -155
  211. package/src/utils/__tests__/sha1.test.ts +0 -16
  212. package/src/utils/sha1.ts +0 -4
  213. /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
@@ -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":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAkC,MAAM,oBAAoB,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAEvE,OAAO,EAAE,uBAAuB,EAAE,6BAA6B,EAAE,MAAM,0BAA0B,CAAC;AAElG,MAAM,CAAN,IAAY,aAKX;AALD,WAAY,aAAa;IACrB,oCAAmB,CAAA;IACnB,sCAAqB,CAAA;IACrB,0CAAyB,CAAA;IACzB,4CAA2B,CAAA;AAC/B,CAAC,EALW,aAAa,KAAb,aAAa,QAKxB;AAWD,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACjC,0BAA0B,EAAE,EAAE;IAC9B,cAAc,EAAE,OAAO;IACvB,gBAAgB,EAAE,MAAM;IACxB,qBAAqB,EAAE,GAAG;CAS7B,CAAC;AAEF,MAAM,OAAO,MAAM;IAGc;IAFZ,MAAM,CAAa;IAEpC,YAA6B,SAAuB,EAAE;QAAzB,WAAM,GAAN,MAAM,CAAmB;QAClD,IAAI,CAAC,MAAM,GAAG,YAAY,EAAE,CAAC;IACjC,CAAC;IAEM,KAAK,CAAC,QAAQ,CACjB,KAEC,EACD,UAA2B,EAAE;QAE7B,OAAO,CAAC,aAAa,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC/C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACvC,MAAM,cAAc,GAAG,qBAAqB,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACnE,wBAAwB,CAAC,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;QAErD,OAAO,CAAC,aAAa,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAEnD,OAAO,CAAC,aAAa,EAAE,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,KAAK,EAAE;YACnC,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,iBAAiB,EAAE,EAAE;YACrB,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;YAC/B,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,CAAC;YAC3C,aAAa,EAAE,EAAE;YACjB,SAAS,EAAE,KAAK;SACnB,CAAC,CAAC;QAEH,OAAO,CAAC,aAAa,EAAE,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;QACnD,OAAO,IAAI,OAAO,CACd,IAAI,EACJ,IAAI,EACJ,IAAI,eAAe,CAAC;YAChB,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,cAAc,CAAC,KAAK;YAC3B,eAAe,EAAE,cAAc,CAAC,eAAe;YAC/C,QAAQ,EAAE,IAAI;YACd,0BAA0B,EAAE,cAAc,CAAC,0BAA0B;YACrE,cAAc,EAAE,cAAc,CAAC,cAAc;YAC7C,gBAAgB,EAAE,cAAc,CAAC,gBAAgB;YACjD,qBAAqB,EAAE,cAAc,CAAC,qBAAqB;SAC9D,CAAC,EACF,6BAA6B,CAAC,cAAc,CAAC,KAAK,CAAC,CACtD,CAAC;IACN,CAAC;IAEM,KAAK,CAAC,OAAO,CAAC,KAEpB;QACG,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QACvD,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,IAAqB,EAAE,OAAwB;QACpE,IAAI,CAAC;YACD,OAAO,MAAM,UAAU,CAAC;gBACpB,IAAI;gBACJ,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,SAAS,EAAE,KAAK;aACnB,CAAC,CAAC;QACP,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,sBAAsB,CAAC,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YAC7C,MAAM,KAAK,CAAC;QAChB,CAAC;IACL,CAAC;CACJ;AAkBD,MAAM,sBAAsB,GAAG,CAAC,KAAc,EAAyB,EAAE,CACrE,KAAK,YAAY,YAAY;IAC7B,CAAC,KAAK,CAAC,IAAI,KAAK,gBAAgB,CAAC,eAAe;QAC5C,KAAK,CAAC,IAAI,KAAK,gBAAgB,CAAC,QAAQ;QACxC,KAAK,CAAC,IAAI,KAAK,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;AAE/D,MAAM,qBAAqB,GAAG,CAC1B,MAAoB,EACpB,OAAwB,EACF,EAAE,CAAC,CAAC;IAC1B,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,IAAI,IAAI;IAC5C,0BAA0B,EACtB,OAAO,CAAC,0BAA0B;QAClC,MAAM,CAAC,0BAA0B;QACjC,qBAAqB,CAAC,0BAA0B;IACpD,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,MAAM,CAAC,eAAe,IAAI,OAAO,CAAC,GAAG,EAAE;IACnF,cAAc,EACV,OAAO,CAAC,cAAc,IAAI,MAAM,CAAC,cAAc,IAAI,qBAAqB,CAAC,cAAc;IAC3F,gBAAgB,EACZ,OAAO,CAAC,gBAAgB;QACxB,MAAM,CAAC,gBAAgB;QACvB,qBAAqB,CAAC,gBAAgB;IAC1C,qBAAqB,EACjB,OAAO,CAAC,qBAAqB;QAC7B,MAAM,CAAC,qBAAqB;QAC5B,qBAAqB,CAAC,qBAAqB;CAClD,CAAC,CAAC;AAEH,MAAM,wBAAwB,GAAG,CAC7B,QAAyB,EACzB,KAAuC,EACnC,EAAE;IACN,MAAM,YAAY,GAAG,uBAAuB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC9D,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAEtC,MAAM,IAAI,WAAW,CACjB,eAAe,CAAC,sBAAsB,EACtC,mCAAmC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC/D,CAAC;AACN,CAAC,CAAC;AAIF,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,KAAuB,EAAuB,EAAE;IAClF,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC;IACpE,IAAI,KAAK,YAAY,UAAU;QAAE,OAAO,KAAK,CAAC;IAC9C,IAAI,KAAK,YAAY,WAAW;QAAE,OAAO,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;IAE/D,MAAM,IAAI,WAAW,CACjB,eAAe,CAAC,8BAA8B,EAC9C,gCAAgC,CACnC,CAAC;AACN,CAAC,CAAC;AAEF,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,CAAC"}
@@ -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
- private readonly pieces: boolean[];
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
- public constructor(
18
- public readonly totalPieces: number,
19
- options: PeerPieceAvailabilityOptions = {},
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
- public hasPiece(pieceIndex: number): boolean {
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
- public markHave(pieceIndex: number): void {
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
- public setBitfield(bitfield: Uint8Array): void {
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
- public toPieceIndexes(): number[] {
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
- totalPieces: number,
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) return false;
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
+ }