magnetk 2.0.0 → 2.1.0

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 (3) hide show
  1. package/README.md +69 -11
  2. package/client.js +109 -22
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -7,7 +7,6 @@ The professional JavaScript SDK for interacting with the Magnetk P2P network. Th
7
7
  If using as a standalone folder, simply place the `magnetk` folder into your `node_modules` or clone it into your project.
8
8
 
9
9
  ```bash
10
- # If published to npm (future)
11
10
  npm install magnetk-sdk-js
12
11
  ```
13
12
 
@@ -25,23 +24,80 @@ console.log(link.fileName); // myfile.zip
25
24
  console.log(link.fileHash); // abc123...
26
25
  ```
27
26
 
27
+ ### Generating a Magnetk Link
28
+
29
+ ```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...
42
+ ```
43
+
28
44
  ### Downloading a File
29
45
 
30
46
  ```javascript
31
- import { MagnetkClient } from 'magnetk';
47
+ import { MagnetkClient, CONNECTION_TYPE } from 'magnetk';
32
48
 
33
49
  const client = new MagnetkClient();
34
- const magnetURI = 'magnetk:?...';
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
+
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).
71
+
72
+ ## Advanced Features
35
73
 
36
- await client.download(magnetURI, './downloads/my_file.zip');
74
+ ### NAT Traversal (STUN)
75
+ The SDK integrates STUN to discover your public identity. You can manually check your public IP/Port:
76
+
77
+ ```javascript
78
+ const identity = await client.getPublicIdentity();
79
+ console.log(`Public Identity: ${identity.ip}:${identity.port}`);
80
+ ```
81
+
82
+ ### Seeding (Distributing Files)
83
+ To share a file on the Magnetk network, use the high-performance **Go Seeder** included in the toolkit:
84
+
85
+ ```bash
86
+ # Seed a file via the relay
87
+ ./bin/seed.exe -relay "/ip4/relay-ip/tcp/4001" -file "path/to/file.zip"
37
88
  ```
38
89
 
39
- ## Features
90
+ ## CLI Interface
91
+
92
+ For quick operations, use the global `magnetk` command:
40
93
 
41
- - **Pure JavaScript**: No native dependencies required for core protocol handling.
42
- - **Fast TCP Discovery**: Connects directly to Magnetk Relay nodes to find seeds.
43
- - **Binary Framing**: Efficient data transfer using the Magnetk framed protocol.
44
- - **Magnet Link Support**: Full support for `magnetk:?` URI scheme.
94
+ ```bash
95
+ # Lookup seeds for a hash
96
+ magnetk lookup 98df9a59... --relay 1.2.3.4
97
+
98
+ # Download a file
99
+ magnetk download "magnetk:?..." --output ./file.zip
100
+ ```
45
101
 
46
102
  ## API Reference
47
103
 
@@ -50,8 +106,10 @@ await client.download(magnetURI, './downloads/my_file.zip');
50
106
  - `toString()`: Serialize back to string.
51
107
 
52
108
  ### `MagnetkClient`
53
- - `download(uri, outputPath)`: High-level download method.
54
- - `lookupPeer(relayHost, relayPort, fileHash)`: Find seeds for a hash.
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.
55
113
 
56
114
  ## License
57
115
  MIT
package/client.js CHANGED
@@ -7,6 +7,7 @@
7
7
 
8
8
  import net from 'net';
9
9
  import fs from 'fs';
10
+ import dgram from 'dgram';
10
11
  import { MagnetkLink } from './magnetk.js';
11
12
 
12
13
  const MSG_TYPE = {
@@ -19,10 +20,69 @@ const MSG_TYPE = {
19
20
  MSG_ERROR: 0xFF
20
21
  };
21
22
 
23
+ export const CONNECTION_TYPE = {
24
+ DIRECT: 'DIRECT',
25
+ RELAYED: 'RELAYED',
26
+ LOCAL: 'LOCAL',
27
+ UNKNOWN: 'UNKNOWN'
28
+ };
29
+
22
30
  export class MagnetkClient {
23
31
  constructor(config = {}) {
24
32
  this.config = config;
25
33
  this.connections = new Map();
34
+ this.lastDownloadStats = {
35
+ connectionType: CONNECTION_TYPE.UNKNOWN,
36
+ remoteAddr: null,
37
+ duration: 0,
38
+ bytesReceived: 0
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Retrieves the public IP and port using STUN.
44
+ * @returns {Promise<{ip: string, port: number}>}
45
+ */
46
+ getPublicIdentity(stunServer = 'stun.l.google.com', stunPort = 19302) {
47
+ return new Promise((resolve, reject) => {
48
+ const socket = dgram.createSocket('udp4');
49
+ const message = Buffer.alloc(20);
50
+ message.writeUInt16BE(0x0001, 0); // Binding Request
51
+ message.writeUInt16BE(0x0000, 2); // Length
52
+ message.writeUInt32BE(0x2112A442, 4); // Magic Cookie
53
+ for (let i = 8; i < 20; i++) message[i] = Math.floor(Math.random() * 256);
54
+
55
+ const timeout = setTimeout(() => {
56
+ socket.close();
57
+ reject(new Error('STUN discovery timed out'));
58
+ }, 5000);
59
+
60
+ socket.on('message', (msg) => {
61
+ let offset = 20;
62
+ while (offset < msg.length) {
63
+ const attrType = msg.readUInt16BE(offset);
64
+ const attrLen = msg.readUInt16BE(offset + 2);
65
+ if (attrType === 0x0020) { // XOR-MAPPED-ADDRESS
66
+ const port = msg.readUInt16BE(offset + 6) ^ 0x2112;
67
+ const ip = [];
68
+ for (let i = 0; i < 4; i++) ip.push(msg[offset + 8 + i] ^ message[4 + i]);
69
+ clearTimeout(timeout);
70
+ socket.close();
71
+ resolve({ ip: ip.join('.'), port });
72
+ return;
73
+ }
74
+ offset += 4 + attrLen;
75
+ }
76
+ });
77
+
78
+ socket.on('error', (err) => {
79
+ clearTimeout(timeout);
80
+ socket.close();
81
+ reject(err);
82
+ });
83
+
84
+ socket.send(message, 0, message.length, stunPort, stunServer);
85
+ });
26
86
  }
27
87
 
28
88
  /**
@@ -87,7 +147,7 @@ export class MagnetkClient {
87
147
  // 1. Resolve seed address from the Relay
88
148
  // We extract the relay host from the Magnetk Link
89
149
  const relayHost = ml.relayAddr.split('/')[2];
90
- const discoveryPort = 4003; // New TCP discovery port on relay
150
+ const discoveryPort = 4003;
91
151
 
92
152
  console.log(`[Magnetk-JS] Querying relay ${relayHost}:${discoveryPort} for seeds...`);
93
153
  let lookupResult;
@@ -95,43 +155,70 @@ export class MagnetkClient {
95
155
  lookupResult = await this.lookupPeer(relayHost, discoveryPort, ml.fileHash);
96
156
  } catch (e) {
97
157
  console.error(`[Magnetk-JS] Initial lookup failed: ${e.message}`);
98
- console.log(`[Magnetk-JS] Attempting fallback to direct connection...`);
99
158
  }
100
159
 
101
- let seedHost = relayHost; // Default to relay as fallback (unlikely to work for data)
102
- let seedPort = 4002;
103
-
160
+ const addressesToTry = [];
104
161
  if (lookupResult && lookupResult.found && lookupResult.seeds.length > 0) {
105
162
  const seed = lookupResult.seeds[0];
106
163
  console.log(`[Magnetk-JS] Found seed: ${seed.peer_id}`);
107
164
 
165
+ // 1. Add transport_addr if present
108
166
  if (seed.transport_addr) {
109
- const parts = seed.transport_addr.split(':');
110
- seedHost = parts[0];
111
- seedPort = parseInt(parts[1], 10) || 4002;
167
+ addressesToTry.push(seed.transport_addr);
168
+ }
169
+
170
+ // 2. Add from multiaddrs (look for /ip4/x.x.x.x/tcp/yyyy)
171
+ if (seed.addresses) {
172
+ seed.addresses.forEach(addr => {
173
+ const parts = addr.split('/');
174
+ if (parts.length >= 5 && parts[1] === 'ip4' && parts[3] === 'tcp') {
175
+ addressesToTry.push(`${parts[2]}:${parts[4]}`);
176
+ }
177
+ });
112
178
  }
113
179
  }
114
180
 
115
- // SPECIAL CASE for local testing:
116
- // If the seed host is exactly the same as the relay host, and the connection refused,
117
- // it's likely because the seeder is actually LOCAL to the user's PC.
118
- // We'll try to connect to localhost if the direct connection fails.
181
+ // Add localhost fallback as last resort for dev
182
+ addressesToTry.push('127.0.0.1:4002');
119
183
 
120
- console.log(`[Magnetk-JS] Connecting to seed at ${seedHost}:${seedPort} (TCP)...`);
184
+ // Unique addresses only
185
+ const uniqueAddrs = [...new Set(addressesToTry)];
186
+ console.log(`[Magnetk-JS] Candidate seeds: [${uniqueAddrs.join(', ')}]`);
121
187
 
122
188
  let socket;
123
- try {
124
- socket = await this.connect(seedHost, seedPort);
125
- } catch (e) {
126
- if (seedHost !== '127.0.0.1' && seedHost !== 'localhost') {
127
- console.log(`[Magnetk-JS] Connection to ${seedHost} failed. Trying LOCALHOST fallback for development...`);
128
- socket = await this.connect('127.0.0.1', 4002);
129
- seedHost = '127.0.0.1';
130
- } else {
131
- throw e;
189
+ let connectionType = CONNECTION_TYPE.UNKNOWN;
190
+ let finalAddr = null;
191
+
192
+ for (const addr of uniqueAddrs) {
193
+ const [host, portStr] = addr.split(':');
194
+ const port = parseInt(portStr, 10) || 4002;
195
+
196
+ try {
197
+ socket = await this.connect(host, port);
198
+ finalAddr = addr;
199
+
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
+ }
208
+ break;
209
+ } catch (e) {
210
+ console.log(`[Magnetk-JS] Failed to connect to ${addr}.`);
132
211
  }
133
212
  }
134
213
 
214
+ if (!socket) {
215
+ throw new Error('All seed connection attempts failed. Possible NAT blocking.');
216
+ }
217
+
218
+ this.lastDownloadStats.connectionType = connectionType;
219
+ this.lastDownloadStats.remoteAddr = finalAddr;
220
+ console.log(`[Magnetk-JS] » Final Connection: ${finalAddr} (${connectionType})`);
221
+
135
222
  const writer = new FrameWriter(socket);
136
223
  const reader = new FrameReader(socket);
137
224
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magnetk",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "JavaScript SDK for Magnetk P2P File Transfer System",
5
5
  "main": "index.js",
6
6
  "bin": {