@decentnetwork/peer 0.1.104 → 0.1.105
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/dist/compat/filetransfer.js +14 -5
- package/dist/peer.js +23 -1
- package/package.json +1 -1
|
@@ -239,17 +239,23 @@ export class FileTransferManager {
|
|
|
239
239
|
}
|
|
240
240
|
return inner;
|
|
241
241
|
}
|
|
242
|
-
#freeNumber(friendId) {
|
|
242
|
+
#freeNumber(friendId, preferred = 0) {
|
|
243
243
|
const inner = this.#map(this.#sending, friendId);
|
|
244
|
-
|
|
244
|
+
// Start at a content-derived slot. Older receivers retain an incomplete
|
|
245
|
+
// receive slot in memory until it finishes or is killed. After the sender
|
|
246
|
+
// restarts, always reusing slot 0 makes a DIFFERENT new file look like a
|
|
247
|
+
// retransmitted offer for that stale slot, so the receiver silently ACKs
|
|
248
|
+
// the old file and the new UI stays at 0%. The same content keeps the same
|
|
249
|
+
// preferred slot (resume), while different content naturally avoids stale
|
|
250
|
+
// collisions; circular search still supports concurrent transfers.
|
|
251
|
+
for (let n = 0; n < MAX_CONCURRENT_FILE_PIPES; n++) {
|
|
252
|
+
const i = (preferred + n) % MAX_CONCURRENT_FILE_PIPES;
|
|
245
253
|
if (!inner.has(i))
|
|
246
254
|
return i;
|
|
255
|
+
}
|
|
247
256
|
return -1;
|
|
248
257
|
}
|
|
249
258
|
sendFile(friendId, data, opts) {
|
|
250
|
-
const fileNumber = this.#freeNumber(friendId);
|
|
251
|
-
if (fileNumber < 0)
|
|
252
|
-
return null;
|
|
253
259
|
// Content-addressed fileId (sha256): re-sending the SAME file after a
|
|
254
260
|
// restart or a dropped peer yields the SAME id, so the receiver can match
|
|
255
261
|
// it against its persisted partial and resume from where it left off — the
|
|
@@ -257,6 +263,9 @@ export class FileTransferManager {
|
|
|
257
263
|
const fileId = data.length > 0
|
|
258
264
|
? new Uint8Array(createHash("sha256").update(data).digest()).slice(0, FILE_ID_LENGTH)
|
|
259
265
|
: randomBytes(FILE_ID_LENGTH);
|
|
266
|
+
const fileNumber = this.#freeNumber(friendId, fileId[0] ?? 0);
|
|
267
|
+
if (fileNumber < 0)
|
|
268
|
+
return null;
|
|
260
269
|
const req = encodeFileSendRequest(fileNumber, opts.kind ?? FILEKIND.DATA, BigInt(data.length), fileId, opts.name);
|
|
261
270
|
const lanPath = this.isLanPath(friendId);
|
|
262
271
|
const st = {
|
package/dist/peer.js
CHANGED
|
@@ -2163,6 +2163,21 @@ export class Peer {
|
|
|
2163
2163
|
// syscall (that was the CCTV CPU regression) — safe at CCTV packet
|
|
2164
2164
|
// rates.
|
|
2165
2165
|
this.#adoptRemote(state, remote.address, remote.port);
|
|
2166
|
+
// A successfully decrypted packet arriving from a private address in
|
|
2167
|
+
// one of our physical subnets is stronger LAN proof than an endpoint
|
|
2168
|
+
// hint: it demonstrates that this exact host:port already reaches us.
|
|
2169
|
+
// Lock it immediately. Without this, duplicate public/relay copies
|
|
2170
|
+
// arriving a moment later overwrite `remote`, so file DATA travels on
|
|
2171
|
+
// the LAN but ACKs hairpin through the public Internet (observed as
|
|
2172
|
+
// ~38KB/s between mac-dev and WSL on the same 10.0.0.0/24).
|
|
2173
|
+
if (!state.lanRemoteHost &&
|
|
2174
|
+
isPrivateAddress(remote.address) &&
|
|
2175
|
+
!isCgnatAddress(remote.address) &&
|
|
2176
|
+
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(remote.address, s))) {
|
|
2177
|
+
state.lanRemoteHost = remote.address;
|
|
2178
|
+
this.#rememberEndpointCandidate(state, remote.address, remote.port);
|
|
2179
|
+
this.#debugLog(`lan_lock_confirmed friend=${friendId} host=${remote.address}:${remote.port} (decrypted UDP source)`);
|
|
2180
|
+
}
|
|
2166
2181
|
if (!state.lastUdpRecvMs) {
|
|
2167
2182
|
this.#debugLog(`udp_confirmed friend=${friendId} via=${remote.address}:${remote.port} (direct UDP path live)`);
|
|
2168
2183
|
}
|
|
@@ -4559,10 +4574,17 @@ export class Peer {
|
|
|
4559
4574
|
if (payload.length >= 18) {
|
|
4560
4575
|
const lanHost = `${payload[12]}.${payload[13]}.${payload[14]}.${payload[15]}`;
|
|
4561
4576
|
const lanPort = ((payload[16] << 8) | payload[17]) >>> 0;
|
|
4577
|
+
// WSL sees only its 172.x virtual NIC, not the Windows host's physical
|
|
4578
|
+
// 10.x subnet. In that topology a peer on the same real LAN is still
|
|
4579
|
+
// safely identifiable because its server-reflexive address equals ours.
|
|
4580
|
+
// Permit the advertised private candidate when both peers share the same
|
|
4581
|
+
// public NAT; if it is a CGNAT/private collision and never validates, the
|
|
4582
|
+
// normal 12s stale-LAN-lock breaker restores the public/relay path.
|
|
4583
|
+
const samePublicNat = !!this.#srflxCache?.addr.host && this.#srflxCache.addr.host === host;
|
|
4562
4584
|
const sameLan = lanPort !== 0 &&
|
|
4563
4585
|
isPrivateAddress(lanHost) &&
|
|
4564
4586
|
!isCgnatAddress(lanHost) &&
|
|
4565
|
-
getPhysicalLanSubnets().some((s) => isInIpv4Subnet(lanHost, s)) &&
|
|
4587
|
+
(getPhysicalLanSubnets().some((s) => isInIpv4Subnet(lanHost, s)) || samePublicNat) &&
|
|
4566
4588
|
!(getPhysicalLanAddresses().includes(lanHost) && this.#udp.localPort() === lanPort);
|
|
4567
4589
|
if (sameLan) {
|
|
4568
4590
|
this.#rememberEndpointCandidate(session, lanHost, lanPort);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.105",
|
|
4
4
|
"description": "Pure TypeScript port of Elastos Carrier (toxcore-derived) P2P messaging. DHT, onion routing, TCP relay, FlatBuffers app payloads, Express offline relay. Wire-compatible with iOS Beagle and the Carrier C SDK.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|