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 +4 -2
- package/client.js +176 -13
- 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,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
|
|
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}`);
|
|
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(
|
|
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
|
-
|
|
397
|
-
|
|
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
|
|
555
|
+
if (isSeeder && this.config.seederPath) return this.config.seederPath;
|
|
413
556
|
|
|
414
557
|
// 2. Check environment variable
|
|
415
|
-
const
|
|
416
|
-
if (
|
|
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
|
|
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
|
|
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));
|
|
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 '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();
|