magnetk 2.2.3 → 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 });
@@ -209,7 +275,9 @@ export class MagnetkClient extends EventEmitter {
209
275
  console.warn(`[Magnetk-JS] Failed to fetch relay identity: ${e.message}. Using default/configured address might fail if Peer ID is missing.`);
210
276
  }
211
277
 
212
- const binPath = this._resolveBinPath('seed.exe');
278
+ const isWin = process.platform === 'win32';
279
+ const binName = isWin ? 'seed.exe' : 'seed';
280
+ const binPath = this._resolveBinPath(binName);
213
281
  const configPath = this._resolveConfigPath();
214
282
 
215
283
  // Construct full multiaddr if we have the Peer ID
@@ -219,12 +287,26 @@ export class MagnetkClient extends EventEmitter {
219
287
  }
220
288
 
221
289
  const args = [
222
- '-relay', relayMultiaddr,
223
290
  '-file', absolutePath,
224
291
  '-port', this.config.seedPort.toString(),
225
292
  '-config', configPath
226
293
  ];
227
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
+
228
310
  console.log(`[Magnetk-JS] Spawning seeder: ${binPath} ${args.join(' ')}`);
229
311
 
230
312
  return new Promise((resolve, reject) => {
@@ -249,10 +331,13 @@ export class MagnetkClient extends EventEmitter {
249
331
 
250
332
  seeder.stderr.on('data', (data) => {
251
333
  const msg = data.toString();
252
- console.log(`[Seeder Stderr] ${msg}`); // Uncommented for debug
334
+ console.log(`[Seeder Stderr] ${msg}`);
253
335
  if (msg.includes('Connected to relay')) {
254
336
  this.emit('relay-connected', { host: this.config.relayUrl, port: 4001 });
255
337
  }
338
+ if (msg.includes('DHT') && msg.includes('announced')) {
339
+ this.emit('dht-announced');
340
+ }
256
341
  });
257
342
 
258
343
  seeder.on('error', (err) => {
@@ -318,7 +403,7 @@ export class MagnetkClient extends EventEmitter {
318
403
  });
319
404
  }
320
405
  }
321
- addressesToTry.push('127.0.0.1:4002');
406
+ addressesToTry.push(`127.0.0.1:${this.config.seedPort}`);
322
407
 
323
408
  const uniqueAddrs = [...new Set(addressesToTry)];
324
409
  let socket;
@@ -390,11 +475,66 @@ export class MagnetkClient extends EventEmitter {
390
475
  }
391
476
 
392
477
  /**
393
- * Finds available seeders for a hash.
478
+ * Finds available seeders for a hash (with multi-relay failover).
394
479
  */
395
480
  async lookupSeeds(fileHash) {
396
- const result = await this.lookupPeer(this.config.relayUrl, this.config.relayPort, fileHash);
397
- 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));
398
538
  }
399
539
 
400
540
  _calculateHash(filePath) {
@@ -408,12 +548,17 @@ export class MagnetkClient extends EventEmitter {
408
548
  }
409
549
 
410
550
  _resolveBinPath(binName) {
551
+ const isWin = process.platform === 'win32';
552
+ const isSeeder = binName.includes('seed');
553
+
411
554
  // 1. Check explicit config
412
- if (this.config.seederPath && binName === 'seed.exe') return this.config.seederPath;
555
+ if (isSeeder && this.config.seederPath) return this.config.seederPath;
413
556
 
414
557
  // 2. Check environment variable
415
- const envPath = binName === 'seed.exe' ? process.env.MAGNETK_SEEDER_PATH : null;
416
- if (envPath && fs.existsSync(envPath)) return envPath;
558
+ const envVar = isSeeder ? 'MAGNETK_SEEDER_PATH' : null;
559
+ if (envVar && process.env[envVar] && fs.existsSync(process.env[envVar])) {
560
+ return process.env[envVar];
561
+ }
417
562
 
418
563
  // 3. Check local dev environment paths
419
564
  const paths = [
@@ -425,6 +570,7 @@ export class MagnetkClient extends EventEmitter {
425
570
  for (const p of paths) {
426
571
  if (fs.existsSync(p)) return p;
427
572
  }
573
+
428
574
  return binName; // Fallback to PATH
429
575
  }
430
576
 
@@ -433,14 +579,31 @@ export class MagnetkClient extends EventEmitter {
433
579
  if (this.config.configPath) return this.config.configPath;
434
580
 
435
581
  // 2. Check local dev environment
436
- const paths = [
582
+ const searchPaths = [
437
583
  path.join(process.cwd(), 'config', 'settings.conf'),
438
584
  path.join(process.cwd(), '..', '..', '..', 'config', 'settings.conf'),
439
585
  path.join(process.cwd(), '..', '..', 'config', 'settings.conf')
440
586
  ];
441
- for (const p of paths) {
587
+ for (const p of searchPaths) {
442
588
  if (fs.existsSync(p)) return p;
443
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
+
444
607
  return ''; // Let seeder use default
445
608
  }
446
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.3",
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();