@decentnetwork/peer 0.1.105 → 0.1.107
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.d.ts +6 -0
- package/dist/compat/filetransfer.js +53 -4
- package/dist/peer.d.ts +5 -0
- package/dist/peer.js +55 -1
- package/package.json +1 -1
|
@@ -57,6 +57,12 @@ export declare class FileTransferManager {
|
|
|
57
57
|
accept(friendId: string, fileNumber: number): void;
|
|
58
58
|
cancel(friendId: string, fileNumber: number, isSending: boolean): void;
|
|
59
59
|
cancelByFileId(friendId: string, fileIdHex: string, isSending: boolean): boolean;
|
|
60
|
+
/** Cancel in-flight sends matching both name and size.
|
|
61
|
+
* This is a conservative registry-desync fallback after fileId/hash lookup. */
|
|
62
|
+
cancelSendsMatching(friendId: string, match: {
|
|
63
|
+
name?: string;
|
|
64
|
+
size: number;
|
|
65
|
+
}): string[];
|
|
60
66
|
handlePacket(friendId: string, packetId: number, payload: Uint8Array): boolean;
|
|
61
67
|
clearFriend(friendId: string): void;
|
|
62
68
|
}
|
|
@@ -280,6 +280,8 @@ export class FileTransferManager {
|
|
|
280
280
|
btlBwBps: 0, bwSamples: [], fullBwRounds: 0, startupQueueRounds: 0, bbrPhase: 0, roundCount: 0,
|
|
281
281
|
rateMarkMs: nowMs(), rateMarkAcked: 0, lowRttCount: 0, lowRttMinMs: Infinity, lastCcLogMs: 0,
|
|
282
282
|
lanPath,
|
|
283
|
+
dbgLanNow: lanPath ? 1 : 0, dbgSampleMs: 0, dbgRoundRate: 0, dbgMeasuredRate: 0,
|
|
284
|
+
dbgRawQueue: 0, dbgUnder: 0, dbgOvershot: 0,
|
|
283
285
|
};
|
|
284
286
|
this.#map(this.#sending, friendId).set(fileNumber, st);
|
|
285
287
|
void this.send(friendId, PACKET_ID_FILE_SENDREQUEST, req).catch(() => undefined);
|
|
@@ -330,27 +332,54 @@ export class FileTransferManager {
|
|
|
330
332
|
}
|
|
331
333
|
}
|
|
332
334
|
cancel(friendId, fileNumber, isSending) {
|
|
335
|
+
const inner = (isSending ? this.#sending : this.#receiving).get(friendId);
|
|
336
|
+
const st = inner?.get(fileNumber);
|
|
337
|
+
const fileId = st ? hex(st.fileId) : undefined;
|
|
333
338
|
const sr = isSending ? 0 : 1;
|
|
334
339
|
void this.send(friendId, PACKET_ID_FILE_CONTROL, encodeFileControl(sr, fileNumber, FILECONTROL.KILL)).catch(() => undefined);
|
|
335
340
|
if (isSending)
|
|
336
341
|
this.#endSend(friendId, fileNumber);
|
|
337
342
|
else
|
|
338
343
|
this.#endRecv(friendId, fileNumber);
|
|
344
|
+
// Local cancel used to tear down silently. The daemon tracks sends in
|
|
345
|
+
// activeSends and needs this event when the registry entry is already gone
|
|
346
|
+
// (or cancel raced with a progress tick) so the UI chip cannot stay "sending".
|
|
347
|
+
if (fileId)
|
|
348
|
+
this.emit("file-cancel", { friendId, fileId, sending: isSending, reason: "local" });
|
|
339
349
|
}
|
|
340
350
|
// Cancel by content fileId (hex) — the daemon/UI tracks transfers by fileId,
|
|
341
351
|
// not the internal fileNumber, so it can stop a specific in-flight transfer.
|
|
342
352
|
cancelByFileId(friendId, fileIdHex, isSending) {
|
|
353
|
+
const want = fileIdHex.trim().toLowerCase();
|
|
343
354
|
const inner = (isSending ? this.#sending : this.#receiving).get(friendId);
|
|
344
355
|
if (!inner)
|
|
345
356
|
return false;
|
|
346
357
|
for (const [fileNumber, st] of inner) {
|
|
347
|
-
if (hex(st.fileId) ===
|
|
358
|
+
if (hex(st.fileId) === want) {
|
|
348
359
|
this.cancel(friendId, fileNumber, isSending);
|
|
349
360
|
return true;
|
|
350
361
|
}
|
|
351
362
|
}
|
|
352
363
|
return false;
|
|
353
364
|
}
|
|
365
|
+
/** Cancel in-flight sends matching both name and size.
|
|
366
|
+
* This is a conservative registry-desync fallback after fileId/hash lookup. */
|
|
367
|
+
cancelSendsMatching(friendId, match) {
|
|
368
|
+
const inner = this.#sending.get(friendId);
|
|
369
|
+
if (!inner)
|
|
370
|
+
return [];
|
|
371
|
+
const candidates = [...inner].filter(([, st]) => st.size === match.size);
|
|
372
|
+
const named = match.name
|
|
373
|
+
? candidates.filter(([, st]) => st.name === match.name)
|
|
374
|
+
: [];
|
|
375
|
+
const picked = named;
|
|
376
|
+
const killed = [];
|
|
377
|
+
for (const [fileNumber, st] of picked) {
|
|
378
|
+
killed.push(hex(st.fileId));
|
|
379
|
+
this.cancel(friendId, fileNumber, true);
|
|
380
|
+
}
|
|
381
|
+
return killed;
|
|
382
|
+
}
|
|
354
383
|
handlePacket(friendId, packetId, payload) {
|
|
355
384
|
if (packetId === PACKET_ID_FILE_SENDREQUEST) {
|
|
356
385
|
this.#onSendRequest(friendId, payload);
|
|
@@ -798,6 +827,12 @@ export class FileTransferManager {
|
|
|
798
827
|
const measuredRate = lanNow
|
|
799
828
|
? Math.min(roundRate, Math.max(PACE_INIT_BPS, st.paceRateBps * LAN_SAMPLE_MAX_GAIN))
|
|
800
829
|
: roundRate;
|
|
830
|
+
st.dbgLanNow = lanNow ? 1 : 0;
|
|
831
|
+
st.dbgSampleMs = Math.round(sampleMs);
|
|
832
|
+
st.dbgRoundRate = Math.round(roundRate);
|
|
833
|
+
st.dbgMeasuredRate = Math.round(measuredRate);
|
|
834
|
+
st.dbgUnder = 0;
|
|
835
|
+
st.dbgOvershot = 0;
|
|
801
836
|
if (measuredRate > 0) {
|
|
802
837
|
st.bwSamples.push(measuredRate);
|
|
803
838
|
if (st.bwSamples.length > PACE_BW_WINDOW)
|
|
@@ -822,6 +857,8 @@ export class FileTransferManager {
|
|
|
822
857
|
st.cwnd = Math.max(st.cwnd, LAN_CWND_INIT_CHUNKS);
|
|
823
858
|
}
|
|
824
859
|
st.lanPath = lanNow;
|
|
860
|
+
const rawQueue = st.minRttMs !== Infinity ? acceptedRtt - st.minRttMs : 0;
|
|
861
|
+
st.dbgRawQueue = Math.round(rawQueue);
|
|
825
862
|
if (!lanNow) {
|
|
826
863
|
// Relay/public-NAT file traffic must remain conservative. A short
|
|
827
864
|
// ACK burst can wildly overstate bandwidth there; allowing LAN's
|
|
@@ -849,7 +886,6 @@ export class FileTransferManager {
|
|
|
849
886
|
else {
|
|
850
887
|
st.fullBwRounds++;
|
|
851
888
|
}
|
|
852
|
-
const rawQueue = st.minRttMs !== Infinity ? acceptedRtt - st.minRttMs : 0;
|
|
853
889
|
if (rawQueue > LAN_STARTUP_QUEUE_MS) {
|
|
854
890
|
st.startupQueueRounds++;
|
|
855
891
|
}
|
|
@@ -864,6 +900,8 @@ export class FileTransferManager {
|
|
|
864
900
|
const startupOvershot = rawQueue > LAN_STARTUP_HARD_QUEUE_MS ||
|
|
865
901
|
st.startupQueueRounds >= LAN_STARTUP_QUEUE_ROUNDS;
|
|
866
902
|
const startupUnderDelivering = st.fullBwRounds >= LAN_STARTUP_SLOW_ROUNDS;
|
|
903
|
+
st.dbgUnder = startupUnderDelivering ? 1 : 0;
|
|
904
|
+
st.dbgOvershot = startupOvershot ? 1 : 0;
|
|
867
905
|
if (startupUnderDelivering || startupOvershot) {
|
|
868
906
|
st.bbrPhase = 2;
|
|
869
907
|
}
|
|
@@ -904,15 +942,26 @@ export class FileTransferManager {
|
|
|
904
942
|
}
|
|
905
943
|
if (process.env.DECENT_DEBUG && nowT - st.lastCcLogMs >= 1000) {
|
|
906
944
|
st.lastCcLogMs = nowT;
|
|
945
|
+
const windowEnd = Math.min(st.acked + st.cwnd * MAX_FILE_DATA_SIZE, st.size);
|
|
907
946
|
console.error(`[file-cc] friend=${friendId.slice(0, 8)} ack=${st.acked}/${st.size}` +
|
|
908
947
|
` rtt=${rtt}ms srtt=${st.srttMs.toFixed(1)}ms low=${st.lowRttCount}` +
|
|
909
948
|
` accepted=${(!lowSample || promoteLowPath) ? 1 : 0}` +
|
|
910
949
|
` pace=${Math.round(st.paceRateBps)}Bps bw=${Math.round(st.btlBwBps)}Bps` +
|
|
911
950
|
` cwnd=${st.cwnd} phase=${st.bbrPhase} slow=${st.fullBwRounds}` +
|
|
912
|
-
` loss=${(st.lossEwma * 100).toFixed(1)}% rounds=${st.roundCount}`
|
|
951
|
+
` loss=${(st.lossEwma * 100).toFixed(1)}% rounds=${st.roundCount}` +
|
|
952
|
+
` lanNow=${st.dbgLanNow} lanPath=${st.lanPath ? 1 : 0}` +
|
|
953
|
+
` sampleMs=${st.dbgSampleMs} roundRate=${st.dbgRoundRate}` +
|
|
954
|
+
` measuredRate=${st.dbgMeasuredRate} rawQueue=${st.dbgRawQueue}` +
|
|
955
|
+
` startupQueueRounds=${st.startupQueueRounds}` +
|
|
956
|
+
` under=${st.dbgUnder} overshot=${st.dbgOvershot}` +
|
|
957
|
+
` rateMarkAcked=${st.rateMarkAcked} nextSend=${st.nextSend}` +
|
|
958
|
+
` windowEnd=${windowEnd} tokens=${Math.round(st.paceTokens)}`);
|
|
913
959
|
}
|
|
914
960
|
}
|
|
915
|
-
|
|
961
|
+
// Small files (UI screenshots etc.) used to sit at 0% until the final
|
|
962
|
+
// byte because progress only fired every 256 KB. Emit at least every
|
|
963
|
+
// 16 KB or on completion so a 100–200 KB send shows movement.
|
|
964
|
+
if (st.acked - st.lastProgressAcked >= 16 * 1024 || st.acked >= st.size) {
|
|
916
965
|
st.lastProgressAcked = st.acked;
|
|
917
966
|
this.emit("file-progress", { friendId, fileId: hex(st.fileId), received: st.acked, total: st.size, sending: true });
|
|
918
967
|
}
|
package/dist/peer.d.ts
CHANGED
|
@@ -164,6 +164,11 @@ export declare class Peer {
|
|
|
164
164
|
cancelFile(userid: string, fileNumber: number, isSending?: boolean): void;
|
|
165
165
|
/** Cancel an in-flight transfer by its content fileId (hex). Returns true if found. */
|
|
166
166
|
cancelFileById(userid: string, fileId: string, isSending?: boolean): boolean;
|
|
167
|
+
/** Cancel in-flight sends that match size/name (registry-desync fallback). */
|
|
168
|
+
cancelSendsMatching(userid: string, match: {
|
|
169
|
+
name?: string;
|
|
170
|
+
size: number;
|
|
171
|
+
}): string[];
|
|
167
172
|
/** Incoming file offer: { friendId, fileNumber, fileId, name, size, kind }. */
|
|
168
173
|
onFile(cb: (offer: {
|
|
169
174
|
friendId: string;
|
package/dist/peer.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
2
4
|
import { mkdir, readFile, writeFile, rename } from "node:fs/promises";
|
|
3
5
|
import { networkInterfaces } from "node:os";
|
|
4
6
|
import { dirname, join } from "node:path";
|
|
@@ -1348,6 +1350,10 @@ export class Peer {
|
|
|
1348
1350
|
cancelFile(userid, fileNumber, isSending = false) { this.#fileTransfer.cancel(userid, fileNumber, isSending); }
|
|
1349
1351
|
/** Cancel an in-flight transfer by its content fileId (hex). Returns true if found. */
|
|
1350
1352
|
cancelFileById(userid, fileId, isSending = false) { return this.#fileTransfer.cancelByFileId(userid, fileId, isSending); }
|
|
1353
|
+
/** Cancel in-flight sends that match size/name (registry-desync fallback). */
|
|
1354
|
+
cancelSendsMatching(userid, match) {
|
|
1355
|
+
return this.#fileTransfer.cancelSendsMatching(userid, match);
|
|
1356
|
+
}
|
|
1351
1357
|
/** Incoming file offer: { friendId, fileNumber, fileId, name, size, kind }. */
|
|
1352
1358
|
onFile(cb) { this.#events.on("file-offer", cb); }
|
|
1353
1359
|
/** Transfer progress: { friendId, fileId, received, total, sending? }. */
|
|
@@ -4489,7 +4495,11 @@ export class Peer {
|
|
|
4489
4495
|
// office silently fall back to the TURN relay (~260ms for a <1ms hop).
|
|
4490
4496
|
const lanIp = getPhysicalLanAddresses().find((ip) => isPrivateAddress(ip));
|
|
4491
4497
|
const lanOctets = lanIp ? lanIp.split(".").map((s) => parseInt(s, 10)) : undefined;
|
|
4492
|
-
|
|
4498
|
+
// WSL packets are NATed by Windows, so the physical host NIC must be paired
|
|
4499
|
+
// with the observed server-reflexive port, not WSL's internal socket port.
|
|
4500
|
+
const lanPort = lanIp && _wslHostAddrsCache.has(lanIp)
|
|
4501
|
+
? srflx.port
|
|
4502
|
+
: (this.#udp.localPort() ?? 0);
|
|
4493
4503
|
const lanValid = !!lanOctets && lanOctets.length === 4 && !lanOctets.some((o) => Number.isNaN(o)) && lanPort > 0;
|
|
4494
4504
|
const size = lanValid ? 18 : relayValid ? 12 : 6;
|
|
4495
4505
|
const payload = new Uint8Array(size);
|
|
@@ -6327,6 +6337,45 @@ let _lanAddrsCache = [];
|
|
|
6327
6337
|
let _lanSubnetsCache = [];
|
|
6328
6338
|
let _allOwnAddrsCache = new Set();
|
|
6329
6339
|
let _ownVirtualAddrsCache = new Set();
|
|
6340
|
+
let _wslHostAddrsCache = new Set();
|
|
6341
|
+
let _wslHostCacheMs = 0;
|
|
6342
|
+
/** Discover the Windows host's physical NIC from WSL2. WSL itself only sees
|
|
6343
|
+
* its 172.x Hyper-V NIC, which is unreachable from another LAN machine. */
|
|
6344
|
+
function getWslWindowsHostAddresses(localSubnets) {
|
|
6345
|
+
const now = Date.now();
|
|
6346
|
+
if (now - _wslHostCacheMs < 60_000)
|
|
6347
|
+
return [..._wslHostAddrsCache];
|
|
6348
|
+
_wslHostCacheMs = now;
|
|
6349
|
+
const found = new Set();
|
|
6350
|
+
const ipconfig = "/mnt/c/Windows/System32/ipconfig.exe";
|
|
6351
|
+
if (process.platform !== "linux" || !existsSync(ipconfig)) {
|
|
6352
|
+
_wslHostAddrsCache = found;
|
|
6353
|
+
return [];
|
|
6354
|
+
}
|
|
6355
|
+
try {
|
|
6356
|
+
const output = execFileSync(ipconfig, [], {
|
|
6357
|
+
encoding: "utf8",
|
|
6358
|
+
timeout: 2000,
|
|
6359
|
+
windowsHide: true,
|
|
6360
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
6361
|
+
});
|
|
6362
|
+
// "IPv4" remains in localized ipconfig output (including Chinese).
|
|
6363
|
+
for (const match of output.matchAll(/IPv4[^:\r\n]*:\s*(\d+\.\d+\.\d+\.\d+)/gi)) {
|
|
6364
|
+
const host = match[1];
|
|
6365
|
+
if (!isPrivateAddress(host) || isCgnatAddress(host) || host.startsWith("127.") || host.startsWith("169.254."))
|
|
6366
|
+
continue;
|
|
6367
|
+
// Exclude WSL/Hyper-V addresses already visible inside Linux.
|
|
6368
|
+
if (localSubnets.some((s) => isInIpv4Subnet(host, s)))
|
|
6369
|
+
continue;
|
|
6370
|
+
found.add(host);
|
|
6371
|
+
}
|
|
6372
|
+
}
|
|
6373
|
+
catch {
|
|
6374
|
+
// WSL interop can be disabled; ordinary interface discovery remains valid.
|
|
6375
|
+
}
|
|
6376
|
+
_wslHostAddrsCache = found;
|
|
6377
|
+
return [...found];
|
|
6378
|
+
}
|
|
6330
6379
|
function refreshLanIfaceCache() {
|
|
6331
6380
|
const now = Date.now();
|
|
6332
6381
|
if (_lanIfaceCacheMs !== 0 && now - _lanIfaceCacheMs < 5000)
|
|
@@ -6363,6 +6412,11 @@ function refreshLanIfaceCache() {
|
|
|
6363
6412
|
catch {
|
|
6364
6413
|
// best-effort
|
|
6365
6414
|
}
|
|
6415
|
+
// Prefer the physical Windows host NIC over WSL's internal eth0 address.
|
|
6416
|
+
const wslHostAddrs = getWslWindowsHostAddresses(subnets);
|
|
6417
|
+
for (const host of wslHostAddrs)
|
|
6418
|
+
allOwn.add(host);
|
|
6419
|
+
addrs.unshift(...wslHostAddrs);
|
|
6366
6420
|
_lanAddrsCache = addrs;
|
|
6367
6421
|
_lanSubnetsCache = subnets;
|
|
6368
6422
|
_allOwnAddrsCache = allOwn;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.107",
|
|
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",
|