magnetk 2.1.1 → 2.2.3

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.
Files changed (4) hide show
  1. package/README.md +78 -62
  2. package/client.js +237 -65
  3. package/index.js +1 -0
  4. package/package.json +4 -2
package/README.md CHANGED
@@ -12,62 +12,58 @@ npm install magnetk
12
12
 
13
13
  ## Usage
14
14
 
15
- ### Parsing a Magnetk Link
15
+ ### One-Line Seeding (Zero Config)
16
+ The SDK automatically hashes your file, discovers the relay, and spawns the high-performance Go seeder in the background.
16
17
 
17
18
  ```javascript
18
- import { MagnetkLink } from 'magnetk';
19
+ import { MagnetkClient } from 'magnetk';
19
20
 
20
- const uri = 'magnetk:?xt=urn:sha256:abc123...&dn=myfile.zip&xl=1048576&relay=1.2.3.4';
21
- const link = MagnetkLink.parse(uri);
21
+ const client = new MagnetkClient({
22
+ relayUrl: '69.169.109.243', // Optional: defaults to public relay
23
+ relayPort: 4003
24
+ });
22
25
 
23
- console.log(link.fileName); // myfile.zip
24
- console.log(link.fileHash); // abc123...
26
+ // Seed a file and get a shareable link
27
+ const link = await client.seed('./my-app.zip');
28
+ console.log(`Share this link: ${link}`);
29
+
30
+ // Keep the process alive to serve peers (Ctrl+C to stop)
31
+ await client.keepSeeding();
25
32
  ```
26
33
 
27
- ### Generating a Magnetk Link
34
+ ### One-Line Downloading
35
+ Download files with automatic connection optimization (Direct > Local > Relay).
28
36
 
29
37
  ```javascript
30
- import { MagnetkLink } from 'magnetk';
31
-
32
- const link = new MagnetkLink(
33
- '98df9a59...', // File Hash
34
- 'app.txt', // File Name
35
- 4, // File Size (bytes)
36
- '12D3KooWDmX2...', // Seed Peer ID
37
- '/ip4/1.2.3.4/tcp/4001' // Relay Multiaddr
38
- );
39
-
40
- console.log(link.toString());
41
- // Output: magnetk:?dn=app.txt&relay=%2Fip4%2F1.2.3.4...
38
+ const magnetURI = 'magnetk:?xt=urn:sha256:abc...&relay=...';
39
+
40
+ console.log('Downloading...');
41
+ await client.download(magnetURI, './my-app-downloaded.zip');
42
+ console.log('Done!');
42
43
  ```
43
44
 
44
- ### Downloading a File
45
+ ### Event-Driven Progress tracking
46
+ Track every stage of the transfer with the new Event API.
45
47
 
46
48
  ```javascript
47
- import { MagnetkClient, CONNECTION_TYPE } from 'magnetk';
48
-
49
49
  const client = new MagnetkClient();
50
- const uri = 'magnetk:?xt=urn:sha256:98df9a...&dn=app.txt&relay=/ip4/1.2.3.4/tcp/4001';
51
-
52
- try {
53
- console.log('Starting P2P download...');
54
- await client.download(uri, './downloads/app.txt');
55
-
56
- // Access connection diagnostics
57
- const stats = client.lastDownloadStats;
58
- console.log(`Success! Connection Type: ${stats.connectionType}`);
59
- console.log(`Remote Address: ${stats.remoteAddr}`);
60
- } catch (err) {
61
- console.error('Download failed:', err.message);
62
- }
63
- ```
64
-
65
- ### Checking Connection Types
66
50
 
67
- The SDK automatically tracks the quality of your P2P connection:
68
- - `CONNECTION_TYPE.DIRECT`: High-speed P2P (via STUN discovered public IP).
69
- - `CONNECTION_TYPE.LOCAL`: Super-fast local transfer (same machine/LAN).
70
- - `CONNECTION_TYPE.RELAYED`: Connection bridged via relay (fallback).
51
+ // Seeding Events
52
+ client.on('hashing', (p) => console.log(`Hashing file: ${p.percent}%`));
53
+ client.on('seeding', (info) => console.log(`Seeding ${info.fileName} (${info.fileSize} bytes)`));
54
+ client.on('relay-connected', (r) => console.log(`Live on Relay: ${r.host}`));
55
+
56
+ // Download Events
57
+ client.on('download-start', (info) => console.log(`Starting: ${info.fileName}`));
58
+ client.on('progress', (p) => {
59
+ process.stdout.write(`\rDownload: ${p.percent.toFixed(1)}% | Speed: ${p.speed} KB/s`);
60
+ });
61
+ client.on('connection-type', (c) => console.log(`\nConnection Mode: ${c.type}`)); // DIRECT, RELAYED, LOCAL
62
+ client.on('complete', (info) => console.log(`\nSaved to: ${info.filePath}`));
63
+
64
+ // Error Handling
65
+ client.on('error', (err) => console.error(`Error (${err.code}): ${err.message}`));
66
+ ```
71
67
 
72
68
  ## Advanced Features
73
69
 
@@ -79,37 +75,57 @@ const identity = await client.getPublicIdentity();
79
75
  console.log(`Public Identity: ${identity.ip}:${identity.port}`);
80
76
  ```
81
77
 
82
- ### Seeding (Distributing Files)
83
- To share a file on the Magnetk network, use the high-performance **Go Seeder** included in the toolkit:
78
+ ### Manual CLI
79
+ For quick operations, use the global `magnetk` command:
84
80
 
85
81
  ```bash
86
- # Seed a file via the relay
87
- ./bin/seed.exe -relay "/ip4/relay-ip/tcp/4001" -file "path/to/file.zip"
82
+ # Download a file
83
+ magnetk download "magnetk:?..." --output ./file.zip
88
84
  ```
89
85
 
90
- ## CLI Interface
86
+ ## API Reference
91
87
 
92
- For quick operations, use the global `magnetk` command:
88
+ ### `MagnetkClient`
89
+ - `constructor(config)`: `{ relayUrl, relayPort, seedPort, enableSTUN }`
90
+ - `seed(filePath)`: Returns `Promise<magnetLink>`. Spawns background seeder.
91
+ - `download(uri, outputPath)`: Downloads file. Emits progress events.
92
+ - `stop()`: Kills all background seeder processes.
93
+ - `getRelayIdentity()`: Fetches Relay Peer ID and Multiaddrs.
93
94
 
94
- ```bash
95
- # Lookup seeds for a hash
96
- magnetk lookup 98df9a59... --relay 1.2.3.4
95
+ ### Events
96
+ - `hashing`: `{ percent }`
97
+ - `seeding`: `{ fileName, fileSize, hash }`
98
+ - `progress`: `{ percent, bytesDownloaded, totalBytes, speed }`
99
+ - `connection-type`: `{ type }` (DIRECT, RELAYED, LOCAL)
100
+ - `error`: `{ code, message }`
97
101
 
98
- # Download a file
99
- magnetk download "magnetk:?..." --output ./file.zip
102
+ ## Prerequisites
103
+
104
+ The JavaScript SDK is a lightweight wrapper around the high-performance **Magnetk Go Binaries**. You must have these binaries on your system to use seeding or relay features.
105
+
106
+ 1. **Download** the latest binaries for your platform (Windows/Linux/macOS).
107
+ 2. **Ensure** they are in your system `PATH` OR provide the path manually in the SDK config.
108
+
109
+ ### Manual Configuration
110
+ ```javascript
111
+ const client = new MagnetkClient({
112
+ seederPath: 'C:\\path\\to\\seed.exe', // Explicit path
113
+ relayUrl: '69.169.109.243'
114
+ });
100
115
  ```
101
116
 
102
- ## API Reference
117
+ ### Environment Variables
118
+ You can also set the binary path globally:
119
+ - `MAGNETK_SEEDER_PATH`: Path to the `seed.exe` binary.
103
120
 
104
- ### `MagnetkLink`
105
- - `static parse(uri)`: Create a link object from string.
106
- - `toString()`: Serialize back to string.
121
+ ## Troubleshooting
107
122
 
108
- ### `MagnetkClient`
109
- - `download(uri, outputPath)`: High-level P2P download.
110
- - `getPublicIdentity()`: Resolve public IP via STUN.
111
- - `lastDownloadStats`: Metadata from the previous download.
112
- - `lookupPeer(relayHost, relayPort, fileHash)`: Query relay for seeds.
123
+ ### "spawn seed.exe ENOENT" Error
124
+ This means the SDK cannot find the Go seeder binary.
125
+ 1. Check if `seed.exe` is in your system `PATH`.
126
+ 2. Or provide `seederPath` in the `MagnetkClient` constructor.
127
+ 3. Or set the `MAGNETK_SEEDER_PATH` environment variable.
113
128
 
114
129
  ## License
115
130
  MIT
131
+
package/client.js CHANGED
@@ -8,6 +8,11 @@
8
8
  import net from 'net';
9
9
  import fs from 'fs';
10
10
  import dgram from 'dgram';
11
+ import crypto from 'crypto';
12
+ import path from 'path';
13
+ import { spawn } from 'child_process';
14
+ import { EventEmitter } from 'events';
15
+ import { fileURLToPath } from 'url';
11
16
  import { MagnetkLink } from './magnetk.js';
12
17
 
13
18
  const MSG_TYPE = {
@@ -17,6 +22,8 @@ const MSG_TYPE = {
17
22
  MSG_MANIFEST_RES: 0x11,
18
23
  MSG_CHUNK_REQ: 0x20,
19
24
  MSG_CHUNK_RES: 0x21,
25
+ MSG_GET_IDENTITY: 0x32,
26
+ MSG_IDENTITY_RES: 0x33,
20
27
  MSG_ERROR: 0xFF
21
28
  };
22
29
 
@@ -27,10 +34,19 @@ export const CONNECTION_TYPE = {
27
34
  UNKNOWN: 'UNKNOWN'
28
35
  };
29
36
 
30
- export class MagnetkClient {
37
+ export class MagnetkClient extends EventEmitter {
31
38
  constructor(config = {}) {
32
- this.config = config;
33
- this.connections = new Map();
39
+ super();
40
+ this.config = {
41
+ relayUrl: '69.169.109.243',
42
+ relayPort: 4003,
43
+ seedPort: 4002,
44
+ enableSTUN: true,
45
+ seederPath: null,
46
+ configPath: null,
47
+ ...config
48
+ };
49
+ this.processes = [];
34
50
  this.lastDownloadStats = {
35
51
  connectionType: CONNECTION_TYPE.UNKNOWN,
36
52
  remoteAddr: null,
@@ -104,6 +120,33 @@ export class MagnetkClient {
104
120
  });
105
121
  }
106
122
 
123
+ /**
124
+ * Retrieves the relay's Peer ID and multiaddrs via TCP.
125
+ */
126
+ async getRelayIdentity() {
127
+ console.log(`[Magnetk-JS] Fetching relay identity from ${this.config.relayUrl}:${this.config.relayPort}...`);
128
+ const socket = await this.connect(this.config.relayUrl, this.config.relayPort);
129
+ const writer = new FrameWriter(socket);
130
+ const reader = new FrameReader(socket);
131
+
132
+ try {
133
+ await writer.writeFrame(MSG_TYPE.MSG_GET_IDENTITY, Buffer.alloc(0)); // Empty payload
134
+ const frame = await reader.readFrame();
135
+
136
+ if (frame.type === MSG_TYPE.MSG_IDENTITY_RES) {
137
+ const result = JSON.parse(frame.payload.toString());
138
+ return result;
139
+ } else if (frame.type === MSG_TYPE.MSG_ERROR) {
140
+ const err = JSON.parse(frame.payload.toString());
141
+ throw new Error(`Relay error: ${err.message}`);
142
+ } else {
143
+ throw new Error(`Unexpected response type: 0x${frame.type.toString(16)}`);
144
+ }
145
+ } finally {
146
+ socket.end();
147
+ }
148
+ }
149
+
107
150
  /**
108
151
  * Look up seeds for a file hash from the relay.
109
152
  * @param {string} relayHost
@@ -135,39 +178,137 @@ export class MagnetkClient {
135
178
  }
136
179
  }
137
180
 
181
+ /**
182
+ * Seeds a file and returns a Magnetk link.
183
+ * @param {string} filePath
184
+ * @returns {Promise<string>}
185
+ */
186
+ async seed(filePath) {
187
+ const absolutePath = path.resolve(filePath);
188
+ if (!fs.existsSync(absolutePath)) {
189
+ throw new Error(`File not found: ${absolutePath}`);
190
+ }
191
+
192
+ this.emit('validating-relay');
193
+ // Simple reachability check (optional but good for UX)
194
+
195
+ const fileStats = fs.statSync(absolutePath);
196
+ this.emit('hashing', { percent: 0 });
197
+
198
+ // Calculate hash
199
+ const hash = await this._calculateHash(absolutePath);
200
+ this.emit('hashing', { percent: 100 });
201
+
202
+ // Fetch Relay Identity to get Peer ID
203
+ let relayPeerId;
204
+ try {
205
+ const identity = await this.getRelayIdentity();
206
+ relayPeerId = identity.peer_id;
207
+ console.log(`[Magnetk-JS] Relay Peer ID: ${relayPeerId}`);
208
+ } catch (e) {
209
+ console.warn(`[Magnetk-JS] Failed to fetch relay identity: ${e.message}. Using default/configured address might fail if Peer ID is missing.`);
210
+ }
211
+
212
+ const binPath = this._resolveBinPath('seed.exe');
213
+ const configPath = this._resolveConfigPath();
214
+
215
+ // Construct full multiaddr if we have the Peer ID
216
+ let relayMultiaddr = `/ip4/${this.config.relayUrl}/tcp/4001`;
217
+ if (relayPeerId) {
218
+ relayMultiaddr += `/p2p/${relayPeerId}`;
219
+ }
220
+
221
+ const args = [
222
+ '-relay', relayMultiaddr,
223
+ '-file', absolutePath,
224
+ '-port', this.config.seedPort.toString(),
225
+ '-config', configPath
226
+ ];
227
+
228
+ console.log(`[Magnetk-JS] Spawning seeder: ${binPath} ${args.join(' ')}`);
229
+
230
+ return new Promise((resolve, reject) => {
231
+ const seeder = spawn(binPath, args);
232
+ this.processes.push(seeder);
233
+
234
+ let linkFound = false;
235
+
236
+ seeder.stdout.on('data', (data) => {
237
+ const output = data.toString();
238
+ if (output.includes('Magnetk Link:')) {
239
+ const link = output.split('Magnetk Link:')[1].trim().split('\n')[0].trim();
240
+ linkFound = true;
241
+ this.emit('seeding', {
242
+ fileName: path.basename(absolutePath),
243
+ fileSize: fileStats.size,
244
+ hash: hash
245
+ });
246
+ resolve(link);
247
+ }
248
+ });
249
+
250
+ seeder.stderr.on('data', (data) => {
251
+ const msg = data.toString();
252
+ console.log(`[Seeder Stderr] ${msg}`); // Uncommented for debug
253
+ if (msg.includes('Connected to relay')) {
254
+ this.emit('relay-connected', { host: this.config.relayUrl, port: 4001 });
255
+ }
256
+ });
257
+
258
+ seeder.on('error', (err) => {
259
+ if (!linkFound) reject(err);
260
+ this.emit('error', { code: 'SEEDER_ERROR', message: err.message });
261
+ });
262
+
263
+ seeder.on('close', (code) => {
264
+ if (!linkFound && code !== 0) {
265
+ reject(new Error(`Seeder exited with code ${code}`));
266
+ }
267
+ });
268
+ });
269
+ }
270
+
271
+ /**
272
+ * Keeps seeding active processes.
273
+ */
274
+ async keepSeeding() {
275
+ console.log('Press Ctrl+C to stop seeding');
276
+ return new Promise(() => { }); // Wait forever
277
+ }
278
+
279
+ /**
280
+ * Stops all active seeding processes.
281
+ */
282
+ stop() {
283
+ this.processes.forEach(p => p.kill());
284
+ this.processes = [];
285
+ }
286
+
138
287
  /**
139
288
  * Downloads a file from a magnetk link.
140
289
  * @param {string} magnetURI
141
290
  * @param {string} outputPath
142
291
  */
143
292
  async download(magnetURI, outputPath) {
144
- const ml = MagnetkLink.parse(magnetURI);
145
- console.log(`[Magnetk-JS] Starting download for: ${ml.fileName}`);
293
+ const ml = typeof magnetURI === 'string' ? MagnetkLink.parse(magnetURI) : magnetURI;
294
+ this.emit('download-start', { fileName: ml.fileName });
146
295
 
147
- // 1. Resolve seed address from the Relay
148
- // We extract the relay host from the Magnetk Link
149
- const relayHost = ml.relayAddr.split('/')[2];
150
- const discoveryPort = 4003;
296
+ // Resolve relay info from link or config
297
+ const relayHost = ml.relayAddr ? ml.relayAddr.split('/')[2] : this.config.relayUrl;
298
+ const discoveryPort = this.config.relayPort;
151
299
 
152
- console.log(`[Magnetk-JS] Querying relay ${relayHost}:${discoveryPort} for seeds...`);
153
300
  let lookupResult;
154
301
  try {
155
302
  lookupResult = await this.lookupPeer(relayHost, discoveryPort, ml.fileHash);
156
303
  } catch (e) {
157
- console.error(`[Magnetk-JS] Initial lookup failed: ${e.message}`);
304
+ this.emit('error', { code: 'LOOKUP_FAILED', message: e.message });
305
+ throw e;
158
306
  }
159
307
 
160
308
  const addressesToTry = [];
161
309
  if (lookupResult && lookupResult.found && lookupResult.seeds.length > 0) {
162
310
  const seed = lookupResult.seeds[0];
163
- console.log(`[Magnetk-JS] Found seed: ${seed.peer_id}`);
164
-
165
- // 1. Add transport_addr if present
166
- if (seed.transport_addr) {
167
- addressesToTry.push(seed.transport_addr);
168
- }
169
-
170
- // 2. Add from multiaddrs (look for /ip4/x.x.x.x/tcp/yyyy)
311
+ if (seed.transport_addr) addressesToTry.push(seed.transport_addr);
171
312
  if (seed.addresses) {
172
313
  seed.addresses.forEach(addr => {
173
314
  const parts = addr.split('/');
@@ -177,14 +318,9 @@ export class MagnetkClient {
177
318
  });
178
319
  }
179
320
  }
180
-
181
- // Add localhost fallback as last resort for dev
182
321
  addressesToTry.push('127.0.0.1:4002');
183
322
 
184
- // Unique addresses only
185
323
  const uniqueAddrs = [...new Set(addressesToTry)];
186
- console.log(`[Magnetk-JS] Candidate seeds: [${uniqueAddrs.join(', ')}]`);
187
-
188
324
  let socket;
189
325
  let connectionType = CONNECTION_TYPE.UNKNOWN;
190
326
  let finalAddr = null;
@@ -196,81 +332,117 @@ export class MagnetkClient {
196
332
  try {
197
333
  socket = await this.connect(host, port);
198
334
  finalAddr = addr;
335
+ if (host === '127.0.0.1' || host === 'localhost') connectionType = CONNECTION_TYPE.LOCAL;
336
+ else if (host === relayHost) connectionType = CONNECTION_TYPE.RELAYED;
337
+ else connectionType = CONNECTION_TYPE.DIRECT;
199
338
 
200
- // Determine connection type
201
- if (host === '127.0.0.1' || host === 'localhost') {
202
- connectionType = CONNECTION_TYPE.LOCAL;
203
- } else if (host === relayHost) {
204
- connectionType = CONNECTION_TYPE.RELAYED;
205
- } else {
206
- connectionType = CONNECTION_TYPE.DIRECT;
207
- }
339
+ this.emit('connection-type', { type: connectionType });
208
340
  break;
209
- } catch (e) {
210
- console.log(`[Magnetk-JS] Failed to connect to ${addr}.`);
211
- }
341
+ } catch (e) { }
212
342
  }
213
343
 
214
- if (!socket) {
215
- throw new Error('All seed connection attempts failed. Possible NAT blocking.');
216
- }
344
+ if (!socket) throw new Error('All seed connection attempts failed.');
217
345
 
218
346
  this.lastDownloadStats.connectionType = connectionType;
219
347
  this.lastDownloadStats.remoteAddr = finalAddr;
220
- console.log(`[Magnetk-JS] » Final Connection: ${finalAddr} (${connectionType})`);
221
348
 
222
349
  const writer = new FrameWriter(socket);
223
350
  const reader = new FrameReader(socket);
224
351
 
225
352
  try {
226
- // STEP 1: Request manifest
227
- console.log(`[Magnetk-JS] Requesting manifest for ${ml.fileHash.substring(0, 10)}...`);
228
353
  await writer.writeJSON(MSG_TYPE.MSG_MANIFEST_REQ, { file_hash: ml.fileHash });
229
-
230
354
  const manifestFrame = await reader.readFrame();
231
- if (manifestFrame.type !== MSG_TYPE.MSG_MANIFEST_RES) {
232
- throw new Error(`Unexpected manifest response type: 0x${manifestFrame.type.toString(16)}`);
233
- }
234
-
235
355
  const manifest = JSON.parse(manifestFrame.payload.toString());
236
- console.log(`[Magnetk-JS] Manifest received: ${manifest.file_name}, ${manifest.total_chunks} chunks, total size: ${manifest.file_size} bytes`);
237
356
 
238
- // STEP 2: Prepare output file
239
357
  const fd = fs.openSync(outputPath, 'w');
358
+ let bytesDownloaded = 0;
359
+ const startTime = Date.now();
240
360
 
241
- // STEP 3: Request and write chunks
242
361
  for (let i = 0; i < manifest.total_chunks; i++) {
243
- process.stdout.write(`\r[Magnetk-JS] Downloading chunk ${i + 1}/${manifest.total_chunks}...`);
244
-
245
362
  await writer.writeJSON(MSG_TYPE.MSG_CHUNK_REQ, {
246
363
  file_hash: ml.fileHash,
247
364
  chunk_indices: [i]
248
365
  });
249
366
 
250
367
  const chunkFrame = await reader.readFrame();
251
- if (chunkFrame.type !== MSG_TYPE.MSG_CHUNK_RES) {
252
- throw new Error(`Unexpected chunk response type: 0x${chunkFrame.type.toString(16)}`);
253
- }
254
-
255
- // Chunk payload format: [4 bytes: index][raw data]
256
- const index = chunkFrame.payload.readUInt32BE(0);
257
368
  const data = chunkFrame.payload.slice(4);
258
369
 
259
- // Write to file at correct offset
260
- const offset = i * manifest.chunk_size;
261
- fs.writeSync(fd, data, 0, data.length, offset);
370
+ fs.writeSync(fd, data, 0, data.length, i * manifest.chunk_size);
371
+
372
+ bytesDownloaded += data.length;
373
+ const percent = (bytesDownloaded / manifest.file_size) * 100;
374
+ const elapsedS = (Date.now() - startTime) / 1000;
375
+ const speed = elapsedS > 0 ? (bytesDownloaded / 1024 / elapsedS).toFixed(1) : 0;
376
+
377
+ this.emit('progress', {
378
+ percent,
379
+ bytesDownloaded,
380
+ totalBytes: manifest.file_size,
381
+ speed
382
+ });
262
383
  }
263
384
 
264
385
  fs.closeSync(fd);
265
- console.log(`\n[Magnetk-JS] Download complete: ${outputPath}`);
266
-
267
- } catch (err) {
268
- console.error(`[Magnetk-JS] Download failed: ${err.message}`);
269
- throw err;
386
+ this.emit('complete', { filePath: outputPath });
270
387
  } finally {
271
388
  socket.end();
272
389
  }
273
390
  }
391
+
392
+ /**
393
+ * Finds available seeders for a hash.
394
+ */
395
+ async lookupSeeds(fileHash) {
396
+ const result = await this.lookupPeer(this.config.relayUrl, this.config.relayPort, fileHash);
397
+ return result.found ? result.seeds : [];
398
+ }
399
+
400
+ _calculateHash(filePath) {
401
+ return new Promise((resolve, reject) => {
402
+ const hash = crypto.createHash('sha256');
403
+ const stream = fs.createReadStream(filePath);
404
+ stream.on('data', data => hash.update(data));
405
+ stream.on('end', () => resolve(hash.digest('hex')));
406
+ stream.on('error', reject);
407
+ });
408
+ }
409
+
410
+ _resolveBinPath(binName) {
411
+ // 1. Check explicit config
412
+ if (this.config.seederPath && binName === 'seed.exe') return this.config.seederPath;
413
+
414
+ // 2. Check environment variable
415
+ const envPath = binName === 'seed.exe' ? process.env.MAGNETK_SEEDER_PATH : null;
416
+ if (envPath && fs.existsSync(envPath)) return envPath;
417
+
418
+ // 3. Check local dev environment paths
419
+ const paths = [
420
+ path.join(process.cwd(), 'bin', binName),
421
+ path.join(process.cwd(), '..', '..', '..', 'bin', binName),
422
+ path.join(process.cwd(), '..', '..', 'bin', binName),
423
+ path.join(process.cwd(), '..', 'bin', binName)
424
+ ];
425
+ for (const p of paths) {
426
+ if (fs.existsSync(p)) return p;
427
+ }
428
+ return binName; // Fallback to PATH
429
+ }
430
+
431
+ _resolveConfigPath() {
432
+ // 1. Check explicit config
433
+ if (this.config.configPath) return this.config.configPath;
434
+
435
+ // 2. Check local dev environment
436
+ const paths = [
437
+ path.join(process.cwd(), 'config', 'settings.conf'),
438
+ path.join(process.cwd(), '..', '..', '..', 'config', 'settings.conf'),
439
+ path.join(process.cwd(), '..', '..', 'config', 'settings.conf')
440
+ ];
441
+ for (const p of paths) {
442
+ if (fs.existsSync(p)) return p;
443
+ }
444
+ return ''; // Let seeder use default
445
+ }
274
446
  }
275
447
 
276
448
  class FrameWriter {
package/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './client.js';
2
2
  export * from './magnetk.js';
3
+ export { CONNECTION_TYPE } from './client.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magnetk",
3
- "version": "2.1.1",
3
+ "version": "2.2.3",
4
4
  "description": "JavaScript SDK for Magnetk P2P File Transfer System",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -14,7 +14,9 @@
14
14
  "utils.js",
15
15
  "README.md",
16
16
  "LICENSE",
17
- "package.json"
17
+ "package.json",
18
+ "share.js",
19
+ "download.js"
18
20
  ],
19
21
  "scripts": {
20
22
  "test": "node test.js"