magnetk 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Magnetk Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # Magnetk JavaScript SDK
2
+
3
+ The professional JavaScript SDK for interacting with the Magnetk P2P network. This SDK allows you to parse/generate Magnetk links and download files via TCP using the Magnetk binary protocol.
4
+
5
+ ## Installation
6
+
7
+ If using as a standalone folder, simply place the `magnetk` folder into your `node_modules` or clone it into your project.
8
+
9
+ ```bash
10
+ # If published to npm (future)
11
+ npm install magnetk-sdk-js
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ### Parsing a Magnetk Link
17
+
18
+ ```javascript
19
+ import { MagnetkLink } from 'magnetk';
20
+
21
+ const uri = 'magnetk:?xt=urn:sha256:abc123...&dn=myfile.zip&xl=1048576&relay=1.2.3.4';
22
+ const link = MagnetkLink.parse(uri);
23
+
24
+ console.log(link.fileName); // myfile.zip
25
+ console.log(link.fileHash); // abc123...
26
+ ```
27
+
28
+ ### Downloading a File
29
+
30
+ ```javascript
31
+ import { MagnetkClient } from 'magnetk';
32
+
33
+ const client = new MagnetkClient();
34
+ const magnetURI = 'magnetk:?...';
35
+
36
+ await client.download(magnetURI, './downloads/my_file.zip');
37
+ ```
38
+
39
+ ## Features
40
+
41
+ - **Pure JavaScript**: No native dependencies required for core protocol handling.
42
+ - **Fast TCP Discovery**: Connects directly to Magnetk Relay nodes to find seeds.
43
+ - **Binary Framing**: Efficient data transfer using the Magnetk framed protocol.
44
+ - **Magnet Link Support**: Full support for `magnetk:?` URI scheme.
45
+
46
+ ## API Reference
47
+
48
+ ### `MagnetkLink`
49
+ - `static parse(uri)`: Create a link object from string.
50
+ - `toString()`: Serialize back to string.
51
+
52
+ ### `MagnetkClient`
53
+ - `download(uri, outputPath)`: High-level download method.
54
+ - `lookupPeer(relayHost, relayPort, fileHash)`: Find seeds for a hash.
55
+
56
+ ## License
57
+ MIT
package/cli.js ADDED
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Magnetk CLI
5
+ *
6
+ * Usage:
7
+ * magnetk lookup <file_hash> [--relay <host>]
8
+ * magnetk download <magnetk_uri> [--output <path>]
9
+ */
10
+
11
+ import { MagnetkClient } from './client.js';
12
+ import path from 'path';
13
+ import os from 'os';
14
+
15
+ const args = process.argv.slice(2);
16
+ const command = args[0];
17
+
18
+ if (!command || command === '--help' || command === '-h' || command === 'help') {
19
+ showHelp();
20
+ process.exit(0);
21
+ }
22
+
23
+ const client = new MagnetkClient();
24
+
25
+ async function run() {
26
+ try {
27
+ switch (command) {
28
+ case 'lookup': {
29
+ const fileHash = args[1];
30
+ if (!fileHash) {
31
+ console.error('Error: Missing file hash.');
32
+ showHelp();
33
+ process.exit(1);
34
+ }
35
+
36
+ const relayArg = args.indexOf('--relay');
37
+ const relay = relayArg !== -1 ? args[relayArg + 1] : '69.169.109.243';
38
+ const port = 4003; // Default JS discovery port
39
+
40
+ const result = await client.lookupPeer(relay, port, fileHash);
41
+
42
+ if (result.found && result.seeds.length > 0) {
43
+ console.log(`\nFound ${result.seeds.length} seed(s) on relay ${relay}:`);
44
+ result.seeds.forEach((seed, i) => {
45
+ console.log(` [${i + 1}] Peer ID: ${seed.peer_id}`);
46
+ console.log(` Transport: ${seed.transport_addr || 'MsQuic/QUIC'}`);
47
+ });
48
+
49
+ // Generate a sample link if possible
50
+ if (result.seeds.length > 0) {
51
+ const seed = result.seeds[0];
52
+ const xt = `urn:sha256:${fileHash}`;
53
+ const relayAddr = `/ip4/${relay}/tcp/4001/p2p/${seed.peer_id}`; // Simplified
54
+ const link = `magnetk:?xt=${xt}&relay=${encodeURIComponent(relayAddr)}&dn=downloaded_file`;
55
+ console.log(`\nGenerated Magnetk Link:\n${link}\n`);
56
+ }
57
+ } else {
58
+ console.log(`\nNo seeds found for hash: ${fileHash}`);
59
+ }
60
+ break;
61
+ }
62
+
63
+ case 'download': {
64
+ const magnetURI = args[1];
65
+ if (!magnetURI) {
66
+ console.error('Error: Missing Magnetk link.');
67
+ showHelp();
68
+ process.exit(1);
69
+ }
70
+
71
+ const outputArg = args.indexOf('--output');
72
+ const outputPath = outputArg !== -1 ? args[outputArg + 1] : path.join(os.homedir(), 'Desktop', 'downloaded_file');
73
+
74
+ console.log(`\n--- Magnetk P2P Download ---`);
75
+ console.log(`Target: ${outputPath}`);
76
+
77
+ await client.download(magnetURI, outputPath);
78
+ break;
79
+ }
80
+
81
+ default:
82
+ console.error(`Unknown command: ${command}`);
83
+ showHelp();
84
+ process.exit(1);
85
+ }
86
+ } catch (err) {
87
+ console.error(`\n[CLI Error] ${err.message}`);
88
+ process.exit(1);
89
+ }
90
+ }
91
+
92
+ function showHelp() {
93
+ console.log(`
94
+ Magnetk SDK CLI v2.0.0
95
+ ----------------------
96
+ A high-performance P2P file transfer utility.
97
+
98
+ Usage:
99
+ magnetk <command> [options]
100
+
101
+ Commands:
102
+ lookup <hash> Search the relay for seeds hosting a specific file hash.
103
+ download <link> Download a file using a Magnetk URI.
104
+
105
+ Options:
106
+ --relay <host> Specify a relay IP (default: 69.169.109.243)
107
+ --output <path> Path where to save the downloaded file (default: Desktop)
108
+ --help, -h Show this help message
109
+
110
+ Examples:
111
+ magnetk lookup abc123hash
112
+ magnetk download "magnetk:?xt=urn:sha256:abc123hash&relay=..."
113
+ `);
114
+ }
115
+
116
+ run();
package/client.js ADDED
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Magnet JS SDK - Client
3
+ *
4
+ * Provides methods for discovery and file download using the binary framing protocol over TCP.
5
+ * Note: Currently supports fetching metadata/manifest. Full QUIC download requires C++ bindings.
6
+ */
7
+
8
+ import net from 'net';
9
+ import fs from 'fs';
10
+ import { MagnetkLink } from './magnetk.js';
11
+
12
+ const MSG_TYPE = {
13
+ MSG_LOOKUP_PEER: 0x03,
14
+ MSG_LOOKUP_RESULT: 0x04,
15
+ MSG_MANIFEST_REQ: 0x10,
16
+ MSG_MANIFEST_RES: 0x11,
17
+ MSG_CHUNK_REQ: 0x20,
18
+ MSG_CHUNK_RES: 0x21,
19
+ MSG_ERROR: 0xFF
20
+ };
21
+
22
+ export class MagnetkClient {
23
+ constructor(config = {}) {
24
+ this.config = config;
25
+ this.connections = new Map();
26
+ }
27
+
28
+ /**
29
+ * Connects to a seed via TCP.
30
+ * @param {string} host
31
+ * @param {number} port
32
+ * @returns {Promise<net.Socket>}
33
+ */
34
+ connect(host, port) {
35
+ return new Promise((resolve, reject) => {
36
+ const socket = net.createConnection(port, host, () => {
37
+ console.log(`[Magnetk-JS] Connected to ${host}:${port}`);
38
+ resolve(socket);
39
+ });
40
+ socket.on('error', (err) => {
41
+ console.error(`[Magnetk-JS] Connection error: ${err.message}`);
42
+ reject(err);
43
+ });
44
+ });
45
+ }
46
+
47
+ /**
48
+ * Look up seeds for a file hash from the relay.
49
+ * @param {string} relayHost
50
+ * @param {number} relayPort
51
+ * @param {string} fileHash
52
+ * @returns {Promise<Object>}
53
+ */
54
+ async lookupPeer(relayHost, relayPort, fileHash) {
55
+ console.log(`[Magnetk-JS] Looking up seeds for ${fileHash.substring(0, 10)} on relay ${relayHost}:${relayPort}...`);
56
+ const socket = await this.connect(relayHost, relayPort);
57
+ const writer = new FrameWriter(socket);
58
+ const reader = new FrameReader(socket);
59
+
60
+ try {
61
+ await writer.writeJSON(MSG_TYPE.MSG_LOOKUP_PEER, { file_hash: fileHash });
62
+ const frame = await reader.readFrame();
63
+
64
+ if (frame.type === MSG_TYPE.MSG_LOOKUP_RESULT) {
65
+ const result = JSON.parse(frame.payload.toString());
66
+ return result;
67
+ } else if (frame.type === MSG_TYPE.MSG_ERROR) {
68
+ const err = JSON.parse(frame.payload.toString());
69
+ throw new Error(`Relay error: ${err.message}`);
70
+ } else {
71
+ throw new Error(`Unexpected response type: 0x${frame.type.toString(16)}`);
72
+ }
73
+ } finally {
74
+ socket.end();
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Downloads a file from a magnetk link.
80
+ * @param {string} magnetURI
81
+ * @param {string} outputPath
82
+ */
83
+ async download(magnetURI, outputPath) {
84
+ const ml = MagnetkLink.parse(magnetURI);
85
+ console.log(`[Magnetk-JS] Starting download for: ${ml.fileName}`);
86
+
87
+ // 1. Resolve seed address from the Relay
88
+ // We extract the relay host from the Magnetk Link
89
+ const relayHost = ml.relayAddr.split('/')[2];
90
+ const discoveryPort = 4003; // New TCP discovery port on relay
91
+
92
+ console.log(`[Magnetk-JS] Querying relay ${relayHost}:${discoveryPort} for seeds...`);
93
+ let lookupResult;
94
+ try {
95
+ lookupResult = await this.lookupPeer(relayHost, discoveryPort, ml.fileHash);
96
+ } catch (e) {
97
+ console.error(`[Magnetk-JS] Initial lookup failed: ${e.message}`);
98
+ console.log(`[Magnetk-JS] Attempting fallback to direct connection...`);
99
+ }
100
+
101
+ let seedHost = relayHost; // Default to relay as fallback (unlikely to work for data)
102
+ let seedPort = 4002;
103
+
104
+ if (lookupResult && lookupResult.found && lookupResult.seeds.length > 0) {
105
+ const seed = lookupResult.seeds[0];
106
+ console.log(`[Magnetk-JS] Found seed: ${seed.peer_id}`);
107
+
108
+ if (seed.transport_addr) {
109
+ const parts = seed.transport_addr.split(':');
110
+ seedHost = parts[0];
111
+ seedPort = parseInt(parts[1], 10) || 4002;
112
+ }
113
+ }
114
+
115
+ // SPECIAL CASE for local testing:
116
+ // If the seed host is exactly the same as the relay host, and the connection refused,
117
+ // it's likely because the seeder is actually LOCAL to the user's PC.
118
+ // We'll try to connect to localhost if the direct connection fails.
119
+
120
+ console.log(`[Magnetk-JS] Connecting to seed at ${seedHost}:${seedPort} (TCP)...`);
121
+
122
+ let socket;
123
+ try {
124
+ socket = await this.connect(seedHost, seedPort);
125
+ } catch (e) {
126
+ if (seedHost !== '127.0.0.1' && seedHost !== 'localhost') {
127
+ console.log(`[Magnetk-JS] Connection to ${seedHost} failed. Trying LOCALHOST fallback for development...`);
128
+ socket = await this.connect('127.0.0.1', 4002);
129
+ seedHost = '127.0.0.1';
130
+ } else {
131
+ throw e;
132
+ }
133
+ }
134
+
135
+ const writer = new FrameWriter(socket);
136
+ const reader = new FrameReader(socket);
137
+
138
+ try {
139
+ // STEP 1: Request manifest
140
+ console.log(`[Magnetk-JS] Requesting manifest for ${ml.fileHash.substring(0, 10)}...`);
141
+ await writer.writeJSON(MSG_TYPE.MSG_MANIFEST_REQ, { file_hash: ml.fileHash });
142
+
143
+ const manifestFrame = await reader.readFrame();
144
+ if (manifestFrame.type !== MSG_TYPE.MSG_MANIFEST_RES) {
145
+ throw new Error(`Unexpected manifest response type: 0x${manifestFrame.type.toString(16)}`);
146
+ }
147
+
148
+ const manifest = JSON.parse(manifestFrame.payload.toString());
149
+ console.log(`[Magnetk-JS] Manifest received: ${manifest.file_name}, ${manifest.total_chunks} chunks, total size: ${manifest.file_size} bytes`);
150
+
151
+ // STEP 2: Prepare output file
152
+ const fd = fs.openSync(outputPath, 'w');
153
+
154
+ // STEP 3: Request and write chunks
155
+ for (let i = 0; i < manifest.total_chunks; i++) {
156
+ process.stdout.write(`\r[Magnetk-JS] Downloading chunk ${i + 1}/${manifest.total_chunks}...`);
157
+
158
+ await writer.writeJSON(MSG_TYPE.MSG_CHUNK_REQ, {
159
+ file_hash: ml.fileHash,
160
+ chunk_indices: [i]
161
+ });
162
+
163
+ const chunkFrame = await reader.readFrame();
164
+ if (chunkFrame.type !== MSG_TYPE.MSG_CHUNK_RES) {
165
+ throw new Error(`Unexpected chunk response type: 0x${chunkFrame.type.toString(16)}`);
166
+ }
167
+
168
+ // Chunk payload format: [4 bytes: index][raw data]
169
+ const index = chunkFrame.payload.readUInt32BE(0);
170
+ const data = chunkFrame.payload.slice(4);
171
+
172
+ // Write to file at correct offset
173
+ const offset = i * manifest.chunk_size;
174
+ fs.writeSync(fd, data, 0, data.length, offset);
175
+ }
176
+
177
+ fs.closeSync(fd);
178
+ console.log(`\n[Magnetk-JS] ✓ Download complete: ${outputPath}`);
179
+
180
+ } catch (err) {
181
+ console.error(`[Magnetk-JS] Download failed: ${err.message}`);
182
+ throw err;
183
+ } finally {
184
+ socket.end();
185
+ }
186
+ }
187
+ }
188
+
189
+ class FrameWriter {
190
+ constructor(socket) {
191
+ this.socket = socket;
192
+ }
193
+
194
+ async writeFrame(type, payload) {
195
+ const header = Buffer.alloc(5);
196
+ header.writeUInt32BE(payload.length, 0);
197
+ header.writeUInt8(type, 4);
198
+
199
+ this.socket.write(header);
200
+ this.socket.write(payload);
201
+ }
202
+
203
+ async writeJSON(type, obj) {
204
+ const payload = Buffer.from(JSON.stringify(obj));
205
+ await this.writeFrame(type, payload);
206
+ }
207
+ }
208
+
209
+ class FrameReader {
210
+ constructor(socket) {
211
+ this.socket = socket;
212
+ this.buffer = Buffer.alloc(0);
213
+ this.pendingResolve = null;
214
+ this.socket.on('data', (chunk) => this._onData(chunk));
215
+ }
216
+
217
+ _onData(chunk) {
218
+ this.buffer = Buffer.concat([this.buffer, chunk]);
219
+ this._tryResolve();
220
+ }
221
+
222
+ _tryResolve() {
223
+ if (!this.pendingResolve) return;
224
+
225
+ if (this.buffer.length >= 5) {
226
+ const len = this.buffer.readUInt32BE(0);
227
+ const type = this.buffer.readUInt8(4);
228
+
229
+ if (this.buffer.length >= 5 + len) {
230
+ const payload = this.buffer.slice(5, 5 + len);
231
+ this.buffer = this.buffer.slice(5 + len);
232
+
233
+ const resolve = this.pendingResolve;
234
+ this.pendingResolve = null;
235
+ resolve({ type, payload });
236
+
237
+ // Try again for any buffered data
238
+ this._tryResolve();
239
+ }
240
+ }
241
+ }
242
+
243
+ readFrame() {
244
+ return new Promise((resolve) => {
245
+ this.pendingResolve = resolve;
246
+ this._tryResolve();
247
+ });
248
+ }
249
+ }
package/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './client.js';
2
+ export * from './magnetk.js';
package/magnetk.js ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Magnetk Link Parser and Generator for Magnetk P2P
3
+ */
4
+ export class MagnetkLink {
5
+ constructor(fileHash, fileName, fileSize, seedId, relayAddr) {
6
+ this.fileHash = fileHash;
7
+ this.fileName = fileName;
8
+ this.fileSize = fileSize;
9
+ this.seedId = seedId;
10
+ this.relayAddr = relayAddr;
11
+ }
12
+
13
+ /**
14
+ * Parses a magnetk URI string.
15
+ * Format: magnetk:?xt=urn:sha256:<hash>&dn=<name>&xl=<size>&xs=<seed_id>&relay=<relay_addr>
16
+ * @param {string} uri
17
+ * @returns {MagnetkLink}
18
+ */
19
+ static parse(uri) {
20
+ if (!uri.startsWith('magnetk:?')) {
21
+ throw new Error('Invalid magnet link: must start with magnetk:?');
22
+ }
23
+
24
+ const params = new URLSearchParams(uri.slice(9)); // slicing 'magnetk:?' length is 9? No. 'magnet:?' is 8. 'magnetk:?' is 9. Correct.
25
+ const xt = params.get('xt');
26
+ if (!xt || !xt.startsWith('urn:sha256:')) {
27
+ throw new Error('Invalid magnet link: missing xt (urn:sha256:...)');
28
+ }
29
+
30
+ const fileHash = xt.split(':')[2];
31
+ const fileName = params.get('dn') || 'unknown_file';
32
+ const fileSize = parseInt(params.get('xl') || '0', 10);
33
+ const seedId = params.get('xs') || '';
34
+ const relayAddr = params.get('relay') || '';
35
+
36
+ return new MagnetkLink(fileHash, fileName, fileSize, seedId, relayAddr);
37
+ }
38
+
39
+ /**
40
+ * Generates a magnetk URI string.
41
+ * @returns {string}
42
+ */
43
+ toString() {
44
+ const parts = [];
45
+ parts.push(`xt=urn:sha256:${this.fileHash}`);
46
+ if (this.fileName) parts.push(`dn=${encodeURIComponent(this.fileName)}`);
47
+ if (this.fileSize > 0) parts.push(`xl=${this.fileSize}`);
48
+ if (this.seedId) parts.push(`xs=${encodeURIComponent(this.seedId)}`);
49
+ if (this.relayAddr) parts.push(`relay=${this.relayAddr}`); // Multiaddr usually kept raw
50
+
51
+ return `magnetk:?${parts.join('&')}`;
52
+ }
53
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "magnetk",
3
+ "version": "2.0.0",
4
+ "description": "JavaScript SDK for Magnetk P2P File Transfer System",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "magnetk": "cli.js"
8
+ },
9
+ "type": "module",
10
+ "files": [
11
+ "client.js",
12
+ "magnetk.js",
13
+ "index.js",
14
+ "utils.js",
15
+ "README.md",
16
+ "LICENSE",
17
+ "package.json"
18
+ ],
19
+ "scripts": {
20
+ "test": "node test.js"
21
+ },
22
+ "dependencies": {},
23
+ "devDependencies": {
24
+ "minimist": "^1.2.8"
25
+ },
26
+ "keywords": [
27
+ "magnetk",
28
+ "p2p",
29
+ "quic",
30
+ "file-transfer",
31
+ "sdk"
32
+ ],
33
+ "author": "Magnetk Contributors",
34
+ "license": "MIT"
35
+ }
package/utils.js ADDED
@@ -0,0 +1,38 @@
1
+ export class MagnetkLink {
2
+ constructor(fileHash, fileName, relayAddr = '', seedID = '') {
3
+ this.fileHash = fileHash;
4
+ this.fileName = fileName;
5
+ this.relayAddr = relayAddr;
6
+ this.seedID = seedID;
7
+ }
8
+
9
+ toString() {
10
+ let link = `magnetk:?xt=urn:sha256:${this.fileHash}&dn=${encodeURIComponent(this.fileName)}`;
11
+ if (this.relayAddr) {
12
+ link += `&relay=${encodeURIComponent(this.relayAddr)}`;
13
+ }
14
+ if (this.seedID) {
15
+ link += `&xs=${encodeURIComponent(this.seedID)}`;
16
+ }
17
+ return link;
18
+ }
19
+
20
+ static parse(linkStr) {
21
+ if (!linkStr.startsWith('magnetk:?')) {
22
+ throw new Error('Invalid magnet link: must start with magnetk:?');
23
+ }
24
+
25
+ const params = new URLSearchParams(linkStr.substring(9));
26
+ const xt = params.get('xt');
27
+ if (!xt || !xt.startsWith('urn:sha256:')) {
28
+ throw new Error('Invalid magnet link: missing sha256 hash');
29
+ }
30
+
31
+ const fileHash = xt.substring(11);
32
+ const fileName = params.get('dn') || '';
33
+ const relayAddr = params.get('relay') || '';
34
+ const seedID = params.get('xs') || '';
35
+
36
+ return new MagnetkLink(fileHash, fileName, relayAddr, seedID);
37
+ }
38
+ }