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.
- package/README.md +69 -11
- package/client.js +109 -22
- 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
|
|
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
|
-
|
|
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
|
-
##
|
|
90
|
+
## CLI Interface
|
|
91
|
+
|
|
92
|
+
For quick operations, use the global `magnetk` command:
|
|
40
93
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
54
|
-
- `
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
//
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|