magnetk 2.2.4 → 2.2.5

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/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * Magnetk CLI
@@ -20,7 +20,9 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
20
20
  process.exit(0);
21
21
  }
22
22
 
23
- const client = new MagnetkClient();
23
+ const client = new MagnetkClient({
24
+ dhtEnabled: true // Enable DHT discovery
25
+ });
24
26
 
25
27
  async function run() {
26
28
  try {
package/client.js CHANGED
@@ -34,16 +34,57 @@ export const CONNECTION_TYPE = {
34
34
  UNKNOWN: 'UNKNOWN'
35
35
  };
36
36
 
37
+ const DEFAULT_CONFIG_CONTENT = `# Magnetk P2P Configuration
38
+ # Auto-generated by Magnetk JS SDK. Edit as needed.
39
+ # All values are optional - defaults are used when not specified
40
+
41
+ # ===== Network Binding =====
42
+ BIND_ADDRESS=0.0.0.0
43
+ DISCOVERY_PORT=4001
44
+ TRANSPORT_PORT=4002
45
+ LOCALHOST_MODE=false
46
+
47
+ # ===== Relay =====
48
+ RELAY_ADDRESS=0.0.0.0:4001
49
+
50
+ # Multi-relay: comma-separated for failover/scaling
51
+ # RELAY_ADDRESSES=
52
+
53
+ # ===== DHT (Decentralized Discovery) =====
54
+ DHT_ENABLED=false
55
+ DHT_MODE=auto
56
+ # Default: Public IPFS bootstrap nodes
57
+ DHT_BOOTSTRAP_PEERS=/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN,/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa,/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb,/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt,/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
58
+
59
+ # ===== Discovery Priority =====
60
+ # Order: dht, relay, direct (comma-separated)
61
+ DISCOVERY_PRIORITY=dht,relay,direct
62
+
63
+ # ===== Chunking =====
64
+ CHUNK_SIZE_MB=1
65
+ MAX_PARALLEL_CHUNKS=10
66
+ `;
67
+
37
68
  export class MagnetkClient extends EventEmitter {
38
69
  constructor(config = {}) {
39
70
  super();
40
71
  this.config = {
72
+ // Relay settings (supports multiple relays for failover)
41
73
  relayUrl: '69.169.109.243',
42
74
  relayPort: 4003,
75
+ relays: [], // Array of {host, port} for multi-relay failover
43
76
  seedPort: 4002,
44
77
  enableSTUN: true,
45
78
  seederPath: null,
46
79
  configPath: null,
80
+
81
+ // DHT settings
82
+ dhtEnabled: false, // Enable DHT discovery
83
+ dhtBootstrapPeers: [], // Custom bootstrap peer multiaddrs
84
+ discoveryPriority: ['dht', 'relay', 'direct'], // Discovery order
85
+
86
+ // Auto-config: create settings.conf if missing
87
+ autoConfig: true,
47
88
  ...config
48
89
  };
49
90
  this.processes = [];
@@ -178,6 +219,32 @@ export class MagnetkClient extends EventEmitter {
178
219
  }
179
220
  }
180
221
 
222
+ /**
223
+ * Look up seeds with multi-relay failover.
224
+ * Tries each relay in order until one succeeds.
225
+ * @param {string} fileHash
226
+ * @returns {Promise<Object>}
227
+ */
228
+ async lookupPeerWithFailover(fileHash) {
229
+ // Build relay list: primary + configured relays
230
+ const relays = [
231
+ { host: this.config.relayUrl, port: this.config.relayPort },
232
+ ...this.config.relays
233
+ ];
234
+
235
+ let lastError = null;
236
+ for (const relay of relays) {
237
+ try {
238
+ const result = await this.lookupPeer(relay.host, relay.port, fileHash);
239
+ if (result && result.found) return result;
240
+ } catch (e) {
241
+ console.log(`[Magnetk-JS] Relay ${relay.host}:${relay.port} failed: ${e.message}`);
242
+ lastError = e;
243
+ }
244
+ }
245
+ throw lastError || new Error('All relays failed');
246
+ }
247
+
181
248
  /**
182
249
  * Seeds a file and returns a Magnetk link.
183
250
  * @param {string} filePath
@@ -190,7 +257,6 @@ export class MagnetkClient extends EventEmitter {
190
257
  }
191
258
 
192
259
  this.emit('validating-relay');
193
- // Simple reachability check (optional but good for UX)
194
260
 
195
261
  const fileStats = fs.statSync(absolutePath);
196
262
  this.emit('hashing', { percent: 0 });
@@ -221,12 +287,26 @@ export class MagnetkClient extends EventEmitter {
221
287
  }
222
288
 
223
289
  const args = [
224
- '-relay', relayMultiaddr,
225
290
  '-file', absolutePath,
226
291
  '-port', this.config.seedPort.toString(),
227
292
  '-config', configPath
228
293
  ];
229
294
 
295
+ // Add relay if available
296
+ if (relayPeerId || this.config.relayUrl) {
297
+ args.push('-relay', relayMultiaddr);
298
+ }
299
+
300
+ // Add DHT if enabled
301
+ if (this.config.dhtEnabled) {
302
+ args.push('-dht');
303
+ }
304
+
305
+ // Add bootstrap peers if configured
306
+ if (this.config.dhtBootstrapPeers && this.config.dhtBootstrapPeers.length > 0) {
307
+ args.push('-bootstrap', this.config.dhtBootstrapPeers.join(','));
308
+ }
309
+
230
310
  console.log(`[Magnetk-JS] Spawning seeder: ${binPath} ${args.join(' ')}`);
231
311
 
232
312
  return new Promise((resolve, reject) => {
@@ -251,10 +331,13 @@ export class MagnetkClient extends EventEmitter {
251
331
 
252
332
  seeder.stderr.on('data', (data) => {
253
333
  const msg = data.toString();
254
- console.log(`[Seeder Stderr] ${msg}`); // Uncommented for debug
334
+ console.log(`[Seeder Stderr] ${msg}`);
255
335
  if (msg.includes('Connected to relay')) {
256
336
  this.emit('relay-connected', { host: this.config.relayUrl, port: 4001 });
257
337
  }
338
+ if (msg.includes('DHT') && msg.includes('announced')) {
339
+ this.emit('dht-announced');
340
+ }
258
341
  });
259
342
 
260
343
  seeder.on('error', (err) => {
@@ -320,7 +403,7 @@ export class MagnetkClient extends EventEmitter {
320
403
  });
321
404
  }
322
405
  }
323
- addressesToTry.push('127.0.0.1:4002');
406
+ addressesToTry.push(`127.0.0.1:${this.config.seedPort}`);
324
407
 
325
408
  const uniqueAddrs = [...new Set(addressesToTry)];
326
409
  let socket;
@@ -392,11 +475,66 @@ export class MagnetkClient extends EventEmitter {
392
475
  }
393
476
 
394
477
  /**
395
- * Finds available seeders for a hash.
478
+ * Finds available seeders for a hash (with multi-relay failover).
396
479
  */
397
480
  async lookupSeeds(fileHash) {
398
- const result = await this.lookupPeer(this.config.relayUrl, this.config.relayPort, fileHash);
399
- return result.found ? result.seeds : [];
481
+ try {
482
+ const result = await this.lookupPeerWithFailover(fileHash);
483
+ return result.found ? result.seeds : [];
484
+ } catch (e) {
485
+ return [];
486
+ }
487
+ }
488
+
489
+ // ═══════════════════════════════════════════════════
490
+ // MINIMAL-CODE API (for SDK users writing less code)
491
+ // ═══════════════════════════════════════════════════
492
+
493
+ /**
494
+ * Share a file with one function call.
495
+ * Handles hashing, seeding, and returns the magnet link.
496
+ *
497
+ * @example
498
+ * const link = await magnetk.share('./my-file.zip');
499
+ * console.log('Share this link:', link);
500
+ *
501
+ * @param {string} filePath - Path to the file to share
502
+ * @returns {Promise<string>} Magnet link
503
+ */
504
+ async share(filePath) {
505
+ return this.seed(filePath);
506
+ }
507
+
508
+ /**
509
+ * Download a file with one function call.
510
+ * Handles discovery, downloading, and verification.
511
+ *
512
+ * @example
513
+ * await magnetk.get('magnetk:?xt=urn:sha256:abc...', './downloads/');
514
+ *
515
+ * @param {string} magnetURI - Magnet link to download
516
+ * @param {string} [outputPath] - Output directory or file path (default: current directory)
517
+ * @returns {Promise<string>} Path to the downloaded file
518
+ */
519
+ async get(magnetURI, outputPath) {
520
+ const ml = typeof magnetURI === 'string' ? MagnetkLink.parse(magnetURI) : magnetURI;
521
+ const output = outputPath || `./${ml.fileName}`;
522
+ await this.download(magnetURI, output);
523
+ return output;
524
+ }
525
+
526
+ /**
527
+ * Hash a file and return its SHA-256 hash.
528
+ * Useful when users want to pre-compute hashes.
529
+ *
530
+ * @example
531
+ * const hash = await magnetk.hash('./my-file.zip');
532
+ *
533
+ * @param {string} filePath
534
+ * @returns {Promise<string>} Hex-encoded SHA-256 hash
535
+ */
536
+ async hash(filePath) {
537
+ return this._calculateHash(path.resolve(filePath));
400
538
  }
401
539
 
402
540
  _calculateHash(filePath) {
@@ -433,7 +571,7 @@ export class MagnetkClient extends EventEmitter {
433
571
  if (fs.existsSync(p)) return p;
434
572
  }
435
573
 
436
- return binName; // Fallback to PATH (e.g. "seed" or "seed.exe")
574
+ return binName; // Fallback to PATH
437
575
  }
438
576
 
439
577
  _resolveConfigPath() {
@@ -441,14 +579,31 @@ export class MagnetkClient extends EventEmitter {
441
579
  if (this.config.configPath) return this.config.configPath;
442
580
 
443
581
  // 2. Check local dev environment
444
- const paths = [
582
+ const searchPaths = [
445
583
  path.join(process.cwd(), 'config', 'settings.conf'),
446
584
  path.join(process.cwd(), '..', '..', '..', 'config', 'settings.conf'),
447
585
  path.join(process.cwd(), '..', '..', 'config', 'settings.conf')
448
586
  ];
449
- for (const p of paths) {
587
+ for (const p of searchPaths) {
450
588
  if (fs.existsSync(p)) return p;
451
589
  }
590
+
591
+ // 3. Auto-create settings.conf in current directory
592
+ if (this.config.autoConfig) {
593
+ const autoPath = path.join(process.cwd(), 'config', 'settings.conf');
594
+ try {
595
+ const configDir = path.dirname(autoPath);
596
+ if (!fs.existsSync(configDir)) {
597
+ fs.mkdirSync(configDir, { recursive: true });
598
+ }
599
+ fs.writeFileSync(autoPath, DEFAULT_CONFIG_CONTENT);
600
+ console.log(`[Magnetk-JS] Created default config at ${autoPath}`);
601
+ return autoPath;
602
+ } catch (e) {
603
+ console.warn(`[Magnetk-JS] Could not auto-create config: ${e.message}`);
604
+ }
605
+ }
606
+
452
607
  return ''; // Let seeder use default
453
608
  }
454
609
  }
package/download.js ADDED
@@ -0,0 +1,95 @@
1
+ import { MagnetkClient, CONNECTION_TYPE } from 'magnetk';
2
+ import path from 'path';
3
+ import os from 'os';
4
+
5
+ // Parse command line arguments
6
+ const args = process.argv.slice(2);
7
+ if (args.length < 1) {
8
+ console.error('Usage: node download.js <magnetk_link>');
9
+ process.exit(1);
10
+ }
11
+
12
+ const magnetURI = args[0];
13
+
14
+ // Determine Desktop Path
15
+ const desktopPath = path.join(os.homedir(), '/OneDrive/Desktop');
16
+
17
+ async function download() {
18
+ const client = new MagnetkClient({
19
+ dhtEnabled: true, // Enable DHT discovery
20
+ relayUrl: '69.169.109.243',
21
+ relayPort: 4003,
22
+ seedPort: 4005
23
+ });
24
+
25
+ // Extract filename from URI roughly to show intention
26
+ // In real download, client.download handles parsing
27
+ let intendedFilename = 'downloaded_file';
28
+ try {
29
+ const urlParams = new URL(magnetURI.replace('magnetk:', 'http://dummy.com')).searchParams;
30
+ if (urlParams.has('dn')) intendedFilename = urlParams.get('dn');
31
+ } catch (e) { }
32
+
33
+ const outputPath = path.join(desktopPath, intendedFilename);
34
+
35
+ console.log(`[Download] Initiating download for: ${magnetURI}`);
36
+ console.log(`[Download] Target: ${outputPath}`);
37
+
38
+ // Check Public Identity
39
+ try {
40
+ console.log('[Download] Resolving Public Identity (STUN)...');
41
+ const identity = await client.getPublicIdentity();
42
+ console.log(`[Download] Public Identity: ${identity.ip}:${identity.port}`);
43
+ } catch (e) {
44
+ console.warn(`[Download] STUN lookup failed: ${e.message}`);
45
+ }
46
+
47
+ // Progress Tracking
48
+ client.on('download-start', (info) => console.log(`[Download] Started: ${info.fileName}`));
49
+
50
+ let lastPercent = -1;
51
+ client.on('progress', (p) => {
52
+ // Show progress bar-like output
53
+ const percent = Math.floor(p.percent);
54
+ if (percent > lastPercent) {
55
+ const barWidth = 20;
56
+ const filled = Math.floor((percent / 100) * barWidth);
57
+ const empty = barWidth - filled;
58
+ const bar = '█'.repeat(filled) + '░'.repeat(empty);
59
+
60
+ process.stdout.write(`\r[Download] [${bar}] ${p.percent.toFixed(1)}% | ${p.speed} KB/s | ${p.bytesDownloaded} / ${p.totalBytes} bytes`);
61
+ lastPercent = percent;
62
+ }
63
+ });
64
+
65
+ client.on('connection-type', (c) => {
66
+ let typeStr = c.type;
67
+ if (c.type === CONNECTION_TYPE.DIRECT) typeStr += ' (P2P Direct)';
68
+ if (c.type === CONNECTION_TYPE.LOCAL) typeStr += ' (Local Network)';
69
+ if (c.type === CONNECTION_TYPE.RELAYED) typeStr += ' (Via Relay)';
70
+ console.log(`\n[Download] Connection established: ${typeStr}`);
71
+ });
72
+
73
+ client.on('error', (err) => console.error(`\n[Download] Error: ${err.message}`));
74
+
75
+ try {
76
+ // Use new simplified API
77
+ await client.get(magnetURI, outputPath);
78
+ console.log('\n\n==================================================');
79
+ console.log(' DOWNLOAD COMPLETE!');
80
+ console.log('==================================================');
81
+ console.log(`File saved to: ${outputPath}`);
82
+
83
+ // Show stats
84
+ const stats = client.lastDownloadStats;
85
+ console.log('Final Stats:');
86
+ console.log(`- Connection Type: ${stats.connectionType}`);
87
+ console.log(`- Remote Address: ${stats.remoteAddr}`);
88
+
89
+ } catch (err) {
90
+ console.error('\n[Download] Failed:', err);
91
+ process.exit(1);
92
+ }
93
+ }
94
+
95
+ download();
package/magnetk.js CHANGED
@@ -1,18 +1,20 @@
1
1
  /**
2
2
  * Magnetk Link Parser and Generator for Magnetk P2P
3
+ * Supports DHT discovery and multi-relay configuration.
3
4
  */
4
5
  export class MagnetkLink {
5
- constructor(fileHash, fileName, fileSize, seedId, relayAddr) {
6
+ constructor(fileHash, fileName, fileSize, seedId, relayAddr, dhtEnabled = false) {
6
7
  this.fileHash = fileHash;
7
8
  this.fileName = fileName;
8
9
  this.fileSize = fileSize;
9
10
  this.seedId = seedId;
10
11
  this.relayAddr = relayAddr;
12
+ this.dhtEnabled = dhtEnabled;
11
13
  }
12
14
 
13
15
  /**
14
16
  * Parses a magnetk URI string.
15
- * Format: magnetk:?xt=urn:sha256:<hash>&dn=<name>&xl=<size>&xs=<seed_id>&relay=<relay_addr>
17
+ * Format: magnetk:?xt=urn:sha256:<hash>&dn=<name>&xl=<size>&xs=<seed_id>&relay=<relay_addr>&dht=true
16
18
  * @param {string} uri
17
19
  * @returns {MagnetkLink}
18
20
  */
@@ -21,7 +23,7 @@ export class MagnetkLink {
21
23
  throw new Error('Invalid magnet link: must start with magnetk:?');
22
24
  }
23
25
 
24
- const params = new URLSearchParams(uri.slice(9)); // slicing 'magnetk:?' length is 9? No. 'magnet:?' is 8. 'magnetk:?' is 9. Correct.
26
+ const params = new URLSearchParams(uri.slice(9));
25
27
  const xt = params.get('xt');
26
28
  if (!xt || !xt.startsWith('urn:sha256:')) {
27
29
  throw new Error('Invalid magnet link: missing xt (urn:sha256:...)');
@@ -32,8 +34,15 @@ export class MagnetkLink {
32
34
  const fileSize = parseInt(params.get('xl') || '0', 10);
33
35
  const seedId = params.get('xs') || '';
34
36
  const relayAddr = params.get('relay') || '';
37
+ const dhtParam = (params.get('dht') || '').toLowerCase();
38
+ const dhtEnabled = dhtParam === 'true' || dhtParam === '1';
35
39
 
36
- return new MagnetkLink(fileHash, fileName, fileSize, seedId, relayAddr);
40
+ // Must have at least one discovery method
41
+ if (!relayAddr && !dhtEnabled) {
42
+ throw new Error('Magnet link needs either relay address or dht=true for discovery');
43
+ }
44
+
45
+ return new MagnetkLink(fileHash, fileName, fileSize, seedId, relayAddr, dhtEnabled);
37
46
  }
38
47
 
39
48
  /**
@@ -46,8 +55,15 @@ export class MagnetkLink {
46
55
  if (this.fileName) parts.push(`dn=${encodeURIComponent(this.fileName)}`);
47
56
  if (this.fileSize > 0) parts.push(`xl=${this.fileSize}`);
48
57
  if (this.seedId) parts.push(`xs=${encodeURIComponent(this.seedId)}`);
49
- if (this.relayAddr) parts.push(`relay=${this.relayAddr}`); // Multiaddr usually kept raw
58
+ if (this.relayAddr) parts.push(`relay=${this.relayAddr}`);
59
+ if (this.dhtEnabled) parts.push('dht=true');
50
60
 
51
61
  return `magnetk:?${parts.join('&')}`;
52
62
  }
63
+
64
+ /** @returns {boolean} */
65
+ hasRelay() { return !!this.relayAddr; }
66
+
67
+ /** @returns {boolean} */
68
+ hasDHT() { return this.dhtEnabled; }
53
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magnetk",
3
- "version": "2.2.4",
3
+ "version": "2.2.5",
4
4
  "description": "JavaScript SDK for Magnetk P2P File Transfer System",
5
5
  "main": "index.js",
6
6
  "bin": {
package/share.js ADDED
@@ -0,0 +1,62 @@
1
+ import { MagnetkClient } from 'magnetk';
2
+ import path from 'path';
3
+ import 'dotenv/config';
4
+
5
+ const filePath = "./app.txt";
6
+
7
+ async function share() {
8
+ const client = new MagnetkClient({
9
+ relayUrl: '69.169.109.243', // Default to local for dev, change to public IPs for prod
10
+ relayPort: 4003,
11
+ seederPath: process.env.MAGNETK_SEEDER_PATH,
12
+ seedPort: 4005 // Default seed port
13
+ });
14
+
15
+ console.log(`[Share] Preparing to share: ${filePath}`);
16
+
17
+ // Check Public Identity
18
+ try {
19
+ console.log('[Share] Resolving Public Identity (STUN)...');
20
+ const identity = await client.getPublicIdentity();
21
+ console.log(`[Share] Public Identity: ${identity.ip}:${identity.port}`);
22
+ } catch (e) {
23
+ console.warn(`[Share] STUN lookup failed: ${e.message}`);
24
+ }
25
+
26
+ // Event listeners for feedback
27
+ client.on('validating-relay', () => console.log('[Share] Connecting to relay...'));
28
+ client.on('hashing', (p) => {
29
+ if (p.percent % 20 === 0 || p.percent === 100) {
30
+ console.log(`[Share] Hashing file... ${p.percent}%`);
31
+ }
32
+ });
33
+ client.on('relay-connected', (info) => console.log(`[Share] Connected to Relay at ${info.host}`));
34
+ client.on('seeding', (info) => {
35
+ console.log(`[Share] Seeding active: ${info.fileName} (${info.fileSize} bytes)`);
36
+ console.log(`[Share] File Hash: ${info.hash}`);
37
+ });
38
+ client.on('error', (err) => console.error(`[Share] Error: ${err.message}`));
39
+
40
+ try {
41
+ // Use new simplified API
42
+ const link = await client.share(filePath);
43
+
44
+ console.log('\n==================================================');
45
+ console.log(' FILE SHARED SUCCESSFULLY!');
46
+ console.log('==================================================');
47
+ console.log('\nmagnet link:');
48
+ console.log(link);
49
+ console.log('\nTo download, run:');
50
+ console.log(`node download.js "${link}"`);
51
+ console.log('\n[Share] Seeder running in background. Press Ctrl+C to stop.');
52
+
53
+ // Keep process alive
54
+ await client.keepSeeding();
55
+ } catch (err) {
56
+ console.error('[Share] Failed:', err);
57
+ client.stop();
58
+ process.exit(1);
59
+ }
60
+ }
61
+
62
+ share();