@decentnetwork/peer 0.1.109 → 0.1.110
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 +4 -1
- package/dist/compat/filetransfer.js +41 -6
- package/dist/peer.js +24 -12
- package/package.json +1 -1
|
@@ -41,12 +41,15 @@ export declare function parseFileData(p: Uint8Array): {
|
|
|
41
41
|
export type FtSend = (friendId: string, packetId: number, payload: Uint8Array) => Promise<unknown>;
|
|
42
42
|
export type FtEmit = (event: string, payload: Record<string, unknown>) => void;
|
|
43
43
|
export type FtIsLanPath = (friendId: string) => boolean;
|
|
44
|
+
export type FtPathKind = "lan" | "udp-direct" | "relay";
|
|
45
|
+
export type FtPathKindFn = (friendId: string) => FtPathKind;
|
|
44
46
|
export declare class FileTransferManager {
|
|
45
47
|
#private;
|
|
46
48
|
private readonly send;
|
|
47
49
|
private readonly emit;
|
|
48
50
|
private readonly isLanPath;
|
|
49
|
-
|
|
51
|
+
private readonly pathKind;
|
|
52
|
+
constructor(send: FtSend, emit: FtEmit, isLanPath?: FtIsLanPath, pathKind?: FtPathKindFn);
|
|
50
53
|
/** Enable resumable receives: partials are persisted under `dir` and matched
|
|
51
54
|
* on re-offer by content-hash fileId. Unset = in-memory only (no resume). */
|
|
52
55
|
setResumeDir(dir: string): void;
|
|
@@ -209,15 +209,17 @@ export class FileTransferManager {
|
|
|
209
209
|
send;
|
|
210
210
|
emit;
|
|
211
211
|
isLanPath;
|
|
212
|
+
pathKind;
|
|
212
213
|
#sending = new Map();
|
|
213
214
|
#receiving = new Map();
|
|
214
215
|
// When set, incoming transfers persist their contiguous prefix here so a
|
|
215
216
|
// dropped/restarted transfer resumes instead of restarting (断点续传).
|
|
216
217
|
#resumeDir;
|
|
217
|
-
constructor(send, emit, isLanPath = () => false) {
|
|
218
|
+
constructor(send, emit, isLanPath = () => false, pathKind = (friendId) => (this.isLanPath(friendId) ? "lan" : "relay")) {
|
|
218
219
|
this.send = send;
|
|
219
220
|
this.emit = emit;
|
|
220
221
|
this.isLanPath = isLanPath;
|
|
222
|
+
this.pathKind = pathKind;
|
|
221
223
|
}
|
|
222
224
|
/** Enable resumable receives: partials are persisted under `dir` and matched
|
|
223
225
|
* on re-offer by content-hash fileId. Unset = in-memory only (no resume). */
|
|
@@ -279,7 +281,7 @@ export class FileTransferManager {
|
|
|
279
281
|
minRttMs: Infinity, srttMs: 0, probeOffset: -1, probeSentMs: 0,
|
|
280
282
|
paceRateBps: lanPath ? LAN_PACE_INIT_BPS : PACE_INIT_BPS,
|
|
281
283
|
paceTokens: 0, lastPaceMs: nowMs(),
|
|
282
|
-
btlBwBps: 0, bwSamples: [], fullBwRounds: 0, startupQueueRounds: 0, bbrPhase: 0, roundCount: 0,
|
|
284
|
+
btlBwBps: 0, bwSamples: [], fullBwBps: 0, fullBwRounds: 0, startupQueueRounds: 0, bbrPhase: 0, roundCount: 0,
|
|
283
285
|
rateMarkMs: nowMs(), rateMarkAcked: 0, lowRttCount: 0, lowRttMinMs: Infinity, lastCcLogMs: 0,
|
|
284
286
|
lanPath,
|
|
285
287
|
dbgLanNow: lanPath ? 1 : 0, dbgSampleMs: 0, dbgRoundRate: 0, dbgMeasuredRate: 0,
|
|
@@ -821,7 +823,9 @@ export class FileTransferManager {
|
|
|
821
823
|
const roundRate = ((st.acked - st.rateMarkAcked) * 1000) / sampleMs;
|
|
822
824
|
st.rateMarkMs = nowT;
|
|
823
825
|
st.rateMarkAcked = st.acked;
|
|
824
|
-
const
|
|
826
|
+
const pathKind = this.pathKind(friendId);
|
|
827
|
+
const lanNow = pathKind === "lan";
|
|
828
|
+
const udpDirectNow = pathKind === "udp-direct";
|
|
825
829
|
// On a paced LAN flow, sustainable delivery cannot suddenly be
|
|
826
830
|
// many times faster than the bytes we are putting on the wire.
|
|
827
831
|
// Such a sample is delayed/coalesced ACKs draining, not new
|
|
@@ -861,15 +865,46 @@ export class FileTransferManager {
|
|
|
861
865
|
st.lanPath = lanNow;
|
|
862
866
|
const rawQueue = st.minRttMs !== Infinity ? acceptedRtt - st.minRttMs : 0;
|
|
863
867
|
st.dbgRawQueue = Math.round(rawQueue);
|
|
864
|
-
if (!lanNow) {
|
|
865
|
-
//
|
|
868
|
+
if (!lanNow && !udpDirectNow) {
|
|
869
|
+
// TCP-relay-only file traffic must remain conservative. A short
|
|
866
870
|
// ACK burst can wildly overstate bandwidth there; allowing LAN's
|
|
867
871
|
// exponential STARTUP filled the TCP relay with an 8.7 s queue.
|
|
868
|
-
//
|
|
872
|
+
// A confirmed public UDP-direct path is different: it is the old
|
|
873
|
+
// long-haul file path with FEC and BBR-style pacing, so do not
|
|
874
|
+
// pin it to 120-240 KB/s.
|
|
869
875
|
st.bbrPhase = 2;
|
|
870
876
|
const gain = PACE_CYCLE_GAINS[st.roundCount % PACE_CYCLE_GAINS.length];
|
|
871
877
|
st.paceRateBps = Math.max(PACE_INIT_BPS, Math.min(PACE_INIT_BPS * 2, st.btlBwBps * gain * lossMult));
|
|
872
878
|
}
|
|
879
|
+
else if (udpDirectNow) {
|
|
880
|
+
// Public UDP direct: restore the pre-LAN-regression remote path.
|
|
881
|
+
// It keeps FEC loss compensation and paced BBR startup/probe, but
|
|
882
|
+
// does NOT use LAN's aggressive 2 MB/s initial probe or ACK-burst
|
|
883
|
+
// sample cap. Queue growth still exits STARTUP quickly, so this
|
|
884
|
+
// does not recreate the deep TCP-relay bufferbloat issue.
|
|
885
|
+
if (st.bbrPhase === 0) {
|
|
886
|
+
if (st.fullBwBps === 0 || st.btlBwBps >= st.fullBwBps * 1.20) {
|
|
887
|
+
st.fullBwBps = st.btlBwBps;
|
|
888
|
+
st.fullBwRounds = 0;
|
|
889
|
+
}
|
|
890
|
+
else {
|
|
891
|
+
st.fullBwRounds++;
|
|
892
|
+
}
|
|
893
|
+
if (st.fullBwRounds >= 3 || rawQueue > 350) {
|
|
894
|
+
st.bbrPhase = 2;
|
|
895
|
+
}
|
|
896
|
+
if (st.bbrPhase === 0) {
|
|
897
|
+
st.paceRateBps = Math.max(st.paceRateBps * 1.4, st.btlBwBps * 2.0 * lossMult);
|
|
898
|
+
}
|
|
899
|
+
else {
|
|
900
|
+
st.paceRateBps = Math.max(PACE_INIT_BPS * 0.5, st.btlBwBps * lossMult);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
const gain = PACE_CYCLE_GAINS[st.roundCount % PACE_CYCLE_GAINS.length];
|
|
905
|
+
st.paceRateBps = Math.max(PACE_INIT_BPS * 0.5, st.btlBwBps * gain * lossMult);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
873
908
|
else if (st.bbrPhase === 0) {
|
|
874
909
|
// BBR full-bandwidth detection: remain in STARTUP while delivered
|
|
875
910
|
// bandwidth grows. A relative RTT threshold is unusable on LAN —
|
package/dist/peer.js
CHANGED
|
@@ -230,6 +230,16 @@ export class Peer {
|
|
|
230
230
|
}, (event, payload) => { this.#events.emit(event, payload); }, (friendId) => {
|
|
231
231
|
const session = this.#friendSessions.get(friendId);
|
|
232
232
|
return !!session?.lanRemoteHost && session.remote?.host === session.lanRemoteHost;
|
|
233
|
+
}, (friendId) => {
|
|
234
|
+
const session = this.#friendSessions.get(friendId);
|
|
235
|
+
const realUdpRemote = session?.remote && !session.remote.host?.startsWith("tcp:") && session.remote.port !== 0;
|
|
236
|
+
if (realUdpRemote && session.lanRemoteHost && session.remote?.host === session.lanRemoteHost)
|
|
237
|
+
return "lan";
|
|
238
|
+
if (realUdpRemote &&
|
|
239
|
+
session.lastUdpRecvMs !== undefined &&
|
|
240
|
+
Date.now() - session.lastUdpRecvMs < 4_000)
|
|
241
|
+
return "udp-direct";
|
|
242
|
+
return "relay";
|
|
233
243
|
});
|
|
234
244
|
#keyPair;
|
|
235
245
|
#udp = new UdpTransport();
|
|
@@ -4012,15 +4022,16 @@ export class Peer {
|
|
|
4012
4022
|
Date.now() - s.lastUdpRecvMs < 4_000;
|
|
4013
4023
|
const forceFileRelay = (this.#fileRelayNegotiationUntil.get(friendId) ?? 0) > Date.now() &&
|
|
4014
4024
|
!(s?.lanRemoteHost && s.remote?.host === s.lanRemoteHost);
|
|
4015
|
-
//
|
|
4016
|
-
//
|
|
4017
|
-
//
|
|
4018
|
-
//
|
|
4019
|
-
//
|
|
4020
|
-
//
|
|
4021
|
-
//
|
|
4022
|
-
|
|
4023
|
-
|
|
4025
|
+
// File DATA/FEC is reliable at the file layer: FEC + ACK/retransmit repair
|
|
4026
|
+
// loss. It should use a fresh decrypted UDP-direct path, including public
|
|
4027
|
+
// long-haul peers. A previous LAN fix accidentally required a LAN lock here,
|
|
4028
|
+
// which forced China/US file sends back onto TCP relay and destroyed the
|
|
4029
|
+
// already-tuned remote-transfer path.
|
|
4030
|
+
//
|
|
4031
|
+
// The one-way-public-UDP blackhole is handled by keeping SENDREQUEST/CONTROL
|
|
4032
|
+
// on relay during negotiation (`forceFileRelay`) until a CONTROL packet
|
|
4033
|
+
// proves the receiver is responding. Once `lastUdpRecvMs` is fresh, bulk may
|
|
4034
|
+
// ride UDP only; if it goes stale, we fall back to relay/TCP.
|
|
4024
4035
|
// Bulk IP data rides the direct path ONLY while it's fresh. When it
|
|
4025
4036
|
// goes stale (likely a NAT remap mid-stream), we do NOT keep firing
|
|
4026
4037
|
// into the dead endpoint and we do NOT duplicate onto UDP — we bridge
|
|
@@ -4030,7 +4041,7 @@ export class Peer {
|
|
|
4030
4041
|
// (to keep the NAT mapping warm) so we still try UDP for them.
|
|
4031
4042
|
const tryUdp = !forceFileRelay &&
|
|
4032
4043
|
realUdpRemote &&
|
|
4033
|
-
(isBulkData ? udpFresh
|
|
4044
|
+
(isBulkData ? udpFresh : true);
|
|
4034
4045
|
if (tryUdp && s?.remote) {
|
|
4035
4046
|
try {
|
|
4036
4047
|
await this.#sendPacket(packet, s.remote);
|
|
@@ -4041,8 +4052,9 @@ export class Peer {
|
|
|
4041
4052
|
}
|
|
4042
4053
|
}
|
|
4043
4054
|
// Fresh confirmed direct path → send over UDP only, but ONLY for bulk.
|
|
4044
|
-
// For files
|
|
4045
|
-
//
|
|
4055
|
+
// For files this includes public UDP-direct, not only LAN; the file layer
|
|
4056
|
+
// handles loss and retransmit. Sparse control/chat still also go over relay
|
|
4057
|
+
// because inbound UDP freshness alone does not prove outbound reachability.
|
|
4046
4058
|
//
|
|
4047
4059
|
// Low-volume traffic (chat 64 + control keepalives / endpoint offers /
|
|
4048
4060
|
// profile) instead ALSO goes over the relay even when the direct path
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/peer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.110",
|
|
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",
|