magnetk 2.2.4 → 2.2.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/cli.js +4 -2
- package/client.js +201 -11
- package/download.js +95 -0
- package/magnetk.js +21 -5
- package/package.json +1 -1
- package/share.js +62 -0
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,75 @@ 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
|
+
ADAPTIVE_CHUNKING=true
|
|
66
|
+
MAX_PARALLEL_CHUNKS=10
|
|
67
|
+
|
|
68
|
+
# ===== Connection Strategy =====
|
|
69
|
+
# RELAY_ASSISTED: auto | true | false
|
|
70
|
+
# auto = try direct P2P first, fall back to relay if unreachable
|
|
71
|
+
# true = always route data through relay (privacy mode)
|
|
72
|
+
# false = direct P2P only, fail if unreachable
|
|
73
|
+
RELAY_ASSISTED=auto
|
|
74
|
+
# HOLE_PUNCH: Enable NAT traversal via STUN + libp2p hole punching
|
|
75
|
+
HOLE_PUNCH=true
|
|
76
|
+
# RELAY_TIMEOUT_MS: How long to wait for direct connection before relay fallback
|
|
77
|
+
RELAY_TIMEOUT_MS=5000
|
|
78
|
+
`;
|
|
79
|
+
|
|
37
80
|
export class MagnetkClient extends EventEmitter {
|
|
38
81
|
constructor(config = {}) {
|
|
39
82
|
super();
|
|
40
83
|
this.config = {
|
|
84
|
+
// Relay settings (supports multiple relays for failover/scaling)
|
|
41
85
|
relayUrl: '69.169.109.243',
|
|
42
86
|
relayPort: 4003,
|
|
87
|
+
relays: [], // Array of {host, port} for multi-relay failover
|
|
43
88
|
seedPort: 4002,
|
|
44
|
-
enableSTUN: true,
|
|
45
89
|
seederPath: null,
|
|
46
90
|
configPath: null,
|
|
91
|
+
|
|
92
|
+
// DHT settings
|
|
93
|
+
dhtEnabled: false, // Enable DHT discovery
|
|
94
|
+
dhtBootstrapPeers: [], // Custom bootstrap peer multiaddrs
|
|
95
|
+
discoveryPriority: ['dht', 'relay', 'direct'], // Discovery order
|
|
96
|
+
|
|
97
|
+
// Connection strategy
|
|
98
|
+
stunServers: ['stun:stun.l.google.com:19302'], // STUN servers for NAT traversal
|
|
99
|
+
holePunch: true, // Enable NAT hole punching (STUN first, relay fallback)
|
|
100
|
+
relayAssisted: 'auto', // 'auto' | true | false — relay-assisted data transfer
|
|
101
|
+
enableRelayFallback: true, // Fall back to relay if direct P2P fails
|
|
102
|
+
relayTimeout: 5000, // ms to wait for direct connection before relay fallback
|
|
103
|
+
|
|
104
|
+
// Auto-config: create settings.conf if missing
|
|
105
|
+
autoConfig: true,
|
|
47
106
|
...config
|
|
48
107
|
};
|
|
49
108
|
this.processes = [];
|
|
@@ -178,6 +237,32 @@ export class MagnetkClient extends EventEmitter {
|
|
|
178
237
|
}
|
|
179
238
|
}
|
|
180
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Look up seeds with multi-relay failover.
|
|
242
|
+
* Tries each relay in order until one succeeds.
|
|
243
|
+
* @param {string} fileHash
|
|
244
|
+
* @returns {Promise<Object>}
|
|
245
|
+
*/
|
|
246
|
+
async lookupPeerWithFailover(fileHash) {
|
|
247
|
+
// Build relay list: primary + configured relays
|
|
248
|
+
const relays = [
|
|
249
|
+
{ host: this.config.relayUrl, port: this.config.relayPort },
|
|
250
|
+
...this.config.relays
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
let lastError = null;
|
|
254
|
+
for (const relay of relays) {
|
|
255
|
+
try {
|
|
256
|
+
const result = await this.lookupPeer(relay.host, relay.port, fileHash);
|
|
257
|
+
if (result && result.found) return result;
|
|
258
|
+
} catch (e) {
|
|
259
|
+
console.log(`[Magnetk-JS] Relay ${relay.host}:${relay.port} failed: ${e.message}`);
|
|
260
|
+
lastError = e;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
throw lastError || new Error('All relays failed');
|
|
264
|
+
}
|
|
265
|
+
|
|
181
266
|
/**
|
|
182
267
|
* Seeds a file and returns a Magnetk link.
|
|
183
268
|
* @param {string} filePath
|
|
@@ -190,7 +275,6 @@ export class MagnetkClient extends EventEmitter {
|
|
|
190
275
|
}
|
|
191
276
|
|
|
192
277
|
this.emit('validating-relay');
|
|
193
|
-
// Simple reachability check (optional but good for UX)
|
|
194
278
|
|
|
195
279
|
const fileStats = fs.statSync(absolutePath);
|
|
196
280
|
this.emit('hashing', { percent: 0 });
|
|
@@ -221,12 +305,43 @@ export class MagnetkClient extends EventEmitter {
|
|
|
221
305
|
}
|
|
222
306
|
|
|
223
307
|
const args = [
|
|
224
|
-
'-relay', relayMultiaddr,
|
|
225
308
|
'-file', absolutePath,
|
|
226
309
|
'-port', this.config.seedPort.toString(),
|
|
227
310
|
'-config', configPath
|
|
228
311
|
];
|
|
229
312
|
|
|
313
|
+
// Add relay if available
|
|
314
|
+
if (relayPeerId || this.config.relayUrl) {
|
|
315
|
+
args.push('-relay', relayMultiaddr);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Add DHT if enabled
|
|
319
|
+
if (this.config.dhtEnabled) {
|
|
320
|
+
args.push('-dht');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Add bootstrap peers if configured
|
|
324
|
+
if (this.config.dhtBootstrapPeers && this.config.dhtBootstrapPeers.length > 0) {
|
|
325
|
+
args.push('-bootstrap', this.config.dhtBootstrapPeers.join(','));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Connection strategy flags
|
|
329
|
+
if (this.config.holePunch) {
|
|
330
|
+
args.push('-holepunch');
|
|
331
|
+
}
|
|
332
|
+
if (this.config.relayAssisted !== undefined) {
|
|
333
|
+
const mode = this.config.relayAssisted === true ? 'true'
|
|
334
|
+
: this.config.relayAssisted === false ? 'false'
|
|
335
|
+
: this.config.relayAssisted;
|
|
336
|
+
args.push('-relay-assisted', mode);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Multi-relay: pass additional relay addresses
|
|
340
|
+
if (this.config.relays && this.config.relays.length > 0) {
|
|
341
|
+
const relayAddrs = this.config.relays.map(r => `${r.host}:${r.port}`).join(',');
|
|
342
|
+
args.push('-relay-addresses', relayAddrs);
|
|
343
|
+
}
|
|
344
|
+
|
|
230
345
|
console.log(`[Magnetk-JS] Spawning seeder: ${binPath} ${args.join(' ')}`);
|
|
231
346
|
|
|
232
347
|
return new Promise((resolve, reject) => {
|
|
@@ -251,10 +366,13 @@ export class MagnetkClient extends EventEmitter {
|
|
|
251
366
|
|
|
252
367
|
seeder.stderr.on('data', (data) => {
|
|
253
368
|
const msg = data.toString();
|
|
254
|
-
console.log(`[Seeder Stderr] ${msg}`);
|
|
369
|
+
console.log(`[Seeder Stderr] ${msg}`);
|
|
255
370
|
if (msg.includes('Connected to relay')) {
|
|
256
371
|
this.emit('relay-connected', { host: this.config.relayUrl, port: 4001 });
|
|
257
372
|
}
|
|
373
|
+
if (msg.includes('DHT') && msg.includes('announced')) {
|
|
374
|
+
this.emit('dht-announced');
|
|
375
|
+
}
|
|
258
376
|
});
|
|
259
377
|
|
|
260
378
|
seeder.on('error', (err) => {
|
|
@@ -320,7 +438,7 @@ export class MagnetkClient extends EventEmitter {
|
|
|
320
438
|
});
|
|
321
439
|
}
|
|
322
440
|
}
|
|
323
|
-
addressesToTry.push(
|
|
441
|
+
addressesToTry.push(`127.0.0.1:${this.config.seedPort}`);
|
|
324
442
|
|
|
325
443
|
const uniqueAddrs = [...new Set(addressesToTry)];
|
|
326
444
|
let socket;
|
|
@@ -392,11 +510,66 @@ export class MagnetkClient extends EventEmitter {
|
|
|
392
510
|
}
|
|
393
511
|
|
|
394
512
|
/**
|
|
395
|
-
* Finds available seeders for a hash.
|
|
513
|
+
* Finds available seeders for a hash (with multi-relay failover).
|
|
396
514
|
*/
|
|
397
515
|
async lookupSeeds(fileHash) {
|
|
398
|
-
|
|
399
|
-
|
|
516
|
+
try {
|
|
517
|
+
const result = await this.lookupPeerWithFailover(fileHash);
|
|
518
|
+
return result.found ? result.seeds : [];
|
|
519
|
+
} catch (e) {
|
|
520
|
+
return [];
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// ═══════════════════════════════════════════════════
|
|
525
|
+
// MINIMAL-CODE API (for SDK users writing less code)
|
|
526
|
+
// ═══════════════════════════════════════════════════
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Share a file with one function call.
|
|
530
|
+
* Handles hashing, seeding, and returns the magnet link.
|
|
531
|
+
*
|
|
532
|
+
* @example
|
|
533
|
+
* const link = await magnetk.share('./my-file.zip');
|
|
534
|
+
* console.log('Share this link:', link);
|
|
535
|
+
*
|
|
536
|
+
* @param {string} filePath - Path to the file to share
|
|
537
|
+
* @returns {Promise<string>} Magnet link
|
|
538
|
+
*/
|
|
539
|
+
async share(filePath) {
|
|
540
|
+
return this.seed(filePath);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Download a file with one function call.
|
|
545
|
+
* Handles discovery, downloading, and verification.
|
|
546
|
+
*
|
|
547
|
+
* @example
|
|
548
|
+
* await magnetk.get('magnetk:?xt=urn:sha256:abc...', './downloads/');
|
|
549
|
+
*
|
|
550
|
+
* @param {string} magnetURI - Magnet link to download
|
|
551
|
+
* @param {string} [outputPath] - Output directory or file path (default: current directory)
|
|
552
|
+
* @returns {Promise<string>} Path to the downloaded file
|
|
553
|
+
*/
|
|
554
|
+
async get(magnetURI, outputPath) {
|
|
555
|
+
const ml = typeof magnetURI === 'string' ? MagnetkLink.parse(magnetURI) : magnetURI;
|
|
556
|
+
const output = outputPath || `./${ml.fileName}`;
|
|
557
|
+
await this.download(magnetURI, output);
|
|
558
|
+
return output;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Hash a file and return its SHA-256 hash.
|
|
563
|
+
* Useful when users want to pre-compute hashes.
|
|
564
|
+
*
|
|
565
|
+
* @example
|
|
566
|
+
* const hash = await magnetk.hash('./my-file.zip');
|
|
567
|
+
*
|
|
568
|
+
* @param {string} filePath
|
|
569
|
+
* @returns {Promise<string>} Hex-encoded SHA-256 hash
|
|
570
|
+
*/
|
|
571
|
+
async hash(filePath) {
|
|
572
|
+
return this._calculateHash(path.resolve(filePath));
|
|
400
573
|
}
|
|
401
574
|
|
|
402
575
|
_calculateHash(filePath) {
|
|
@@ -433,7 +606,7 @@ export class MagnetkClient extends EventEmitter {
|
|
|
433
606
|
if (fs.existsSync(p)) return p;
|
|
434
607
|
}
|
|
435
608
|
|
|
436
|
-
return binName; // Fallback to PATH
|
|
609
|
+
return binName; // Fallback to PATH
|
|
437
610
|
}
|
|
438
611
|
|
|
439
612
|
_resolveConfigPath() {
|
|
@@ -441,14 +614,31 @@ export class MagnetkClient extends EventEmitter {
|
|
|
441
614
|
if (this.config.configPath) return this.config.configPath;
|
|
442
615
|
|
|
443
616
|
// 2. Check local dev environment
|
|
444
|
-
const
|
|
617
|
+
const searchPaths = [
|
|
445
618
|
path.join(process.cwd(), 'config', 'settings.conf'),
|
|
446
619
|
path.join(process.cwd(), '..', '..', '..', 'config', 'settings.conf'),
|
|
447
620
|
path.join(process.cwd(), '..', '..', 'config', 'settings.conf')
|
|
448
621
|
];
|
|
449
|
-
for (const p of
|
|
622
|
+
for (const p of searchPaths) {
|
|
450
623
|
if (fs.existsSync(p)) return p;
|
|
451
624
|
}
|
|
625
|
+
|
|
626
|
+
// 3. Auto-create settings.conf in current directory
|
|
627
|
+
if (this.config.autoConfig) {
|
|
628
|
+
const autoPath = path.join(process.cwd(), 'config', 'settings.conf');
|
|
629
|
+
try {
|
|
630
|
+
const configDir = path.dirname(autoPath);
|
|
631
|
+
if (!fs.existsSync(configDir)) {
|
|
632
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
633
|
+
}
|
|
634
|
+
fs.writeFileSync(autoPath, DEFAULT_CONFIG_CONTENT);
|
|
635
|
+
console.log(`[Magnetk-JS] Created default config at ${autoPath}`);
|
|
636
|
+
return autoPath;
|
|
637
|
+
} catch (e) {
|
|
638
|
+
console.warn(`[Magnetk-JS] Could not auto-create config: ${e.message}`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
452
642
|
return ''; // Let seeder use default
|
|
453
643
|
}
|
|
454
644
|
}
|
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: 4002
|
|
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));
|
|
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
|
-
|
|
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}`);
|
|
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
package/share.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { MagnetkClient } from './client.js';
|
|
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: 4002 // Data transfer port (must not conflict with relay 4003)
|
|
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();
|