@dropgate/core 2.0.0-beta.1 → 2.1.0
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 +76 -15
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +385 -138
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +182 -120
- package/dist/index.d.ts +182 -120
- package/dist/index.js +383 -138
- package/dist/index.js.map +1 -1
- package/dist/p2p/index.cjs +268 -62
- package/dist/p2p/index.cjs.map +1 -1
- package/dist/p2p/index.d.cts +154 -92
- package/dist/p2p/index.d.ts +154 -92
- package/dist/p2p/index.js +267 -62
- package/dist/p2p/index.js.map +1 -1
- package/package.json +88 -88
package/dist/p2p/index.cjs
CHANGED
|
@@ -26,6 +26,7 @@ __export(p2p_exports, {
|
|
|
26
26
|
isLocalhostHostname: () => isLocalhostHostname,
|
|
27
27
|
isP2PCodeLike: () => isP2PCodeLike,
|
|
28
28
|
isSecureContextForP2P: () => isSecureContextForP2P,
|
|
29
|
+
resolvePeerConfig: () => resolvePeerConfig,
|
|
29
30
|
startP2PReceive: () => startP2PReceive,
|
|
30
31
|
startP2PSend: () => startP2PSend
|
|
31
32
|
});
|
|
@@ -99,11 +100,11 @@ function isSecureContextForP2P(hostname, isSecureContext) {
|
|
|
99
100
|
return Boolean(isSecureContext) || isLocalhostHostname(hostname || "");
|
|
100
101
|
}
|
|
101
102
|
function generateP2PCode(cryptoObj) {
|
|
102
|
-
const
|
|
103
|
+
const crypto2 = cryptoObj || getDefaultCrypto();
|
|
103
104
|
const letters = "ABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
104
|
-
if (
|
|
105
|
+
if (crypto2) {
|
|
105
106
|
const randomBytes = new Uint8Array(8);
|
|
106
|
-
|
|
107
|
+
crypto2.getRandomValues(randomBytes);
|
|
107
108
|
let letterPart = "";
|
|
108
109
|
for (let i = 0; i < 4; i++) {
|
|
109
110
|
letterPart += letters[randomBytes[i] % letters.length];
|
|
@@ -129,8 +130,14 @@ function isP2PCodeLike(code) {
|
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
// src/p2p/helpers.ts
|
|
132
|
-
function
|
|
133
|
-
|
|
133
|
+
function resolvePeerConfig(userConfig, serverCaps) {
|
|
134
|
+
return {
|
|
135
|
+
path: userConfig.peerjsPath ?? serverCaps?.peerjsPath ?? "/peerjs",
|
|
136
|
+
iceServers: userConfig.iceServers ?? serverCaps?.iceServers ?? []
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function buildPeerOptions(config = {}) {
|
|
140
|
+
const { host, port, peerjsPath = "/peerjs", secure = false, iceServers = [] } = config;
|
|
134
141
|
const peerOpts = {
|
|
135
142
|
host,
|
|
136
143
|
path: peerjsPath,
|
|
@@ -172,6 +179,12 @@ async function createPeerWithRetries(opts) {
|
|
|
172
179
|
}
|
|
173
180
|
|
|
174
181
|
// src/p2p/send.ts
|
|
182
|
+
function generateSessionId() {
|
|
183
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
184
|
+
return crypto.randomUUID();
|
|
185
|
+
}
|
|
186
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
187
|
+
}
|
|
175
188
|
async function startP2PSend(opts) {
|
|
176
189
|
const {
|
|
177
190
|
file,
|
|
@@ -186,15 +199,16 @@ async function startP2PSend(opts) {
|
|
|
186
199
|
cryptoObj,
|
|
187
200
|
maxAttempts = 4,
|
|
188
201
|
chunkSize = 256 * 1024,
|
|
189
|
-
readyTimeoutMs = 8e3,
|
|
190
202
|
endAckTimeoutMs = 15e3,
|
|
191
203
|
bufferHighWaterMark = 8 * 1024 * 1024,
|
|
192
204
|
bufferLowWaterMark = 2 * 1024 * 1024,
|
|
205
|
+
heartbeatIntervalMs = 5e3,
|
|
193
206
|
onCode,
|
|
194
207
|
onStatus,
|
|
195
208
|
onProgress,
|
|
196
209
|
onComplete,
|
|
197
|
-
onError
|
|
210
|
+
onError,
|
|
211
|
+
onDisconnect
|
|
198
212
|
} = opts;
|
|
199
213
|
if (!file) {
|
|
200
214
|
throw new DropgateValidationError("File is missing.");
|
|
@@ -208,8 +222,10 @@ async function startP2PSend(opts) {
|
|
|
208
222
|
if (serverInfo && !p2pCaps?.enabled) {
|
|
209
223
|
throw new DropgateValidationError("Direct transfer is disabled on this server.");
|
|
210
224
|
}
|
|
211
|
-
const finalPath
|
|
212
|
-
|
|
225
|
+
const { path: finalPath, iceServers: finalIceServers } = resolvePeerConfig(
|
|
226
|
+
{ peerjsPath, iceServers },
|
|
227
|
+
p2pCaps
|
|
228
|
+
);
|
|
213
229
|
const peerOpts = buildPeerOptions({
|
|
214
230
|
host,
|
|
215
231
|
port,
|
|
@@ -226,18 +242,37 @@ async function startP2PSend(opts) {
|
|
|
226
242
|
buildPeer,
|
|
227
243
|
onCode
|
|
228
244
|
});
|
|
229
|
-
|
|
245
|
+
const sessionId = generateSessionId();
|
|
246
|
+
let state = "listening";
|
|
230
247
|
let activeConn = null;
|
|
231
|
-
let
|
|
232
|
-
let
|
|
248
|
+
let sentBytes = 0;
|
|
249
|
+
let heartbeatTimer = null;
|
|
233
250
|
const reportProgress = (data) => {
|
|
234
251
|
const safeTotal = Number.isFinite(data.total) && data.total > 0 ? data.total : file.size;
|
|
235
252
|
const safeReceived = Math.min(Number(data.received) || 0, safeTotal || 0);
|
|
236
253
|
const percent = safeTotal ? safeReceived / safeTotal * 100 : 0;
|
|
237
|
-
onProgress?.({
|
|
254
|
+
onProgress?.({ processedBytes: safeReceived, totalBytes: safeTotal, percent });
|
|
238
255
|
};
|
|
239
|
-
const
|
|
240
|
-
|
|
256
|
+
const safeError = (err) => {
|
|
257
|
+
if (state === "closed" || state === "completed") return;
|
|
258
|
+
state = "closed";
|
|
259
|
+
onError?.(err);
|
|
260
|
+
cleanup();
|
|
261
|
+
};
|
|
262
|
+
const safeComplete = () => {
|
|
263
|
+
if (state !== "finishing") return;
|
|
264
|
+
state = "completed";
|
|
265
|
+
onComplete?.();
|
|
266
|
+
cleanup();
|
|
267
|
+
};
|
|
268
|
+
const cleanup = () => {
|
|
269
|
+
if (heartbeatTimer) {
|
|
270
|
+
clearInterval(heartbeatTimer);
|
|
271
|
+
heartbeatTimer = null;
|
|
272
|
+
}
|
|
273
|
+
if (typeof window !== "undefined") {
|
|
274
|
+
window.removeEventListener("beforeunload", handleUnload);
|
|
275
|
+
}
|
|
241
276
|
try {
|
|
242
277
|
activeConn?.close();
|
|
243
278
|
} catch {
|
|
@@ -247,21 +282,59 @@ async function startP2PSend(opts) {
|
|
|
247
282
|
} catch {
|
|
248
283
|
}
|
|
249
284
|
};
|
|
285
|
+
const handleUnload = () => {
|
|
286
|
+
try {
|
|
287
|
+
activeConn?.send({ t: "error", message: "Sender closed the connection." });
|
|
288
|
+
} catch {
|
|
289
|
+
}
|
|
290
|
+
stop();
|
|
291
|
+
};
|
|
292
|
+
if (typeof window !== "undefined") {
|
|
293
|
+
window.addEventListener("beforeunload", handleUnload);
|
|
294
|
+
}
|
|
295
|
+
const stop = () => {
|
|
296
|
+
if (state === "closed") return;
|
|
297
|
+
state = "closed";
|
|
298
|
+
cleanup();
|
|
299
|
+
};
|
|
300
|
+
const isStopped = () => state === "closed";
|
|
250
301
|
peer.on("connection", (conn) => {
|
|
251
|
-
if (
|
|
302
|
+
if (state === "closed") return;
|
|
252
303
|
if (activeConn) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
304
|
+
const isOldConnOpen = activeConn.open !== false;
|
|
305
|
+
if (isOldConnOpen && state === "transferring") {
|
|
306
|
+
try {
|
|
307
|
+
conn.send({ t: "error", message: "Transfer already in progress." });
|
|
308
|
+
} catch {
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
conn.close();
|
|
312
|
+
} catch {
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
} else if (!isOldConnOpen) {
|
|
316
|
+
try {
|
|
317
|
+
activeConn.close();
|
|
318
|
+
} catch {
|
|
319
|
+
}
|
|
320
|
+
activeConn = null;
|
|
321
|
+
state = "listening";
|
|
322
|
+
sentBytes = 0;
|
|
323
|
+
} else {
|
|
324
|
+
try {
|
|
325
|
+
conn.send({ t: "error", message: "Another receiver is already connected." });
|
|
326
|
+
} catch {
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
conn.close();
|
|
330
|
+
} catch {
|
|
331
|
+
}
|
|
332
|
+
return;
|
|
260
333
|
}
|
|
261
|
-
return;
|
|
262
334
|
}
|
|
263
335
|
activeConn = conn;
|
|
264
|
-
|
|
336
|
+
state = "negotiating";
|
|
337
|
+
onStatus?.({ phase: "waiting", message: "Connected. Waiting for receiver to accept..." });
|
|
265
338
|
let readyResolve = null;
|
|
266
339
|
let ackResolve = null;
|
|
267
340
|
const readyPromise = new Promise((resolve) => {
|
|
@@ -277,6 +350,7 @@ async function startP2PSend(opts) {
|
|
|
277
350
|
const msg = data;
|
|
278
351
|
if (!msg.t) return;
|
|
279
352
|
if (msg.t === "ready") {
|
|
353
|
+
onStatus?.({ phase: "transferring", message: "Receiver accepted. Starting transfer..." });
|
|
280
354
|
readyResolve?.();
|
|
281
355
|
return;
|
|
282
356
|
}
|
|
@@ -288,22 +362,23 @@ async function startP2PSend(opts) {
|
|
|
288
362
|
ackResolve?.(msg);
|
|
289
363
|
return;
|
|
290
364
|
}
|
|
365
|
+
if (msg.t === "pong") {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
291
368
|
if (msg.t === "error") {
|
|
292
|
-
|
|
293
|
-
stop();
|
|
369
|
+
safeError(new DropgateNetworkError(msg.message || "Receiver reported an error."));
|
|
294
370
|
}
|
|
295
371
|
});
|
|
296
372
|
conn.on("open", async () => {
|
|
297
373
|
try {
|
|
298
|
-
|
|
299
|
-
if (stopped) return;
|
|
374
|
+
if (isStopped()) return;
|
|
300
375
|
conn.send({
|
|
301
376
|
t: "meta",
|
|
377
|
+
sessionId,
|
|
302
378
|
name: file.name,
|
|
303
379
|
size: file.size,
|
|
304
380
|
mime: file.type || "application/octet-stream"
|
|
305
381
|
});
|
|
306
|
-
let sent = 0;
|
|
307
382
|
const total = file.size;
|
|
308
383
|
const dc = conn._dc;
|
|
309
384
|
if (dc && Number.isFinite(bufferLowWaterMark)) {
|
|
@@ -312,13 +387,25 @@ async function startP2PSend(opts) {
|
|
|
312
387
|
} catch {
|
|
313
388
|
}
|
|
314
389
|
}
|
|
315
|
-
await
|
|
390
|
+
await readyPromise;
|
|
391
|
+
if (isStopped()) return;
|
|
392
|
+
if (heartbeatIntervalMs > 0) {
|
|
393
|
+
heartbeatTimer = setInterval(() => {
|
|
394
|
+
if (state === "transferring" || state === "finishing") {
|
|
395
|
+
try {
|
|
396
|
+
conn.send({ t: "ping" });
|
|
397
|
+
} catch {
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}, heartbeatIntervalMs);
|
|
401
|
+
}
|
|
402
|
+
state = "transferring";
|
|
316
403
|
for (let offset = 0; offset < total; offset += chunkSize) {
|
|
317
|
-
if (
|
|
404
|
+
if (isStopped()) return;
|
|
318
405
|
const slice = file.slice(offset, offset + chunkSize);
|
|
319
406
|
const buf = await slice.arrayBuffer();
|
|
320
407
|
conn.send(buf);
|
|
321
|
-
|
|
408
|
+
sentBytes += buf.byteLength;
|
|
322
409
|
if (dc) {
|
|
323
410
|
while (dc.bufferedAmount > bufferHighWaterMark) {
|
|
324
411
|
await new Promise((resolve) => {
|
|
@@ -338,13 +425,15 @@ async function startP2PSend(opts) {
|
|
|
338
425
|
}
|
|
339
426
|
}
|
|
340
427
|
}
|
|
341
|
-
if (
|
|
428
|
+
if (isStopped()) return;
|
|
429
|
+
state = "finishing";
|
|
342
430
|
conn.send({ t: "end" });
|
|
343
431
|
const ackTimeoutMs = Number.isFinite(endAckTimeoutMs) ? Math.max(endAckTimeoutMs, Math.ceil(file.size / (1024 * 1024)) * 1e3) : null;
|
|
344
432
|
const ackResult = await Promise.race([
|
|
345
433
|
ackPromise,
|
|
346
434
|
sleep(ackTimeoutMs || 15e3).catch(() => null)
|
|
347
435
|
]);
|
|
436
|
+
if (isStopped()) return;
|
|
348
437
|
if (!ackResult || typeof ackResult !== "object") {
|
|
349
438
|
throw new DropgateNetworkError("Receiver did not confirm completion.");
|
|
350
439
|
}
|
|
@@ -355,29 +444,43 @@ async function startP2PSend(opts) {
|
|
|
355
444
|
throw new DropgateNetworkError("Receiver reported an incomplete transfer.");
|
|
356
445
|
}
|
|
357
446
|
reportProgress({ received: ackReceived || ackTotal, total: ackTotal });
|
|
358
|
-
|
|
359
|
-
transferActive = false;
|
|
360
|
-
onComplete?.();
|
|
361
|
-
stop();
|
|
447
|
+
safeComplete();
|
|
362
448
|
} catch (err) {
|
|
363
|
-
|
|
364
|
-
stop();
|
|
449
|
+
safeError(err);
|
|
365
450
|
}
|
|
366
451
|
});
|
|
367
452
|
conn.on("error", (err) => {
|
|
368
|
-
|
|
369
|
-
stop();
|
|
453
|
+
safeError(err);
|
|
370
454
|
});
|
|
371
455
|
conn.on("close", () => {
|
|
372
|
-
if (
|
|
373
|
-
|
|
456
|
+
if (state === "closed" || state === "completed") {
|
|
457
|
+
cleanup();
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (state === "transferring" || state === "finishing") {
|
|
461
|
+
safeError(
|
|
374
462
|
new DropgateNetworkError("Receiver disconnected before transfer completed.")
|
|
375
463
|
);
|
|
464
|
+
} else {
|
|
465
|
+
activeConn = null;
|
|
466
|
+
state = "listening";
|
|
467
|
+
sentBytes = 0;
|
|
468
|
+
onDisconnect?.();
|
|
376
469
|
}
|
|
377
|
-
stop();
|
|
378
470
|
});
|
|
379
471
|
});
|
|
380
|
-
return {
|
|
472
|
+
return {
|
|
473
|
+
peer,
|
|
474
|
+
code,
|
|
475
|
+
sessionId,
|
|
476
|
+
stop,
|
|
477
|
+
getStatus: () => state,
|
|
478
|
+
getBytesSent: () => sentBytes,
|
|
479
|
+
getConnectedPeerId: () => {
|
|
480
|
+
if (!activeConn) return null;
|
|
481
|
+
return activeConn.peer || null;
|
|
482
|
+
}
|
|
483
|
+
};
|
|
381
484
|
}
|
|
382
485
|
|
|
383
486
|
// src/p2p/receive.ts
|
|
@@ -391,6 +494,8 @@ async function startP2PReceive(opts) {
|
|
|
391
494
|
peerjsPath,
|
|
392
495
|
secure = false,
|
|
393
496
|
iceServers,
|
|
497
|
+
autoReady = true,
|
|
498
|
+
watchdogTimeoutMs = 15e3,
|
|
394
499
|
onStatus,
|
|
395
500
|
onMeta,
|
|
396
501
|
onData,
|
|
@@ -415,8 +520,10 @@ async function startP2PReceive(opts) {
|
|
|
415
520
|
if (!isP2PCodeLike(normalizedCode)) {
|
|
416
521
|
throw new DropgateValidationError("Invalid direct transfer code.");
|
|
417
522
|
}
|
|
418
|
-
const finalPath
|
|
419
|
-
|
|
523
|
+
const { path: finalPath, iceServers: finalIceServers } = resolvePeerConfig(
|
|
524
|
+
{ peerjsPath, iceServers },
|
|
525
|
+
p2pCaps
|
|
526
|
+
);
|
|
420
527
|
const peerOpts = buildPeerOptions({
|
|
421
528
|
host,
|
|
422
529
|
port,
|
|
@@ -425,44 +532,127 @@ async function startP2PReceive(opts) {
|
|
|
425
532
|
iceServers: finalIceServers
|
|
426
533
|
});
|
|
427
534
|
const peer = new Peer(void 0, peerOpts);
|
|
535
|
+
let state = "initializing";
|
|
428
536
|
let total = 0;
|
|
429
537
|
let received = 0;
|
|
538
|
+
let currentSessionId = null;
|
|
430
539
|
let lastProgressSentAt = 0;
|
|
431
540
|
const progressIntervalMs = 120;
|
|
432
541
|
let writeQueue = Promise.resolve();
|
|
433
|
-
|
|
542
|
+
let watchdogTimer = null;
|
|
543
|
+
let activeConn = null;
|
|
544
|
+
const resetWatchdog = () => {
|
|
545
|
+
if (watchdogTimeoutMs <= 0) return;
|
|
546
|
+
if (watchdogTimer) {
|
|
547
|
+
clearTimeout(watchdogTimer);
|
|
548
|
+
}
|
|
549
|
+
watchdogTimer = setTimeout(() => {
|
|
550
|
+
if (state === "transferring") {
|
|
551
|
+
safeError(new DropgateNetworkError("Connection timed out (no data received)."));
|
|
552
|
+
}
|
|
553
|
+
}, watchdogTimeoutMs);
|
|
554
|
+
};
|
|
555
|
+
const clearWatchdog = () => {
|
|
556
|
+
if (watchdogTimer) {
|
|
557
|
+
clearTimeout(watchdogTimer);
|
|
558
|
+
watchdogTimer = null;
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
const safeError = (err) => {
|
|
562
|
+
if (state === "closed" || state === "completed") return;
|
|
563
|
+
state = "closed";
|
|
564
|
+
onError?.(err);
|
|
565
|
+
cleanup();
|
|
566
|
+
};
|
|
567
|
+
const safeComplete = (completeData) => {
|
|
568
|
+
if (state !== "transferring") return;
|
|
569
|
+
state = "completed";
|
|
570
|
+
onComplete?.(completeData);
|
|
571
|
+
cleanup();
|
|
572
|
+
};
|
|
573
|
+
const cleanup = () => {
|
|
574
|
+
clearWatchdog();
|
|
575
|
+
if (typeof window !== "undefined") {
|
|
576
|
+
window.removeEventListener("beforeunload", handleUnload);
|
|
577
|
+
}
|
|
434
578
|
try {
|
|
435
579
|
peer.destroy();
|
|
436
580
|
} catch {
|
|
437
581
|
}
|
|
438
582
|
};
|
|
439
|
-
|
|
440
|
-
|
|
583
|
+
const handleUnload = () => {
|
|
584
|
+
try {
|
|
585
|
+
activeConn?.send({ t: "error", message: "Receiver closed the connection." });
|
|
586
|
+
} catch {
|
|
587
|
+
}
|
|
441
588
|
stop();
|
|
589
|
+
};
|
|
590
|
+
if (typeof window !== "undefined") {
|
|
591
|
+
window.addEventListener("beforeunload", handleUnload);
|
|
592
|
+
}
|
|
593
|
+
const stop = () => {
|
|
594
|
+
if (state === "closed") return;
|
|
595
|
+
state = "closed";
|
|
596
|
+
cleanup();
|
|
597
|
+
};
|
|
598
|
+
peer.on("error", (err) => {
|
|
599
|
+
safeError(err);
|
|
442
600
|
});
|
|
443
601
|
peer.on("open", () => {
|
|
602
|
+
state = "connecting";
|
|
444
603
|
const conn = peer.connect(normalizedCode, { reliable: true });
|
|
604
|
+
activeConn = conn;
|
|
445
605
|
conn.on("open", () => {
|
|
606
|
+
state = "negotiating";
|
|
446
607
|
onStatus?.({ phase: "connected", message: "Waiting for file details..." });
|
|
447
608
|
});
|
|
448
609
|
conn.on("data", async (data) => {
|
|
449
610
|
try {
|
|
611
|
+
resetWatchdog();
|
|
450
612
|
if (data && typeof data === "object" && !(data instanceof ArrayBuffer) && !ArrayBuffer.isView(data)) {
|
|
451
613
|
const msg = data;
|
|
452
614
|
if (msg.t === "meta") {
|
|
615
|
+
if (currentSessionId && msg.sessionId && msg.sessionId !== currentSessionId) {
|
|
616
|
+
try {
|
|
617
|
+
conn.send({ t: "error", message: "Busy with another session." });
|
|
618
|
+
} catch {
|
|
619
|
+
}
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
if (msg.sessionId) {
|
|
623
|
+
currentSessionId = msg.sessionId;
|
|
624
|
+
}
|
|
453
625
|
const name = String(msg.name || "file");
|
|
454
626
|
total = Number(msg.size) || 0;
|
|
455
627
|
received = 0;
|
|
456
628
|
writeQueue = Promise.resolve();
|
|
457
|
-
|
|
458
|
-
|
|
629
|
+
const sendReady = () => {
|
|
630
|
+
state = "transferring";
|
|
631
|
+
resetWatchdog();
|
|
632
|
+
try {
|
|
633
|
+
conn.send({ t: "ready" });
|
|
634
|
+
} catch {
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
if (autoReady) {
|
|
638
|
+
onMeta?.({ name, total });
|
|
639
|
+
onProgress?.({ processedBytes: received, totalBytes: total, percent: 0 });
|
|
640
|
+
sendReady();
|
|
641
|
+
} else {
|
|
642
|
+
onMeta?.({ name, total, sendReady });
|
|
643
|
+
onProgress?.({ processedBytes: received, totalBytes: total, percent: 0 });
|
|
644
|
+
}
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (msg.t === "ping") {
|
|
459
648
|
try {
|
|
460
|
-
conn.send({ t: "
|
|
649
|
+
conn.send({ t: "pong" });
|
|
461
650
|
} catch {
|
|
462
651
|
}
|
|
463
652
|
return;
|
|
464
653
|
}
|
|
465
654
|
if (msg.t === "end") {
|
|
655
|
+
clearWatchdog();
|
|
466
656
|
await writeQueue;
|
|
467
657
|
if (total && received < total) {
|
|
468
658
|
const err = new DropgateNetworkError(
|
|
@@ -474,11 +664,11 @@ async function startP2PReceive(opts) {
|
|
|
474
664
|
}
|
|
475
665
|
throw err;
|
|
476
666
|
}
|
|
477
|
-
onComplete?.({ received, total });
|
|
478
667
|
try {
|
|
479
668
|
conn.send({ t: "ack", phase: "end", received, total });
|
|
480
669
|
} catch {
|
|
481
670
|
}
|
|
671
|
+
safeComplete({ received, total });
|
|
482
672
|
return;
|
|
483
673
|
}
|
|
484
674
|
if (msg.t === "error") {
|
|
@@ -505,7 +695,7 @@ async function startP2PReceive(opts) {
|
|
|
505
695
|
}
|
|
506
696
|
received += buf.byteLength;
|
|
507
697
|
const percent = total ? Math.min(100, received / total * 100) : 0;
|
|
508
|
-
onProgress?.({ received, total, percent });
|
|
698
|
+
onProgress?.({ processedBytes: received, totalBytes: total, percent });
|
|
509
699
|
const now = Date.now();
|
|
510
700
|
if (received === total || now - lastProgressSentAt >= progressIntervalMs) {
|
|
511
701
|
lastProgressSentAt = now;
|
|
@@ -522,21 +712,36 @@ async function startP2PReceive(opts) {
|
|
|
522
712
|
});
|
|
523
713
|
} catch {
|
|
524
714
|
}
|
|
525
|
-
|
|
526
|
-
stop();
|
|
715
|
+
safeError(err);
|
|
527
716
|
});
|
|
528
717
|
} catch (err) {
|
|
529
|
-
|
|
530
|
-
stop();
|
|
718
|
+
safeError(err);
|
|
531
719
|
}
|
|
532
720
|
});
|
|
533
721
|
conn.on("close", () => {
|
|
534
|
-
if (
|
|
722
|
+
if (state === "closed" || state === "completed") {
|
|
723
|
+
cleanup();
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (state === "transferring") {
|
|
727
|
+
safeError(new DropgateNetworkError("Sender disconnected during transfer."));
|
|
728
|
+
} else if (state === "negotiating") {
|
|
729
|
+
state = "closed";
|
|
730
|
+
cleanup();
|
|
535
731
|
onDisconnect?.();
|
|
732
|
+
} else {
|
|
733
|
+
safeError(new DropgateNetworkError("Sender disconnected before file details were received."));
|
|
536
734
|
}
|
|
537
735
|
});
|
|
538
736
|
});
|
|
539
|
-
return {
|
|
737
|
+
return {
|
|
738
|
+
peer,
|
|
739
|
+
stop,
|
|
740
|
+
getStatus: () => state,
|
|
741
|
+
getBytesReceived: () => received,
|
|
742
|
+
getTotalBytes: () => total,
|
|
743
|
+
getSessionId: () => currentSessionId
|
|
744
|
+
};
|
|
540
745
|
}
|
|
541
746
|
// Annotate the CommonJS export names for ESM import in node:
|
|
542
747
|
0 && (module.exports = {
|
|
@@ -546,6 +751,7 @@ async function startP2PReceive(opts) {
|
|
|
546
751
|
isLocalhostHostname,
|
|
547
752
|
isP2PCodeLike,
|
|
548
753
|
isSecureContextForP2P,
|
|
754
|
+
resolvePeerConfig,
|
|
549
755
|
startP2PReceive,
|
|
550
756
|
startP2PSend
|
|
551
757
|
});
|