@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.js
CHANGED
|
@@ -1066,6 +1066,7 @@ var P2P_END_ACK_RETRY_DELAY_MS = 100;
|
|
|
1066
1066
|
var P2P_CLOSE_GRACE_PERIOD_MS = 2e3;
|
|
1067
1067
|
|
|
1068
1068
|
// src/p2p/send.ts
|
|
1069
|
+
var P2P_UNACKED_CHUNK_TIMEOUT_MS = 3e4;
|
|
1069
1070
|
function generateSessionId() {
|
|
1070
1071
|
return crypto.randomUUID();
|
|
1071
1072
|
}
|
|
@@ -1155,6 +1156,10 @@ async function startP2PSend(opts) {
|
|
|
1155
1156
|
const unackedChunks = /* @__PURE__ */ new Map();
|
|
1156
1157
|
let nextSeq = 0;
|
|
1157
1158
|
let ackResolvers = [];
|
|
1159
|
+
let transferEverStarted = false;
|
|
1160
|
+
const connectionAttempts = [];
|
|
1161
|
+
const MAX_CONNECTION_ATTEMPTS = 10;
|
|
1162
|
+
const CONNECTION_RATE_WINDOW_MS = 1e4;
|
|
1158
1163
|
const transitionTo = (newState) => {
|
|
1159
1164
|
if (!ALLOWED_TRANSITIONS[state].includes(newState)) {
|
|
1160
1165
|
console.warn(`[P2P Send] Invalid state transition: ${state} -> ${newState}`);
|
|
@@ -1265,6 +1270,12 @@ async function startP2PSend(opts) {
|
|
|
1265
1270
|
const sendChunk = async (conn, data, offset, fileTotal) => {
|
|
1266
1271
|
if (chunkAcknowledgments) {
|
|
1267
1272
|
while (unackedChunks.size >= maxUnackedChunks) {
|
|
1273
|
+
const now = Date.now();
|
|
1274
|
+
for (const [_seq, chunk] of unackedChunks) {
|
|
1275
|
+
if (now - chunk.sentAt > P2P_UNACKED_CHUNK_TIMEOUT_MS) {
|
|
1276
|
+
throw new DropgateNetworkError("Receiver stopped acknowledging chunks");
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1268
1279
|
await Promise.race([
|
|
1269
1280
|
waitForAck(),
|
|
1270
1281
|
sleep(1e3)
|
|
@@ -1321,6 +1332,23 @@ async function startP2PSend(opts) {
|
|
|
1321
1332
|
};
|
|
1322
1333
|
peer.on("connection", (conn) => {
|
|
1323
1334
|
if (isStopped()) return;
|
|
1335
|
+
const now = Date.now();
|
|
1336
|
+
while (connectionAttempts.length > 0 && connectionAttempts[0] < now - CONNECTION_RATE_WINDOW_MS) {
|
|
1337
|
+
connectionAttempts.shift();
|
|
1338
|
+
}
|
|
1339
|
+
if (connectionAttempts.length >= MAX_CONNECTION_ATTEMPTS) {
|
|
1340
|
+
console.warn("[P2P Send] Connection rate limit exceeded, rejecting connection");
|
|
1341
|
+
try {
|
|
1342
|
+
conn.send({ t: "error", message: "Too many connection attempts. Please wait." });
|
|
1343
|
+
} catch {
|
|
1344
|
+
}
|
|
1345
|
+
try {
|
|
1346
|
+
conn.close();
|
|
1347
|
+
} catch {
|
|
1348
|
+
}
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
connectionAttempts.push(now);
|
|
1324
1352
|
if (activeConn) {
|
|
1325
1353
|
const isOldConnOpen = activeConn.open !== false;
|
|
1326
1354
|
if (isOldConnOpen && state === "transferring") {
|
|
@@ -1339,6 +1367,17 @@ async function startP2PSend(opts) {
|
|
|
1339
1367
|
} catch {
|
|
1340
1368
|
}
|
|
1341
1369
|
activeConn = null;
|
|
1370
|
+
if (transferEverStarted) {
|
|
1371
|
+
try {
|
|
1372
|
+
conn.send({ t: "error", message: "Transfer already started with another receiver. Cannot reconnect." });
|
|
1373
|
+
} catch {
|
|
1374
|
+
}
|
|
1375
|
+
try {
|
|
1376
|
+
conn.close();
|
|
1377
|
+
} catch {
|
|
1378
|
+
}
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1342
1381
|
state = "listening";
|
|
1343
1382
|
sentBytes = 0;
|
|
1344
1383
|
nextSeq = 0;
|
|
@@ -1468,6 +1507,7 @@ async function startP2PSend(opts) {
|
|
|
1468
1507
|
}, heartbeatIntervalMs);
|
|
1469
1508
|
}
|
|
1470
1509
|
transitionTo("transferring");
|
|
1510
|
+
transferEverStarted = true;
|
|
1471
1511
|
let overallSentBytes = 0;
|
|
1472
1512
|
for (let fi = 0; fi < files.length; fi++) {
|
|
1473
1513
|
const currentFile = files[fi];
|
|
@@ -1639,6 +1679,10 @@ async function startP2PReceive(opts) {
|
|
|
1639
1679
|
let fileList = null;
|
|
1640
1680
|
let currentFileReceived = 0;
|
|
1641
1681
|
let totalReceivedAllFiles = 0;
|
|
1682
|
+
let expectedChunkSeq = 0;
|
|
1683
|
+
let writeQueueDepth = 0;
|
|
1684
|
+
const MAX_WRITE_QUEUE_DEPTH = 100;
|
|
1685
|
+
const MAX_FILE_COUNT = 1e4;
|
|
1642
1686
|
const transitionTo = (newState) => {
|
|
1643
1687
|
if (!ALLOWED_TRANSITIONS2[state].includes(newState)) {
|
|
1644
1688
|
console.warn(`[P2P Receive] Invalid state transition: ${state} -> ${newState}`);
|
|
@@ -1739,8 +1783,16 @@ async function startP2PReceive(opts) {
|
|
|
1739
1783
|
});
|
|
1740
1784
|
conn.on("data", async (data) => {
|
|
1741
1785
|
try {
|
|
1742
|
-
resetWatchdog();
|
|
1743
1786
|
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data) || typeof Blob !== "undefined" && data instanceof Blob) {
|
|
1787
|
+
if (state !== "transferring") {
|
|
1788
|
+
throw new DropgateValidationError(
|
|
1789
|
+
"Received binary data before transfer was accepted. Possible malicious sender."
|
|
1790
|
+
);
|
|
1791
|
+
}
|
|
1792
|
+
resetWatchdog();
|
|
1793
|
+
if (writeQueueDepth >= MAX_WRITE_QUEUE_DEPTH) {
|
|
1794
|
+
throw new DropgateNetworkError("Write queue overflow - receiver cannot keep up");
|
|
1795
|
+
}
|
|
1744
1796
|
let bufPromise;
|
|
1745
1797
|
if (data instanceof ArrayBuffer) {
|
|
1746
1798
|
bufPromise = Promise.resolve(new Uint8Array(data));
|
|
@@ -1754,9 +1806,22 @@ async function startP2PReceive(opts) {
|
|
|
1754
1806
|
return;
|
|
1755
1807
|
}
|
|
1756
1808
|
const chunkSeq = pendingChunk?.seq ?? -1;
|
|
1809
|
+
const expectedSize = pendingChunk?.size;
|
|
1757
1810
|
pendingChunk = null;
|
|
1811
|
+
writeQueueDepth++;
|
|
1758
1812
|
writeQueue = writeQueue.then(async () => {
|
|
1759
1813
|
const buf = await bufPromise;
|
|
1814
|
+
if (expectedSize !== void 0 && buf.byteLength !== expectedSize) {
|
|
1815
|
+
throw new DropgateValidationError(
|
|
1816
|
+
`Chunk size mismatch: expected ${expectedSize}, got ${buf.byteLength}`
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
const newReceived = received + buf.byteLength;
|
|
1820
|
+
if (total > 0 && newReceived > total) {
|
|
1821
|
+
throw new DropgateValidationError(
|
|
1822
|
+
`Received more data than expected: ${newReceived} > ${total}`
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1760
1825
|
if (onData) {
|
|
1761
1826
|
await onData(buf);
|
|
1762
1827
|
}
|
|
@@ -1778,6 +1843,8 @@ async function startP2PReceive(opts) {
|
|
|
1778
1843
|
} catch {
|
|
1779
1844
|
}
|
|
1780
1845
|
safeError(err2);
|
|
1846
|
+
}).finally(() => {
|
|
1847
|
+
writeQueueDepth--;
|
|
1781
1848
|
});
|
|
1782
1849
|
return;
|
|
1783
1850
|
}
|
|
@@ -1789,10 +1856,21 @@ async function startP2PReceive(opts) {
|
|
|
1789
1856
|
transitionTo("negotiating");
|
|
1790
1857
|
onStatus?.({ phase: "waiting", message: "Waiting for file details..." });
|
|
1791
1858
|
break;
|
|
1792
|
-
case "file_list":
|
|
1793
|
-
|
|
1794
|
-
|
|
1859
|
+
case "file_list": {
|
|
1860
|
+
const fileListMsg = msg;
|
|
1861
|
+
if (fileListMsg.fileCount > MAX_FILE_COUNT) {
|
|
1862
|
+
throw new DropgateValidationError(`Too many files: ${fileListMsg.fileCount}`);
|
|
1863
|
+
}
|
|
1864
|
+
const sumSize = fileListMsg.files.reduce((sum, f) => sum + f.size, 0);
|
|
1865
|
+
if (sumSize !== fileListMsg.totalSize) {
|
|
1866
|
+
throw new DropgateValidationError(
|
|
1867
|
+
`File list size mismatch: declared ${fileListMsg.totalSize}, actual sum ${sumSize}`
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
fileList = fileListMsg;
|
|
1871
|
+
total = fileListMsg.totalSize;
|
|
1795
1872
|
break;
|
|
1873
|
+
}
|
|
1796
1874
|
case "meta": {
|
|
1797
1875
|
if (state !== "negotiating" && !(state === "transferring" && fileList)) {
|
|
1798
1876
|
return;
|
|
@@ -1854,9 +1932,22 @@ async function startP2PReceive(opts) {
|
|
|
1854
1932
|
}
|
|
1855
1933
|
break;
|
|
1856
1934
|
}
|
|
1857
|
-
case "chunk":
|
|
1858
|
-
|
|
1935
|
+
case "chunk": {
|
|
1936
|
+
const chunkMsg = msg;
|
|
1937
|
+
if (state !== "transferring") {
|
|
1938
|
+
throw new DropgateValidationError(
|
|
1939
|
+
"Received chunk message before transfer was accepted."
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1942
|
+
if (chunkMsg.seq !== expectedChunkSeq) {
|
|
1943
|
+
throw new DropgateValidationError(
|
|
1944
|
+
`Chunk sequence error: expected ${expectedChunkSeq}, got ${chunkMsg.seq}`
|
|
1945
|
+
);
|
|
1946
|
+
}
|
|
1947
|
+
expectedChunkSeq++;
|
|
1948
|
+
pendingChunk = chunkMsg;
|
|
1859
1949
|
break;
|
|
1950
|
+
}
|
|
1860
1951
|
case "ping":
|
|
1861
1952
|
try {
|
|
1862
1953
|
conn.send({ t: "pong", timestamp: Date.now() });
|