bun-torrent 0.0.4-beta → 0.0.6
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 +36 -5
- package/dist/client.d.ts +15 -17
- package/dist/client.js +23 -32
- package/dist/client.js.map +1 -1
- package/dist/configs/defaults.d.ts +29 -0
- package/dist/configs/defaults.js +30 -0
- package/dist/configs/defaults.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/magnet/base32/errors.d.ts +8 -0
- package/dist/magnet/base32/errors.js +13 -0
- package/dist/magnet/base32/errors.js.map +1 -0
- package/dist/magnet/base32/index.d.ts +3 -0
- package/dist/magnet/base32/index.js +24 -0
- package/dist/magnet/base32/index.js.map +1 -0
- package/dist/magnet/index.d.ts +2 -0
- package/dist/magnet/index.js +3 -0
- package/dist/magnet/index.js.map +1 -0
- package/dist/magnet/parser/errors.d.ts +12 -0
- package/dist/magnet/parser/errors.js +17 -0
- package/dist/magnet/parser/errors.js.map +1 -0
- package/dist/magnet/parser/index.d.ts +8 -0
- package/dist/magnet/parser/index.js +74 -0
- package/dist/magnet/parser/index.js.map +1 -0
- package/dist/magnet/parser/meta.d.ts +2 -0
- package/dist/magnet/parser/meta.js +68 -0
- package/dist/magnet/parser/meta.js.map +1 -0
- package/dist/magnet/parser/types.d.ts +5 -0
- package/dist/magnet/parser/types.js +2 -0
- package/dist/magnet/parser/types.js.map +1 -0
- package/dist/peer/extended/connection.d.ts +30 -0
- package/dist/peer/extended/connection.js +143 -0
- package/dist/peer/extended/connection.js.map +1 -0
- package/dist/peer/extended/errors.d.ts +14 -0
- package/dist/peer/extended/errors.js +19 -0
- package/dist/peer/extended/errors.js.map +1 -0
- package/dist/peer/extended/index.d.ts +5 -0
- package/dist/peer/extended/index.js +4 -0
- package/dist/peer/extended/index.js.map +1 -0
- package/dist/peer/extended/metadata/assembler.d.ts +12 -0
- package/dist/peer/extended/metadata/assembler.js +44 -0
- package/dist/peer/extended/metadata/assembler.js.map +1 -0
- package/dist/peer/extended/metadata/index.d.ts +8 -0
- package/dist/peer/extended/metadata/index.js +74 -0
- package/dist/peer/extended/metadata/index.js.map +1 -0
- package/dist/peer/extended/metadata/messages.d.ts +6 -0
- package/dist/peer/extended/metadata/messages.js +20 -0
- package/dist/peer/extended/metadata/messages.js.map +1 -0
- package/dist/peer/extended/protocol.d.ts +8 -0
- package/dist/peer/extended/protocol.js +31 -0
- package/dist/peer/extended/protocol.js.map +1 -0
- package/dist/peer/messages/index.js +5 -0
- package/dist/peer/messages/index.js.map +1 -1
- package/dist/peer/messages/types.d.ts +8 -2
- package/dist/peer/messages/types.js +1 -0
- package/dist/peer/messages/types.js.map +1 -1
- package/dist/peer/pool/index.js +4 -5
- package/dist/peer/pool/index.js.map +1 -1
- package/dist/peer/session/index.d.ts +1 -1
- package/dist/peer/session/index.js +10 -5
- package/dist/peer/session/index.js.map +1 -1
- package/dist/torrent/bencode/decoder.d.ts +4 -0
- package/dist/torrent/bencode/decoder.js +9 -0
- package/dist/torrent/bencode/decoder.js.map +1 -1
- package/dist/torrent/download/manager.d.ts +4 -0
- package/dist/torrent/download/manager.js +44 -18
- package/dist/torrent/download/manager.js.map +1 -1
- package/dist/torrent/index.d.ts +1 -1
- package/dist/torrent/index.js +1 -1
- package/dist/torrent/index.js.map +1 -1
- package/dist/torrent/pieces/DefaultPlanner.d.ts +1 -1
- package/dist/torrent/pieces/DefaultPlanner.js +6 -2
- package/dist/torrent/pieces/DefaultPlanner.js.map +1 -1
- package/dist/torrent/pieces/index.d.ts +1 -1
- package/dist/torrent/pieces/index.js +1 -1
- package/dist/torrent/pieces/index.js.map +1 -1
- package/dist/torrent/pieces/types.d.ts +1 -1
- package/dist/torrent/pieces/utils.d.ts +1 -2
- package/dist/torrent/pieces/utils.js +3 -3
- package/dist/torrent/pieces/utils.js.map +1 -1
- package/dist/tracker/http.d.ts +1 -1
- package/dist/tracker/http.js +4 -4
- package/dist/tracker/http.js.map +1 -1
- package/dist/tracker/index.d.ts +1 -1
- package/dist/tracker/index.js +4 -4
- package/dist/tracker/index.js.map +1 -1
- package/dist/tracker/types.d.ts +1 -2
- package/dist/tracker/types.js +1 -1
- package/dist/tracker/types.js.map +1 -1
- package/dist/tracker/udp.d.ts +1 -1
- package/dist/tracker/udp.js +4 -3
- package/dist/tracker/udp.js.map +1 -1
- package/package.json +33 -33
package/README.md
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
A minimal Bun-native BitTorrent download-only client written in TypeScript.
|
|
4
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()`.
|
|
5
|
+
`bun-torrent` can parse `.torrent` files and tracker-backed magnet links, 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
6
|
|
|
7
7
|
It has no runtime dependencies.
|
|
8
8
|
|
|
9
|
-
> This package is currently beta software. The API can still change before a stable release.
|
|
10
|
-
|
|
11
9
|
## Requirements
|
|
12
10
|
|
|
13
11
|
- Bun `>= 1.3.0`
|
|
@@ -15,13 +13,13 @@ It has no runtime dependencies.
|
|
|
15
13
|
## Installation
|
|
16
14
|
|
|
17
15
|
```bash
|
|
18
|
-
bun add bun-torrent
|
|
16
|
+
bun add bun-torrent
|
|
19
17
|
```
|
|
20
18
|
|
|
21
19
|
or:
|
|
22
20
|
|
|
23
21
|
```bash
|
|
24
|
-
npm install bun-torrent
|
|
22
|
+
npm install bun-torrent
|
|
25
23
|
```
|
|
26
24
|
|
|
27
25
|
## Basic Usage
|
|
@@ -111,6 +109,39 @@ console.log(metadata.files);
|
|
|
111
109
|
|
|
112
110
|
Torrent file input can be a file path, `Uint8Array`, or `ArrayBuffer`.
|
|
113
111
|
|
|
112
|
+
## Magnet Links
|
|
113
|
+
|
|
114
|
+
Magnet links are supported when they include at least one HTTP, HTTPS, or UDP tracker through the `tr` parameter. Metadata is fetched from peers with the `ut_metadata` extension before the normal download flow starts.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
const magnet =
|
|
118
|
+
'magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567' +
|
|
119
|
+
'&dn=file.bin' +
|
|
120
|
+
'&tr=udp%3A%2F%2Ftracker.example.com%3A6969%2Fannounce';
|
|
121
|
+
|
|
122
|
+
const metadata = await client.inspect({ magnet });
|
|
123
|
+
|
|
124
|
+
console.log(metadata.name);
|
|
125
|
+
console.log(metadata.files);
|
|
126
|
+
|
|
127
|
+
const torrent = await client.download(
|
|
128
|
+
{ magnet },
|
|
129
|
+
{
|
|
130
|
+
outputDirectory: './downloads',
|
|
131
|
+
},
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
await torrent.done;
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Supported magnet fields:
|
|
138
|
+
|
|
139
|
+
- `xt=urn:btih:<infoHash>`: required. Hex-encoded 40-character info hashes and base32 32-character info hashes are supported.
|
|
140
|
+
- `dn`: optional display name.
|
|
141
|
+
- `tr`: optional tracker URL. Multiple `tr` parameters are supported.
|
|
142
|
+
|
|
143
|
+
Trackerless magnets are not supported yet because DHT peer discovery is not implemented. A magnet without `tr` currently fails during inspection with a DHT-not-implemented error.
|
|
144
|
+
|
|
114
145
|
## Download Options
|
|
115
146
|
|
|
116
147
|
```ts
|
package/dist/client.d.ts
CHANGED
|
@@ -22,27 +22,25 @@ export type ClientConfig = {
|
|
|
22
22
|
targetConnections?: number;
|
|
23
23
|
trackerTimeoutMs?: number;
|
|
24
24
|
};
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
readonly speedSampleIntervalMs: 500;
|
|
34
|
-
readonly targetConnections: 20;
|
|
35
|
-
readonly trackerTimeoutMs: 5000;
|
|
25
|
+
type InspectInput = {
|
|
26
|
+
torrentFile: string | Uint8Array | ArrayBuffer;
|
|
27
|
+
magnet?: undefined | null;
|
|
28
|
+
meta?: null;
|
|
29
|
+
} | {
|
|
30
|
+
magnet: string;
|
|
31
|
+
torrentFile?: undefined | null;
|
|
32
|
+
meta?: null;
|
|
36
33
|
};
|
|
34
|
+
type DownloadInput = {
|
|
35
|
+
meta: TorrentMetadata;
|
|
36
|
+
} | InspectInput;
|
|
37
37
|
export declare class Client {
|
|
38
38
|
private readonly config;
|
|
39
39
|
private readonly peerId;
|
|
40
40
|
constructor(config?: ClientConfig);
|
|
41
|
-
download(input:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
inspect(input: {
|
|
45
|
-
torrentFile: string | Uint8Array | ArrayBuffer;
|
|
41
|
+
download(input: DownloadInput, options?: DownloadOptions): Promise<Torrent>;
|
|
42
|
+
inspect(input: InspectInput, options?: {
|
|
43
|
+
timeout?: number;
|
|
46
44
|
}): Promise<TorrentMetadata>;
|
|
47
45
|
private trackPeers;
|
|
48
46
|
}
|
|
@@ -63,5 +61,5 @@ export type DownloadOptions = {
|
|
|
63
61
|
trackerTimeoutMs?: number;
|
|
64
62
|
};
|
|
65
63
|
export type TorrentFileInput = string | Uint8Array | ArrayBuffer;
|
|
66
|
-
export declare const readTorrentFile: (input:
|
|
64
|
+
export declare const readTorrentFile: (input: unknown) => Promise<Uint8Array>;
|
|
67
65
|
export { Client as TorrentClient };
|
package/dist/client.js
CHANGED
|
@@ -6,6 +6,9 @@ import { Torrent } from './torrent/session/index';
|
|
|
6
6
|
import { ClientError, ClientErrorCode } from './client.error';
|
|
7
7
|
import { trackPeers, TrackerError, TrackerErrorCode } from './tracker';
|
|
8
8
|
import { getUnknownSelectedFiles, normalizeTorrentFileSelection } from './torrent/file-selection';
|
|
9
|
+
import { defaults } from './configs/defaults';
|
|
10
|
+
import { BunTorrentError } from './utils/errors';
|
|
11
|
+
import { parseMagnet } from './magnet';
|
|
9
12
|
export var DownloadState;
|
|
10
13
|
(function (DownloadState) {
|
|
11
14
|
DownloadState["PARSING"] = "parsing";
|
|
@@ -13,18 +16,6 @@ export var DownloadState;
|
|
|
13
16
|
DownloadState["CONNECTING"] = "connecting";
|
|
14
17
|
DownloadState["DOWNLOADING"] = "downloading";
|
|
15
18
|
})(DownloadState || (DownloadState = {}));
|
|
16
|
-
export const DEFAULT_CLIENT_CONFIG = {
|
|
17
|
-
maxInFlightRequestsPerPeer: 20,
|
|
18
|
-
maxConnecting: 30,
|
|
19
|
-
minConnections: 0,
|
|
20
|
-
peerConnectTimeoutMs: 5_000,
|
|
21
|
-
progressEvents: 'piece',
|
|
22
|
-
requestTimeoutMs: 15_000,
|
|
23
|
-
seed: false,
|
|
24
|
-
speedSampleIntervalMs: 500,
|
|
25
|
-
targetConnections: 20,
|
|
26
|
-
trackerTimeoutMs: 5_000,
|
|
27
|
-
};
|
|
28
19
|
export class Client {
|
|
29
20
|
config;
|
|
30
21
|
peerId;
|
|
@@ -34,7 +25,7 @@ export class Client {
|
|
|
34
25
|
}
|
|
35
26
|
async download(input, options = {}) {
|
|
36
27
|
options.onChangeState?.(DownloadState.PARSING);
|
|
37
|
-
const meta = await this.inspect(input);
|
|
28
|
+
const meta = input.meta ?? (await this.inspect(input, { timeout: options.trackerTimeoutMs }));
|
|
38
29
|
const downloadConfig = resolveDownloadConfig(this.config, options);
|
|
39
30
|
assertValidFileSelection(meta, downloadConfig.files);
|
|
40
31
|
options.onChangeState?.(DownloadState.TRACKING);
|
|
@@ -61,9 +52,15 @@ export class Client {
|
|
|
61
52
|
speedSampleIntervalMs: downloadConfig.speedSampleIntervalMs,
|
|
62
53
|
}), normalizeTorrentFileSelection(downloadConfig.files), downloadConfig.seed);
|
|
63
54
|
}
|
|
64
|
-
async inspect(input) {
|
|
65
|
-
|
|
66
|
-
|
|
55
|
+
async inspect(input, options) {
|
|
56
|
+
if ('torrentFile' in input && input.torrentFile !== undefined) {
|
|
57
|
+
const bytes = await readTorrentFile(input.torrentFile);
|
|
58
|
+
return parseTorrent(bytes);
|
|
59
|
+
}
|
|
60
|
+
if ('magnet' in input && input.magnet !== undefined && input.magnet !== null) {
|
|
61
|
+
return parseMagnet(input.magnet, this.peerId, options);
|
|
62
|
+
}
|
|
63
|
+
throw new BunTorrentError('No input provided', 'NO_INPUT');
|
|
67
64
|
}
|
|
68
65
|
async trackPeers(meta, options, config) {
|
|
69
66
|
try {
|
|
@@ -89,27 +86,21 @@ const resolveDownloadConfig = (config, options) => ({
|
|
|
89
86
|
files: options.files ?? config.files ?? null,
|
|
90
87
|
maxInFlightRequestsPerPeer: options.maxInFlightRequestsPerPeer ??
|
|
91
88
|
config.maxInFlightRequestsPerPeer ??
|
|
92
|
-
|
|
93
|
-
maxConnecting: options.maxConnecting ?? config.maxConnecting ??
|
|
94
|
-
minConnections: options.minConnections ?? config.minConnections ??
|
|
89
|
+
defaults.download.maxInFlightRequestsPerPeer,
|
|
90
|
+
maxConnecting: options.maxConnecting ?? config.maxConnecting ?? defaults.peers.maxConnecting,
|
|
91
|
+
minConnections: options.minConnections ?? config.minConnections ?? defaults.peers.minConnections,
|
|
95
92
|
outputDirectory: options.outputDirectory ?? config.outputDirectory ?? process.cwd(),
|
|
96
93
|
peerConnectTimeoutMs: options.peerConnectTimeoutMs ??
|
|
97
94
|
config.peerConnectTimeoutMs ??
|
|
98
|
-
|
|
99
|
-
progressEvents: options.progressEvents ?? config.progressEvents ??
|
|
100
|
-
requestTimeoutMs: options.requestTimeoutMs ??
|
|
101
|
-
|
|
102
|
-
DEFAULT_CLIENT_CONFIG.requestTimeoutMs,
|
|
103
|
-
seed: options.seed ?? config.seed ?? DEFAULT_CLIENT_CONFIG.seed,
|
|
95
|
+
defaults.peers.connectTimeoutMs,
|
|
96
|
+
progressEvents: options.progressEvents ?? config.progressEvents ?? defaults.progress.events,
|
|
97
|
+
requestTimeoutMs: options.requestTimeoutMs ?? config.requestTimeoutMs ?? defaults.download.requestTimeoutMs,
|
|
98
|
+
seed: options.seed ?? config.seed ?? defaults.download.seed,
|
|
104
99
|
speedSampleIntervalMs: options.speedSampleIntervalMs ??
|
|
105
100
|
config.speedSampleIntervalMs ??
|
|
106
|
-
|
|
107
|
-
targetConnections: options.targetConnections ??
|
|
108
|
-
|
|
109
|
-
DEFAULT_CLIENT_CONFIG.targetConnections,
|
|
110
|
-
trackerTimeoutMs: options.trackerTimeoutMs ??
|
|
111
|
-
config.trackerTimeoutMs ??
|
|
112
|
-
DEFAULT_CLIENT_CONFIG.trackerTimeoutMs,
|
|
101
|
+
defaults.progress.speedSampleIntervalMs,
|
|
102
|
+
targetConnections: options.targetConnections ?? config.targetConnections ?? defaults.peers.targetConnections,
|
|
103
|
+
trackerTimeoutMs: options.trackerTimeoutMs ?? config.trackerTimeoutMs ?? defaults.trackers.timeoutMs,
|
|
113
104
|
});
|
|
114
105
|
const assertValidFileSelection = (metadata, files) => {
|
|
115
106
|
const unknownFiles = getUnknownSelectedFiles(metadata, files);
|
package/dist/client.js.map
CHANGED
|
@@ -1 +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;
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const defaults: {
|
|
2
|
+
readonly pieces: {
|
|
3
|
+
readonly blockSize: number;
|
|
4
|
+
};
|
|
5
|
+
readonly peers: {
|
|
6
|
+
readonly targetConnections: 20;
|
|
7
|
+
readonly minConnections: 0;
|
|
8
|
+
readonly maxConnecting: 30;
|
|
9
|
+
readonly connectTimeoutMs: 5000;
|
|
10
|
+
};
|
|
11
|
+
readonly download: {
|
|
12
|
+
readonly maxInFlightRequestsPerPeer: 300;
|
|
13
|
+
readonly baseInFlightRequestsPerPeer: 8;
|
|
14
|
+
readonly requestTimeoutMs: 15000;
|
|
15
|
+
readonly seed: false;
|
|
16
|
+
};
|
|
17
|
+
readonly progress: {
|
|
18
|
+
readonly events: "piece";
|
|
19
|
+
readonly speedSampleIntervalMs: 500;
|
|
20
|
+
};
|
|
21
|
+
readonly trackers: {
|
|
22
|
+
readonly announcePort: 6881;
|
|
23
|
+
readonly timeoutMs: 5000;
|
|
24
|
+
};
|
|
25
|
+
readonly magnet: {
|
|
26
|
+
readonly trackerTimeoutMs: 3000;
|
|
27
|
+
readonly peerTimeoutMs: 5000;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const defaults = {
|
|
2
|
+
pieces: {
|
|
3
|
+
blockSize: 16 * 1024,
|
|
4
|
+
},
|
|
5
|
+
peers: {
|
|
6
|
+
targetConnections: 20,
|
|
7
|
+
minConnections: 0,
|
|
8
|
+
maxConnecting: 30,
|
|
9
|
+
connectTimeoutMs: 5_000,
|
|
10
|
+
},
|
|
11
|
+
download: {
|
|
12
|
+
maxInFlightRequestsPerPeer: 300,
|
|
13
|
+
baseInFlightRequestsPerPeer: 8,
|
|
14
|
+
requestTimeoutMs: 15_000,
|
|
15
|
+
seed: false,
|
|
16
|
+
},
|
|
17
|
+
progress: {
|
|
18
|
+
events: 'piece',
|
|
19
|
+
speedSampleIntervalMs: 500,
|
|
20
|
+
},
|
|
21
|
+
trackers: {
|
|
22
|
+
announcePort: 6881,
|
|
23
|
+
timeoutMs: 5_000,
|
|
24
|
+
},
|
|
25
|
+
magnet: {
|
|
26
|
+
trackerTimeoutMs: 3_000,
|
|
27
|
+
peerTimeoutMs: 5_000,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/configs/defaults.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,QAAQ,GAAG;IACpB,MAAM,EAAE;QACJ,SAAS,EAAE,EAAE,GAAG,IAAI;KACvB;IACD,KAAK,EAAE;QACH,iBAAiB,EAAE,EAAE;QACrB,cAAc,EAAE,CAAC;QACjB,aAAa,EAAE,EAAE;QACjB,gBAAgB,EAAE,KAAK;KAC1B;IACD,QAAQ,EAAE;QACN,0BAA0B,EAAE,GAAG;QAC/B,2BAA2B,EAAE,CAAC;QAC9B,gBAAgB,EAAE,MAAM;QACxB,IAAI,EAAE,KAAK;KACd;IACD,QAAQ,EAAE;QACN,MAAM,EAAE,OAAO;QACf,qBAAqB,EAAE,GAAG;KAC7B;IACD,QAAQ,EAAE;QACN,YAAY,EAAE,IAAI;QAClB,SAAS,EAAE,KAAK;KACnB;IACD,MAAM,EAAE;QACJ,gBAAgB,EAAE,KAAK;QACvB,aAAa,EAAE,KAAK;KACvB;CACK,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* Copyright (c) 2026 Nikola Nedeljkovic
|
|
3
3
|
* SPDX-License-Identifier: MIT
|
|
4
4
|
*/
|
|
5
|
-
export { Client,
|
|
5
|
+
export { Client, DownloadState, TorrentClient } from './client';
|
|
6
6
|
export { ClientError, ClientErrorCode } from './client.error';
|
|
7
7
|
export type { ClientConfig, DownloadOptions, TorrentFileInput } from './client';
|
|
8
8
|
export { Torrent, TorrentState, TorrentParseError, TorrentParseErrorCode, TorrentStorageError, TorrentStorageErrorCode, PiecePlannerError, PiecePlannerErrorCode, } from './torrent/index';
|
|
9
|
+
export { MagnetParseError, MagnetParseErrorCode, Base32DecoderError, Base32DecoderErrorCode, } from './magnet';
|
|
9
10
|
export { PeerPoolError, PeerPoolErrorCode, PeerSessionError, PeerSessionErrorCode } from './peer';
|
|
10
11
|
export { TrackerError, TrackerErrorCode } from './tracker';
|
|
11
12
|
export type { DownloadProgress, DownloadProgressEventMode, TorrentFileSelection, TorrentFiles, TorrentMetadata, TorrentStats, TorrentStateChange, } from './torrent/index';
|
package/dist/index.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Copyright (c) 2026 Nikola Nedeljkovic
|
|
3
3
|
* SPDX-License-Identifier: MIT
|
|
4
4
|
*/
|
|
5
|
-
export { Client,
|
|
5
|
+
export { Client, DownloadState, TorrentClient } from './client';
|
|
6
6
|
export { ClientError, ClientErrorCode } from './client.error';
|
|
7
7
|
export { Torrent, TorrentState, TorrentParseError, TorrentParseErrorCode, TorrentStorageError, TorrentStorageErrorCode, PiecePlannerError, PiecePlannerErrorCode, } from './torrent/index';
|
|
8
|
+
export { MagnetParseError, MagnetParseErrorCode, Base32DecoderError, Base32DecoderErrorCode, } from './magnet';
|
|
8
9
|
export { PeerPoolError, PeerPoolErrorCode, PeerSessionError, PeerSessionErrorCode } from './peer';
|
|
9
10
|
export { TrackerError, TrackerErrorCode } from './tracker';
|
|
10
11
|
export { BunTorrentError } from './utils/errors';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAG9D,OAAO,EACH,OAAO,EACP,YAAY,EACZ,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,uBAAuB,EACvB,iBAAiB,EACjB,qBAAqB,GACxB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACH,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,GACzB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,QAAQ,CAAC;AAClG,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAa3D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BunTorrentError } from '../../utils/errors';
|
|
2
|
+
export declare enum Base32DecoderErrorCode {
|
|
3
|
+
INVALID_CHAR = "INVALID_BASE32_CHAR"
|
|
4
|
+
}
|
|
5
|
+
export declare class Base32DecoderError extends BunTorrentError {
|
|
6
|
+
readonly field?: string | undefined;
|
|
7
|
+
constructor(code: Base32DecoderErrorCode, message: string, field?: string | undefined);
|
|
8
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BunTorrentError } from '../../utils/errors';
|
|
2
|
+
export var Base32DecoderErrorCode;
|
|
3
|
+
(function (Base32DecoderErrorCode) {
|
|
4
|
+
Base32DecoderErrorCode["INVALID_CHAR"] = "INVALID_BASE32_CHAR";
|
|
5
|
+
})(Base32DecoderErrorCode || (Base32DecoderErrorCode = {}));
|
|
6
|
+
export class Base32DecoderError extends BunTorrentError {
|
|
7
|
+
field;
|
|
8
|
+
constructor(code, message, field) {
|
|
9
|
+
super(message, code);
|
|
10
|
+
this.field = field;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/magnet/base32/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,CAAN,IAAY,sBAEX;AAFD,WAAY,sBAAsB;IAC9B,8DAAoC,CAAA;AACxC,CAAC,EAFW,sBAAsB,KAAtB,sBAAsB,QAEjC;AAED,MAAM,OAAO,kBAAmB,SAAQ,eAAe;IAI/B;IAHpB,YACI,IAA4B,EAC5B,OAAe,EACC,KAAc;QAE9B,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAFL,UAAK,GAAL,KAAK,CAAS;IAGlC,CAAC;CACJ"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Base32DecoderError, Base32DecoderErrorCode } from './errors';
|
|
2
|
+
const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
|
3
|
+
export const decodeBase32 = (str) => {
|
|
4
|
+
const base32 = str.toUpperCase().replace(/=+$/, '');
|
|
5
|
+
let bits = 0;
|
|
6
|
+
let value = 0;
|
|
7
|
+
let index = 0;
|
|
8
|
+
const output = new Uint8Array(Math.floor((base32.length * 5) / 8));
|
|
9
|
+
for (const char of base32) {
|
|
10
|
+
const charIndex = BASE32_ALPHABET.indexOf(char);
|
|
11
|
+
if (charIndex === -1) {
|
|
12
|
+
throw new Base32DecoderError(Base32DecoderErrorCode.INVALID_CHAR, `Invalid base32 character: ${char}`);
|
|
13
|
+
}
|
|
14
|
+
value = (value << 5) | charIndex;
|
|
15
|
+
bits += 5;
|
|
16
|
+
if (bits >= 8) {
|
|
17
|
+
output[index++] = (value >> (bits - 8)) & 0xff;
|
|
18
|
+
bits -= 8;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return output;
|
|
22
|
+
};
|
|
23
|
+
export { Base32DecoderError, Base32DecoderErrorCode };
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/magnet/base32/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAEtE,MAAM,eAAe,GAAG,kCAAkC,CAAC;AAE3D,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAW,EAAc,EAAE;IACpD,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAEpD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAEnE,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QACxB,MAAM,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChD,IAAI,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC;YACnB,MAAM,IAAI,kBAAkB,CACxB,sBAAsB,CAAC,YAAY,EACnC,6BAA6B,IAAI,EAAE,CACtC,CAAC;QACN,CAAC;QAED,KAAK,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;QACjC,IAAI,IAAI,CAAC,CAAC;QAEV,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;YACZ,MAAM,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;YAC/C,IAAI,IAAI,CAAC,CAAC;QACd,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC,CAAC;AAEF,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/magnet/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BunTorrentError } from '../../utils/errors';
|
|
2
|
+
export declare enum MagnetParseErrorCode {
|
|
3
|
+
INVALID_URI = "INVALID_MAGNET_URI",
|
|
4
|
+
INVALID_XT = "INVALID_XT_PARAM",
|
|
5
|
+
NOT_IMPLEMENTED = "NOT_IMPLEMENTED",
|
|
6
|
+
NO_METADATA = "NO_METADATA",
|
|
7
|
+
PARSING_FAILED = "METADATA_FAILED_TO_PARSE"
|
|
8
|
+
}
|
|
9
|
+
export declare class MagnetParseError extends BunTorrentError {
|
|
10
|
+
readonly field?: string | undefined;
|
|
11
|
+
constructor(code: MagnetParseErrorCode, message: string, field?: string | undefined);
|
|
12
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BunTorrentError } from '../../utils/errors';
|
|
2
|
+
export var MagnetParseErrorCode;
|
|
3
|
+
(function (MagnetParseErrorCode) {
|
|
4
|
+
MagnetParseErrorCode["INVALID_URI"] = "INVALID_MAGNET_URI";
|
|
5
|
+
MagnetParseErrorCode["INVALID_XT"] = "INVALID_XT_PARAM";
|
|
6
|
+
MagnetParseErrorCode["NOT_IMPLEMENTED"] = "NOT_IMPLEMENTED";
|
|
7
|
+
MagnetParseErrorCode["NO_METADATA"] = "NO_METADATA";
|
|
8
|
+
MagnetParseErrorCode["PARSING_FAILED"] = "METADATA_FAILED_TO_PARSE";
|
|
9
|
+
})(MagnetParseErrorCode || (MagnetParseErrorCode = {}));
|
|
10
|
+
export class MagnetParseError extends BunTorrentError {
|
|
11
|
+
field;
|
|
12
|
+
constructor(code, message, field) {
|
|
13
|
+
super(message, code);
|
|
14
|
+
this.field = field;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/magnet/parser/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,CAAN,IAAY,oBAMX;AAND,WAAY,oBAAoB;IAC5B,0DAAkC,CAAA;IAClC,uDAA+B,CAAA;IAC/B,2DAAmC,CAAA;IACnC,mDAA2B,CAAA;IAC3B,mEAA2C,CAAA;AAC/C,CAAC,EANW,oBAAoB,KAApB,oBAAoB,QAM/B;AAED,MAAM,OAAO,gBAAiB,SAAQ,eAAe;IAI7B;IAHpB,YACI,IAA0B,EAC1B,OAAe,EACC,KAAc;QAE9B,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAFL,UAAK,GAAL,KAAK,CAAS;IAGlC,CAAC;CACJ"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type TorrentMetadata } from '../../torrent';
|
|
2
|
+
import { MagnetParseError, MagnetParseErrorCode } from './errors';
|
|
3
|
+
import type { ParsedMagnetURI } from './types';
|
|
4
|
+
export declare const parseMagnet: (magnet: string, peerId: Uint8Array, options?: {
|
|
5
|
+
timeout?: number;
|
|
6
|
+
}) => Promise<TorrentMetadata>;
|
|
7
|
+
export declare const parseMagnetURI: (uri: string) => ParsedMagnetURI;
|
|
8
|
+
export { MagnetParseError, MagnetParseErrorCode };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { defaults } from '../../configs/defaults';
|
|
2
|
+
import { fetchMetadataFromPeer } from '../../peer/extended';
|
|
3
|
+
import {} from '../../torrent';
|
|
4
|
+
import { trackPeers } from '../../tracker';
|
|
5
|
+
import { decodeBase32 } from '../base32';
|
|
6
|
+
import { MagnetParseError, MagnetParseErrorCode } from './errors';
|
|
7
|
+
import { parseInfoDict } from './meta';
|
|
8
|
+
export const parseMagnet = async (magnet, peerId, options) => {
|
|
9
|
+
const data = parseMagnetURI(magnet);
|
|
10
|
+
if (data.trackers.length <= 0) {
|
|
11
|
+
throw new MagnetParseError(MagnetParseErrorCode.NOT_IMPLEMENTED, 'No trackers found, DHT is not yet implemented');
|
|
12
|
+
}
|
|
13
|
+
const peers = await trackPeers({
|
|
14
|
+
meta: {
|
|
15
|
+
infoHash: data.infoHash,
|
|
16
|
+
length: 0,
|
|
17
|
+
announce: undefined,
|
|
18
|
+
announceList: data.trackers.map((t) => [t]),
|
|
19
|
+
},
|
|
20
|
+
peerId: peerId,
|
|
21
|
+
timeoutMs: defaults.magnet.trackerTimeoutMs,
|
|
22
|
+
});
|
|
23
|
+
let raw;
|
|
24
|
+
try {
|
|
25
|
+
raw = await Promise.any(peers.map((p) => fetchMetadataFromPeer(p, data.infoHash, peerId, {
|
|
26
|
+
timeoutMs: options?.timeout ?? defaults.magnet.peerTimeoutMs,
|
|
27
|
+
})));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
throw new MagnetParseError(MagnetParseErrorCode.NO_METADATA, 'Failed to fetch metadata from any peer');
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
return parseInfoDict(raw, data.infoHash, undefined, data.trackers.map((t) => [t]));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new MagnetParseError(MagnetParseErrorCode.PARSING_FAILED, 'Failed to parse metadata from peer');
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
export const parseMagnetURI = (uri) => {
|
|
40
|
+
let url;
|
|
41
|
+
try {
|
|
42
|
+
url = new URL(uri);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
throw new MagnetParseError(MagnetParseErrorCode.INVALID_URI, 'Invalid magnet URI');
|
|
46
|
+
}
|
|
47
|
+
if (url.protocol !== 'magnet:') {
|
|
48
|
+
throw new MagnetParseError(MagnetParseErrorCode.INVALID_URI, 'Invalid magnet URI scheme');
|
|
49
|
+
}
|
|
50
|
+
const params = new URLSearchParams(url.search);
|
|
51
|
+
const xt = params.get('xt');
|
|
52
|
+
const name = params.get('dn') ?? undefined;
|
|
53
|
+
const trackers = params.getAll('tr');
|
|
54
|
+
if (!xt?.startsWith('urn:btih:'))
|
|
55
|
+
throw new MagnetParseError(MagnetParseErrorCode.INVALID_XT, 'Invalid xt parameter');
|
|
56
|
+
const raw = xt.slice(9);
|
|
57
|
+
if (raw.length !== 40 && raw.length !== 32) {
|
|
58
|
+
throw new MagnetParseError(MagnetParseErrorCode.INVALID_XT, 'Invalid info hash length');
|
|
59
|
+
}
|
|
60
|
+
if (raw.length === 40 && !isHex(raw)) {
|
|
61
|
+
throw new MagnetParseError(MagnetParseErrorCode.INVALID_XT, 'Invalid hex info hash');
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
infoHash: raw.length === 40 ? Uint8Array.fromHex(raw) : decodeBase32(raw),
|
|
65
|
+
name,
|
|
66
|
+
trackers,
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
const isHex = (str) => {
|
|
70
|
+
const hexRegex = /^[0-9a-fA-F]+$/;
|
|
71
|
+
return hexRegex.test(str) && str.length === 40;
|
|
72
|
+
};
|
|
73
|
+
export { MagnetParseError, MagnetParseErrorCode };
|
|
74
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/magnet/parser/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const textDecoder = new TextDecoder();
|
|
2
|
+
export const parseInfoDict = (dict, infoHash, announce, announceList = []) => {
|
|
3
|
+
if (!(dict instanceof Map))
|
|
4
|
+
throw new Error('Info dict must be a Map');
|
|
5
|
+
const name = dict.get('name');
|
|
6
|
+
if (!(name instanceof Uint8Array))
|
|
7
|
+
throw new Error('Missing name');
|
|
8
|
+
const pieceLength = dict.get('piece length');
|
|
9
|
+
if (typeof pieceLength !== 'number')
|
|
10
|
+
throw new Error('Missing piece length');
|
|
11
|
+
const piecesRaw = dict.get('pieces');
|
|
12
|
+
if (!(piecesRaw instanceof Uint8Array) || piecesRaw.byteLength % 20 !== 0)
|
|
13
|
+
throw new Error('Invalid pieces');
|
|
14
|
+
const pieces = [];
|
|
15
|
+
for (let i = 0; i < piecesRaw.byteLength; i += 20) {
|
|
16
|
+
pieces.push(piecesRaw.subarray(i, i + 20));
|
|
17
|
+
}
|
|
18
|
+
const filesRaw = dict.get('files');
|
|
19
|
+
if (Array.isArray(filesRaw)) {
|
|
20
|
+
let offset = 0;
|
|
21
|
+
const files = filesRaw.map((f) => {
|
|
22
|
+
if (!(f instanceof Map))
|
|
23
|
+
throw new Error('Invalid file entry');
|
|
24
|
+
const length = f.get('length');
|
|
25
|
+
if (typeof length !== 'number')
|
|
26
|
+
throw new Error('Missing file length');
|
|
27
|
+
const path = f.get('path');
|
|
28
|
+
if (!Array.isArray(path))
|
|
29
|
+
throw new Error('Missing file path');
|
|
30
|
+
const file = {
|
|
31
|
+
path: path.map((p) => {
|
|
32
|
+
if (!(p instanceof Uint8Array))
|
|
33
|
+
throw new Error('Invalid path segment');
|
|
34
|
+
return textDecoder.decode(p);
|
|
35
|
+
}),
|
|
36
|
+
length,
|
|
37
|
+
offset,
|
|
38
|
+
};
|
|
39
|
+
offset += length;
|
|
40
|
+
return file;
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
announce,
|
|
44
|
+
announceList,
|
|
45
|
+
infoHash,
|
|
46
|
+
name: textDecoder.decode(name),
|
|
47
|
+
pieceLength,
|
|
48
|
+
pieces,
|
|
49
|
+
length: offset,
|
|
50
|
+
files,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// single-file
|
|
54
|
+
const length = dict.get('length');
|
|
55
|
+
if (typeof length !== 'number')
|
|
56
|
+
throw new Error('Missing length');
|
|
57
|
+
return {
|
|
58
|
+
announce,
|
|
59
|
+
announceList,
|
|
60
|
+
infoHash,
|
|
61
|
+
name: textDecoder.decode(name),
|
|
62
|
+
pieceLength,
|
|
63
|
+
pieces,
|
|
64
|
+
length,
|
|
65
|
+
files: [{ path: [textDecoder.decode(name)], length, offset: 0 }],
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.js","sourceRoot":"","sources":["../../../src/magnet/parser/meta.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/magnet/parser/types.ts"],"names":[],"mappings":""}
|