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 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}`); // Uncommented for debug
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('127.0.0.1:4002');
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
- const result = await this.lookupPeer(this.config.relayUrl, this.config.relayPort, fileHash);
399
- return result.found ? result.seeds : [];
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 (e.g. "seed" or "seed.exe")
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 paths = [
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 paths) {
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)); // 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.6",
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 './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();