@dropgate/core 3.0.0 → 3.0.1
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 +4 -4
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +97 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +97 -6
- package/dist/index.js.map +1 -1
- package/dist/p2p/index.cjs +97 -6
- package/dist/p2p/index.cjs.map +1 -1
- package/dist/p2p/index.js +97 -6
- package/dist/p2p/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1129,6 +1129,7 @@ var P2P_END_ACK_RETRY_DELAY_MS = 100;
|
|
|
1129
1129
|
var P2P_CLOSE_GRACE_PERIOD_MS = 2e3;
|
|
1130
1130
|
|
|
1131
1131
|
// src/p2p/send.ts
|
|
1132
|
+
var P2P_UNACKED_CHUNK_TIMEOUT_MS = 3e4;
|
|
1132
1133
|
function generateSessionId() {
|
|
1133
1134
|
return crypto.randomUUID();
|
|
1134
1135
|
}
|
|
@@ -1218,6 +1219,10 @@ async function startP2PSend(opts) {
|
|
|
1218
1219
|
const unackedChunks = /* @__PURE__ */ new Map();
|
|
1219
1220
|
let nextSeq = 0;
|
|
1220
1221
|
let ackResolvers = [];
|
|
1222
|
+
let transferEverStarted = false;
|
|
1223
|
+
const connectionAttempts = [];
|
|
1224
|
+
const MAX_CONNECTION_ATTEMPTS = 10;
|
|
1225
|
+
const CONNECTION_RATE_WINDOW_MS = 1e4;
|
|
1221
1226
|
const transitionTo = (newState) => {
|
|
1222
1227
|
if (!ALLOWED_TRANSITIONS[state].includes(newState)) {
|
|
1223
1228
|
console.warn(`[P2P Send] Invalid state transition: ${state} -> ${newState}`);
|
|
@@ -1328,6 +1333,12 @@ async function startP2PSend(opts) {
|
|
|
1328
1333
|
const sendChunk = async (conn, data, offset, fileTotal) => {
|
|
1329
1334
|
if (chunkAcknowledgments) {
|
|
1330
1335
|
while (unackedChunks.size >= maxUnackedChunks) {
|
|
1336
|
+
const now = Date.now();
|
|
1337
|
+
for (const [_seq, chunk] of unackedChunks) {
|
|
1338
|
+
if (now - chunk.sentAt > P2P_UNACKED_CHUNK_TIMEOUT_MS) {
|
|
1339
|
+
throw new DropgateNetworkError("Receiver stopped acknowledging chunks");
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1331
1342
|
await Promise.race([
|
|
1332
1343
|
waitForAck(),
|
|
1333
1344
|
sleep(1e3)
|
|
@@ -1384,6 +1395,23 @@ async function startP2PSend(opts) {
|
|
|
1384
1395
|
};
|
|
1385
1396
|
peer.on("connection", (conn) => {
|
|
1386
1397
|
if (isStopped()) return;
|
|
1398
|
+
const now = Date.now();
|
|
1399
|
+
while (connectionAttempts.length > 0 && connectionAttempts[0] < now - CONNECTION_RATE_WINDOW_MS) {
|
|
1400
|
+
connectionAttempts.shift();
|
|
1401
|
+
}
|
|
1402
|
+
if (connectionAttempts.length >= MAX_CONNECTION_ATTEMPTS) {
|
|
1403
|
+
console.warn("[P2P Send] Connection rate limit exceeded, rejecting connection");
|
|
1404
|
+
try {
|
|
1405
|
+
conn.send({ t: "error", message: "Too many connection attempts. Please wait." });
|
|
1406
|
+
} catch {
|
|
1407
|
+
}
|
|
1408
|
+
try {
|
|
1409
|
+
conn.close();
|
|
1410
|
+
} catch {
|
|
1411
|
+
}
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
connectionAttempts.push(now);
|
|
1387
1415
|
if (activeConn) {
|
|
1388
1416
|
const isOldConnOpen = activeConn.open !== false;
|
|
1389
1417
|
if (isOldConnOpen && state === "transferring") {
|
|
@@ -1402,6 +1430,17 @@ async function startP2PSend(opts) {
|
|
|
1402
1430
|
} catch {
|
|
1403
1431
|
}
|
|
1404
1432
|
activeConn = null;
|
|
1433
|
+
if (transferEverStarted) {
|
|
1434
|
+
try {
|
|
1435
|
+
conn.send({ t: "error", message: "Transfer already started with another receiver. Cannot reconnect." });
|
|
1436
|
+
} catch {
|
|
1437
|
+
}
|
|
1438
|
+
try {
|
|
1439
|
+
conn.close();
|
|
1440
|
+
} catch {
|
|
1441
|
+
}
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1405
1444
|
state = "listening";
|
|
1406
1445
|
sentBytes = 0;
|
|
1407
1446
|
nextSeq = 0;
|
|
@@ -1531,6 +1570,7 @@ async function startP2PSend(opts) {
|
|
|
1531
1570
|
}, heartbeatIntervalMs);
|
|
1532
1571
|
}
|
|
1533
1572
|
transitionTo("transferring");
|
|
1573
|
+
transferEverStarted = true;
|
|
1534
1574
|
let overallSentBytes = 0;
|
|
1535
1575
|
for (let fi = 0; fi < files.length; fi++) {
|
|
1536
1576
|
const currentFile = files[fi];
|
|
@@ -1702,6 +1742,10 @@ async function startP2PReceive(opts) {
|
|
|
1702
1742
|
let fileList = null;
|
|
1703
1743
|
let currentFileReceived = 0;
|
|
1704
1744
|
let totalReceivedAllFiles = 0;
|
|
1745
|
+
let expectedChunkSeq = 0;
|
|
1746
|
+
let writeQueueDepth = 0;
|
|
1747
|
+
const MAX_WRITE_QUEUE_DEPTH = 100;
|
|
1748
|
+
const MAX_FILE_COUNT = 1e4;
|
|
1705
1749
|
const transitionTo = (newState) => {
|
|
1706
1750
|
if (!ALLOWED_TRANSITIONS2[state].includes(newState)) {
|
|
1707
1751
|
console.warn(`[P2P Receive] Invalid state transition: ${state} -> ${newState}`);
|
|
@@ -1802,8 +1846,16 @@ async function startP2PReceive(opts) {
|
|
|
1802
1846
|
});
|
|
1803
1847
|
conn.on("data", async (data) => {
|
|
1804
1848
|
try {
|
|
1805
|
-
resetWatchdog();
|
|
1806
1849
|
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data) || typeof Blob !== "undefined" && data instanceof Blob) {
|
|
1850
|
+
if (state !== "transferring") {
|
|
1851
|
+
throw new DropgateValidationError(
|
|
1852
|
+
"Received binary data before transfer was accepted. Possible malicious sender."
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1855
|
+
resetWatchdog();
|
|
1856
|
+
if (writeQueueDepth >= MAX_WRITE_QUEUE_DEPTH) {
|
|
1857
|
+
throw new DropgateNetworkError("Write queue overflow - receiver cannot keep up");
|
|
1858
|
+
}
|
|
1807
1859
|
let bufPromise;
|
|
1808
1860
|
if (data instanceof ArrayBuffer) {
|
|
1809
1861
|
bufPromise = Promise.resolve(new Uint8Array(data));
|
|
@@ -1817,9 +1869,22 @@ async function startP2PReceive(opts) {
|
|
|
1817
1869
|
return;
|
|
1818
1870
|
}
|
|
1819
1871
|
const chunkSeq = pendingChunk?.seq ?? -1;
|
|
1872
|
+
const expectedSize = pendingChunk?.size;
|
|
1820
1873
|
pendingChunk = null;
|
|
1874
|
+
writeQueueDepth++;
|
|
1821
1875
|
writeQueue = writeQueue.then(async () => {
|
|
1822
1876
|
const buf = await bufPromise;
|
|
1877
|
+
if (expectedSize !== void 0 && buf.byteLength !== expectedSize) {
|
|
1878
|
+
throw new DropgateValidationError(
|
|
1879
|
+
`Chunk size mismatch: expected ${expectedSize}, got ${buf.byteLength}`
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
const newReceived = received + buf.byteLength;
|
|
1883
|
+
if (total > 0 && newReceived > total) {
|
|
1884
|
+
throw new DropgateValidationError(
|
|
1885
|
+
`Received more data than expected: ${newReceived} > ${total}`
|
|
1886
|
+
);
|
|
1887
|
+
}
|
|
1823
1888
|
if (onData) {
|
|
1824
1889
|
await onData(buf);
|
|
1825
1890
|
}
|
|
@@ -1841,6 +1906,8 @@ async function startP2PReceive(opts) {
|
|
|
1841
1906
|
} catch {
|
|
1842
1907
|
}
|
|
1843
1908
|
safeError(err2);
|
|
1909
|
+
}).finally(() => {
|
|
1910
|
+
writeQueueDepth--;
|
|
1844
1911
|
});
|
|
1845
1912
|
return;
|
|
1846
1913
|
}
|
|
@@ -1852,10 +1919,21 @@ async function startP2PReceive(opts) {
|
|
|
1852
1919
|
transitionTo("negotiating");
|
|
1853
1920
|
onStatus?.({ phase: "waiting", message: "Waiting for file details..." });
|
|
1854
1921
|
break;
|
|
1855
|
-
case "file_list":
|
|
1856
|
-
|
|
1857
|
-
|
|
1922
|
+
case "file_list": {
|
|
1923
|
+
const fileListMsg = msg;
|
|
1924
|
+
if (fileListMsg.fileCount > MAX_FILE_COUNT) {
|
|
1925
|
+
throw new DropgateValidationError(`Too many files: ${fileListMsg.fileCount}`);
|
|
1926
|
+
}
|
|
1927
|
+
const sumSize = fileListMsg.files.reduce((sum, f) => sum + f.size, 0);
|
|
1928
|
+
if (sumSize !== fileListMsg.totalSize) {
|
|
1929
|
+
throw new DropgateValidationError(
|
|
1930
|
+
`File list size mismatch: declared ${fileListMsg.totalSize}, actual sum ${sumSize}`
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
fileList = fileListMsg;
|
|
1934
|
+
total = fileListMsg.totalSize;
|
|
1858
1935
|
break;
|
|
1936
|
+
}
|
|
1859
1937
|
case "meta": {
|
|
1860
1938
|
if (state !== "negotiating" && !(state === "transferring" && fileList)) {
|
|
1861
1939
|
return;
|
|
@@ -1917,9 +1995,22 @@ async function startP2PReceive(opts) {
|
|
|
1917
1995
|
}
|
|
1918
1996
|
break;
|
|
1919
1997
|
}
|
|
1920
|
-
case "chunk":
|
|
1921
|
-
|
|
1998
|
+
case "chunk": {
|
|
1999
|
+
const chunkMsg = msg;
|
|
2000
|
+
if (state !== "transferring") {
|
|
2001
|
+
throw new DropgateValidationError(
|
|
2002
|
+
"Received chunk message before transfer was accepted."
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
if (chunkMsg.seq !== expectedChunkSeq) {
|
|
2006
|
+
throw new DropgateValidationError(
|
|
2007
|
+
`Chunk sequence error: expected ${expectedChunkSeq}, got ${chunkMsg.seq}`
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
expectedChunkSeq++;
|
|
2011
|
+
pendingChunk = chunkMsg;
|
|
1922
2012
|
break;
|
|
2013
|
+
}
|
|
1923
2014
|
case "ping":
|
|
1924
2015
|
try {
|
|
1925
2016
|
conn.send({ t: "pong", timestamp: Date.now() });
|