@dropgate/core 2.0.0-beta.2 → 2.2.0-beta.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 +76 -15
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +639 -303
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +208 -121
- package/dist/index.d.ts +208 -121
- package/dist/index.js +637 -303
- package/dist/index.js.map +1 -1
- package/dist/p2p/index.cjs +310 -65
- package/dist/p2p/index.cjs.map +1 -1
- package/dist/p2p/index.d.cts +165 -92
- package/dist/p2p/index.d.ts +165 -92
- package/dist/p2p/index.js +309 -65
- 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,17 @@ 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,
|
|
212
|
+
onCancel
|
|
198
213
|
} = opts;
|
|
199
214
|
if (!file) {
|
|
200
215
|
throw new DropgateValidationError("File is missing.");
|
|
@@ -208,8 +223,10 @@ async function startP2PSend(opts) {
|
|
|
208
223
|
if (serverInfo && !p2pCaps?.enabled) {
|
|
209
224
|
throw new DropgateValidationError("Direct transfer is disabled on this server.");
|
|
210
225
|
}
|
|
211
|
-
const finalPath
|
|
212
|
-
|
|
226
|
+
const { path: finalPath, iceServers: finalIceServers } = resolvePeerConfig(
|
|
227
|
+
{ peerjsPath, iceServers },
|
|
228
|
+
p2pCaps
|
|
229
|
+
);
|
|
213
230
|
const peerOpts = buildPeerOptions({
|
|
214
231
|
host,
|
|
215
232
|
port,
|
|
@@ -226,18 +243,37 @@ async function startP2PSend(opts) {
|
|
|
226
243
|
buildPeer,
|
|
227
244
|
onCode
|
|
228
245
|
});
|
|
229
|
-
|
|
246
|
+
const sessionId = generateSessionId();
|
|
247
|
+
let state = "listening";
|
|
230
248
|
let activeConn = null;
|
|
231
|
-
let
|
|
232
|
-
let
|
|
249
|
+
let sentBytes = 0;
|
|
250
|
+
let heartbeatTimer = null;
|
|
233
251
|
const reportProgress = (data) => {
|
|
234
252
|
const safeTotal = Number.isFinite(data.total) && data.total > 0 ? data.total : file.size;
|
|
235
253
|
const safeReceived = Math.min(Number(data.received) || 0, safeTotal || 0);
|
|
236
254
|
const percent = safeTotal ? safeReceived / safeTotal * 100 : 0;
|
|
237
|
-
onProgress?.({
|
|
255
|
+
onProgress?.({ processedBytes: safeReceived, totalBytes: safeTotal, percent });
|
|
238
256
|
};
|
|
239
|
-
const
|
|
240
|
-
|
|
257
|
+
const safeError = (err) => {
|
|
258
|
+
if (state === "closed" || state === "completed" || state === "cancelled") return;
|
|
259
|
+
state = "closed";
|
|
260
|
+
onError?.(err);
|
|
261
|
+
cleanup();
|
|
262
|
+
};
|
|
263
|
+
const safeComplete = () => {
|
|
264
|
+
if (state !== "finishing") return;
|
|
265
|
+
state = "completed";
|
|
266
|
+
onComplete?.();
|
|
267
|
+
cleanup();
|
|
268
|
+
};
|
|
269
|
+
const cleanup = () => {
|
|
270
|
+
if (heartbeatTimer) {
|
|
271
|
+
clearInterval(heartbeatTimer);
|
|
272
|
+
heartbeatTimer = null;
|
|
273
|
+
}
|
|
274
|
+
if (typeof window !== "undefined") {
|
|
275
|
+
window.removeEventListener("beforeunload", handleUnload);
|
|
276
|
+
}
|
|
241
277
|
try {
|
|
242
278
|
activeConn?.close();
|
|
243
279
|
} catch {
|
|
@@ -247,21 +283,69 @@ async function startP2PSend(opts) {
|
|
|
247
283
|
} catch {
|
|
248
284
|
}
|
|
249
285
|
};
|
|
286
|
+
const handleUnload = () => {
|
|
287
|
+
try {
|
|
288
|
+
activeConn?.send({ t: "error", message: "Sender closed the connection." });
|
|
289
|
+
} catch {
|
|
290
|
+
}
|
|
291
|
+
stop();
|
|
292
|
+
};
|
|
293
|
+
if (typeof window !== "undefined") {
|
|
294
|
+
window.addEventListener("beforeunload", handleUnload);
|
|
295
|
+
}
|
|
296
|
+
const stop = () => {
|
|
297
|
+
if (state === "closed" || state === "cancelled") return;
|
|
298
|
+
const wasActive = state === "transferring" || state === "finishing";
|
|
299
|
+
state = "cancelled";
|
|
300
|
+
try {
|
|
301
|
+
if (activeConn && activeConn.open) {
|
|
302
|
+
activeConn.send({ t: "cancelled", message: "Sender cancelled the transfer." });
|
|
303
|
+
}
|
|
304
|
+
} catch {
|
|
305
|
+
}
|
|
306
|
+
if (wasActive && onCancel) {
|
|
307
|
+
onCancel({ cancelledBy: "sender" });
|
|
308
|
+
}
|
|
309
|
+
cleanup();
|
|
310
|
+
};
|
|
311
|
+
const isStopped = () => state === "closed" || state === "cancelled";
|
|
250
312
|
peer.on("connection", (conn) => {
|
|
251
|
-
if (
|
|
313
|
+
if (state === "closed") return;
|
|
252
314
|
if (activeConn) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
315
|
+
const isOldConnOpen = activeConn.open !== false;
|
|
316
|
+
if (isOldConnOpen && state === "transferring") {
|
|
317
|
+
try {
|
|
318
|
+
conn.send({ t: "error", message: "Transfer already in progress." });
|
|
319
|
+
} catch {
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
conn.close();
|
|
323
|
+
} catch {
|
|
324
|
+
}
|
|
325
|
+
return;
|
|
326
|
+
} else if (!isOldConnOpen) {
|
|
327
|
+
try {
|
|
328
|
+
activeConn.close();
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
activeConn = null;
|
|
332
|
+
state = "listening";
|
|
333
|
+
sentBytes = 0;
|
|
334
|
+
} else {
|
|
335
|
+
try {
|
|
336
|
+
conn.send({ t: "error", message: "Another receiver is already connected." });
|
|
337
|
+
} catch {
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
conn.close();
|
|
341
|
+
} catch {
|
|
342
|
+
}
|
|
343
|
+
return;
|
|
260
344
|
}
|
|
261
|
-
return;
|
|
262
345
|
}
|
|
263
346
|
activeConn = conn;
|
|
264
|
-
|
|
347
|
+
state = "negotiating";
|
|
348
|
+
onStatus?.({ phase: "waiting", message: "Connected. Waiting for receiver to accept..." });
|
|
265
349
|
let readyResolve = null;
|
|
266
350
|
let ackResolve = null;
|
|
267
351
|
const readyPromise = new Promise((resolve) => {
|
|
@@ -277,6 +361,7 @@ async function startP2PSend(opts) {
|
|
|
277
361
|
const msg = data;
|
|
278
362
|
if (!msg.t) return;
|
|
279
363
|
if (msg.t === "ready") {
|
|
364
|
+
onStatus?.({ phase: "transferring", message: "Receiver accepted. Starting transfer..." });
|
|
280
365
|
readyResolve?.();
|
|
281
366
|
return;
|
|
282
367
|
}
|
|
@@ -288,22 +373,30 @@ async function startP2PSend(opts) {
|
|
|
288
373
|
ackResolve?.(msg);
|
|
289
374
|
return;
|
|
290
375
|
}
|
|
376
|
+
if (msg.t === "pong") {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
291
379
|
if (msg.t === "error") {
|
|
292
|
-
|
|
293
|
-
|
|
380
|
+
safeError(new DropgateNetworkError(msg.message || "Receiver reported an error."));
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (msg.t === "cancelled") {
|
|
384
|
+
if (state === "cancelled" || state === "closed" || state === "completed") return;
|
|
385
|
+
state = "cancelled";
|
|
386
|
+
onCancel?.({ cancelledBy: "receiver", message: msg.message });
|
|
387
|
+
cleanup();
|
|
294
388
|
}
|
|
295
389
|
});
|
|
296
390
|
conn.on("open", async () => {
|
|
297
391
|
try {
|
|
298
|
-
|
|
299
|
-
if (stopped) return;
|
|
392
|
+
if (isStopped()) return;
|
|
300
393
|
conn.send({
|
|
301
394
|
t: "meta",
|
|
395
|
+
sessionId,
|
|
302
396
|
name: file.name,
|
|
303
397
|
size: file.size,
|
|
304
398
|
mime: file.type || "application/octet-stream"
|
|
305
399
|
});
|
|
306
|
-
let sent = 0;
|
|
307
400
|
const total = file.size;
|
|
308
401
|
const dc = conn._dc;
|
|
309
402
|
if (dc && Number.isFinite(bufferLowWaterMark)) {
|
|
@@ -312,13 +405,26 @@ async function startP2PSend(opts) {
|
|
|
312
405
|
} catch {
|
|
313
406
|
}
|
|
314
407
|
}
|
|
315
|
-
await
|
|
408
|
+
await readyPromise;
|
|
409
|
+
if (isStopped()) return;
|
|
410
|
+
if (heartbeatIntervalMs > 0) {
|
|
411
|
+
heartbeatTimer = setInterval(() => {
|
|
412
|
+
if (state === "transferring" || state === "finishing") {
|
|
413
|
+
try {
|
|
414
|
+
conn.send({ t: "ping" });
|
|
415
|
+
} catch {
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}, heartbeatIntervalMs);
|
|
419
|
+
}
|
|
420
|
+
state = "transferring";
|
|
316
421
|
for (let offset = 0; offset < total; offset += chunkSize) {
|
|
317
|
-
if (
|
|
422
|
+
if (isStopped()) return;
|
|
318
423
|
const slice = file.slice(offset, offset + chunkSize);
|
|
319
424
|
const buf = await slice.arrayBuffer();
|
|
425
|
+
if (isStopped()) return;
|
|
320
426
|
conn.send(buf);
|
|
321
|
-
|
|
427
|
+
sentBytes += buf.byteLength;
|
|
322
428
|
if (dc) {
|
|
323
429
|
while (dc.bufferedAmount > bufferHighWaterMark) {
|
|
324
430
|
await new Promise((resolve) => {
|
|
@@ -338,13 +444,15 @@ async function startP2PSend(opts) {
|
|
|
338
444
|
}
|
|
339
445
|
}
|
|
340
446
|
}
|
|
341
|
-
if (
|
|
447
|
+
if (isStopped()) return;
|
|
448
|
+
state = "finishing";
|
|
342
449
|
conn.send({ t: "end" });
|
|
343
450
|
const ackTimeoutMs = Number.isFinite(endAckTimeoutMs) ? Math.max(endAckTimeoutMs, Math.ceil(file.size / (1024 * 1024)) * 1e3) : null;
|
|
344
451
|
const ackResult = await Promise.race([
|
|
345
452
|
ackPromise,
|
|
346
453
|
sleep(ackTimeoutMs || 15e3).catch(() => null)
|
|
347
454
|
]);
|
|
455
|
+
if (isStopped()) return;
|
|
348
456
|
if (!ackResult || typeof ackResult !== "object") {
|
|
349
457
|
throw new DropgateNetworkError("Receiver did not confirm completion.");
|
|
350
458
|
}
|
|
@@ -355,29 +463,43 @@ async function startP2PSend(opts) {
|
|
|
355
463
|
throw new DropgateNetworkError("Receiver reported an incomplete transfer.");
|
|
356
464
|
}
|
|
357
465
|
reportProgress({ received: ackReceived || ackTotal, total: ackTotal });
|
|
358
|
-
|
|
359
|
-
transferActive = false;
|
|
360
|
-
onComplete?.();
|
|
361
|
-
stop();
|
|
466
|
+
safeComplete();
|
|
362
467
|
} catch (err) {
|
|
363
|
-
|
|
364
|
-
stop();
|
|
468
|
+
safeError(err);
|
|
365
469
|
}
|
|
366
470
|
});
|
|
367
471
|
conn.on("error", (err) => {
|
|
368
|
-
|
|
369
|
-
stop();
|
|
472
|
+
safeError(err);
|
|
370
473
|
});
|
|
371
474
|
conn.on("close", () => {
|
|
372
|
-
if (
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
475
|
+
if (state === "closed" || state === "completed" || state === "cancelled") {
|
|
476
|
+
cleanup();
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (state === "transferring" || state === "finishing") {
|
|
480
|
+
state = "cancelled";
|
|
481
|
+
onCancel?.({ cancelledBy: "receiver" });
|
|
482
|
+
cleanup();
|
|
483
|
+
} else {
|
|
484
|
+
activeConn = null;
|
|
485
|
+
state = "listening";
|
|
486
|
+
sentBytes = 0;
|
|
487
|
+
onDisconnect?.();
|
|
376
488
|
}
|
|
377
|
-
stop();
|
|
378
489
|
});
|
|
379
490
|
});
|
|
380
|
-
return {
|
|
491
|
+
return {
|
|
492
|
+
peer,
|
|
493
|
+
code,
|
|
494
|
+
sessionId,
|
|
495
|
+
stop,
|
|
496
|
+
getStatus: () => state,
|
|
497
|
+
getBytesSent: () => sentBytes,
|
|
498
|
+
getConnectedPeerId: () => {
|
|
499
|
+
if (!activeConn) return null;
|
|
500
|
+
return activeConn.peer || null;
|
|
501
|
+
}
|
|
502
|
+
};
|
|
381
503
|
}
|
|
382
504
|
|
|
383
505
|
// src/p2p/receive.ts
|
|
@@ -391,13 +513,16 @@ async function startP2PReceive(opts) {
|
|
|
391
513
|
peerjsPath,
|
|
392
514
|
secure = false,
|
|
393
515
|
iceServers,
|
|
516
|
+
autoReady = true,
|
|
517
|
+
watchdogTimeoutMs = 15e3,
|
|
394
518
|
onStatus,
|
|
395
519
|
onMeta,
|
|
396
520
|
onData,
|
|
397
521
|
onProgress,
|
|
398
522
|
onComplete,
|
|
399
523
|
onError,
|
|
400
|
-
onDisconnect
|
|
524
|
+
onDisconnect,
|
|
525
|
+
onCancel
|
|
401
526
|
} = opts;
|
|
402
527
|
if (!code) {
|
|
403
528
|
throw new DropgateValidationError("No sharing code was provided.");
|
|
@@ -415,8 +540,10 @@ async function startP2PReceive(opts) {
|
|
|
415
540
|
if (!isP2PCodeLike(normalizedCode)) {
|
|
416
541
|
throw new DropgateValidationError("Invalid direct transfer code.");
|
|
417
542
|
}
|
|
418
|
-
const finalPath
|
|
419
|
-
|
|
543
|
+
const { path: finalPath, iceServers: finalIceServers } = resolvePeerConfig(
|
|
544
|
+
{ peerjsPath, iceServers },
|
|
545
|
+
p2pCaps
|
|
546
|
+
);
|
|
420
547
|
const peerOpts = buildPeerOptions({
|
|
421
548
|
host,
|
|
422
549
|
port,
|
|
@@ -425,44 +552,137 @@ async function startP2PReceive(opts) {
|
|
|
425
552
|
iceServers: finalIceServers
|
|
426
553
|
});
|
|
427
554
|
const peer = new Peer(void 0, peerOpts);
|
|
555
|
+
let state = "initializing";
|
|
428
556
|
let total = 0;
|
|
429
557
|
let received = 0;
|
|
558
|
+
let currentSessionId = null;
|
|
430
559
|
let lastProgressSentAt = 0;
|
|
431
560
|
const progressIntervalMs = 120;
|
|
432
561
|
let writeQueue = Promise.resolve();
|
|
433
|
-
|
|
562
|
+
let watchdogTimer = null;
|
|
563
|
+
let activeConn = null;
|
|
564
|
+
const resetWatchdog = () => {
|
|
565
|
+
if (watchdogTimeoutMs <= 0) return;
|
|
566
|
+
if (watchdogTimer) {
|
|
567
|
+
clearTimeout(watchdogTimer);
|
|
568
|
+
}
|
|
569
|
+
watchdogTimer = setTimeout(() => {
|
|
570
|
+
if (state === "transferring") {
|
|
571
|
+
safeError(new DropgateNetworkError("Connection timed out (no data received)."));
|
|
572
|
+
}
|
|
573
|
+
}, watchdogTimeoutMs);
|
|
574
|
+
};
|
|
575
|
+
const clearWatchdog = () => {
|
|
576
|
+
if (watchdogTimer) {
|
|
577
|
+
clearTimeout(watchdogTimer);
|
|
578
|
+
watchdogTimer = null;
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
const safeError = (err) => {
|
|
582
|
+
if (state === "closed" || state === "completed" || state === "cancelled") return;
|
|
583
|
+
state = "closed";
|
|
584
|
+
onError?.(err);
|
|
585
|
+
cleanup();
|
|
586
|
+
};
|
|
587
|
+
const safeComplete = (completeData) => {
|
|
588
|
+
if (state !== "transferring") return;
|
|
589
|
+
state = "completed";
|
|
590
|
+
onComplete?.(completeData);
|
|
591
|
+
cleanup();
|
|
592
|
+
};
|
|
593
|
+
const cleanup = () => {
|
|
594
|
+
clearWatchdog();
|
|
595
|
+
if (typeof window !== "undefined") {
|
|
596
|
+
window.removeEventListener("beforeunload", handleUnload);
|
|
597
|
+
}
|
|
434
598
|
try {
|
|
435
599
|
peer.destroy();
|
|
436
600
|
} catch {
|
|
437
601
|
}
|
|
438
602
|
};
|
|
439
|
-
|
|
440
|
-
|
|
603
|
+
const handleUnload = () => {
|
|
604
|
+
try {
|
|
605
|
+
activeConn?.send({ t: "error", message: "Receiver closed the connection." });
|
|
606
|
+
} catch {
|
|
607
|
+
}
|
|
441
608
|
stop();
|
|
609
|
+
};
|
|
610
|
+
if (typeof window !== "undefined") {
|
|
611
|
+
window.addEventListener("beforeunload", handleUnload);
|
|
612
|
+
}
|
|
613
|
+
const stop = () => {
|
|
614
|
+
if (state === "closed" || state === "cancelled") return;
|
|
615
|
+
const wasActive = state === "transferring";
|
|
616
|
+
state = "cancelled";
|
|
617
|
+
try {
|
|
618
|
+
if (activeConn && activeConn.open) {
|
|
619
|
+
activeConn.send({ t: "cancelled", message: "Receiver cancelled the transfer." });
|
|
620
|
+
}
|
|
621
|
+
} catch {
|
|
622
|
+
}
|
|
623
|
+
if (wasActive && onCancel) {
|
|
624
|
+
onCancel({ cancelledBy: "receiver" });
|
|
625
|
+
}
|
|
626
|
+
cleanup();
|
|
627
|
+
};
|
|
628
|
+
peer.on("error", (err) => {
|
|
629
|
+
safeError(err);
|
|
442
630
|
});
|
|
443
631
|
peer.on("open", () => {
|
|
632
|
+
state = "connecting";
|
|
444
633
|
const conn = peer.connect(normalizedCode, { reliable: true });
|
|
634
|
+
activeConn = conn;
|
|
445
635
|
conn.on("open", () => {
|
|
636
|
+
state = "negotiating";
|
|
446
637
|
onStatus?.({ phase: "connected", message: "Waiting for file details..." });
|
|
447
638
|
});
|
|
448
639
|
conn.on("data", async (data) => {
|
|
449
640
|
try {
|
|
641
|
+
resetWatchdog();
|
|
450
642
|
if (data && typeof data === "object" && !(data instanceof ArrayBuffer) && !ArrayBuffer.isView(data)) {
|
|
451
643
|
const msg = data;
|
|
452
644
|
if (msg.t === "meta") {
|
|
645
|
+
if (currentSessionId && msg.sessionId && msg.sessionId !== currentSessionId) {
|
|
646
|
+
try {
|
|
647
|
+
conn.send({ t: "error", message: "Busy with another session." });
|
|
648
|
+
} catch {
|
|
649
|
+
}
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
if (msg.sessionId) {
|
|
653
|
+
currentSessionId = msg.sessionId;
|
|
654
|
+
}
|
|
453
655
|
const name = String(msg.name || "file");
|
|
454
656
|
total = Number(msg.size) || 0;
|
|
455
657
|
received = 0;
|
|
456
658
|
writeQueue = Promise.resolve();
|
|
457
|
-
|
|
458
|
-
|
|
659
|
+
const sendReady = () => {
|
|
660
|
+
state = "transferring";
|
|
661
|
+
resetWatchdog();
|
|
662
|
+
try {
|
|
663
|
+
conn.send({ t: "ready" });
|
|
664
|
+
} catch {
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
if (autoReady) {
|
|
668
|
+
onMeta?.({ name, total });
|
|
669
|
+
onProgress?.({ processedBytes: received, totalBytes: total, percent: 0 });
|
|
670
|
+
sendReady();
|
|
671
|
+
} else {
|
|
672
|
+
onMeta?.({ name, total, sendReady });
|
|
673
|
+
onProgress?.({ processedBytes: received, totalBytes: total, percent: 0 });
|
|
674
|
+
}
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
if (msg.t === "ping") {
|
|
459
678
|
try {
|
|
460
|
-
conn.send({ t: "
|
|
679
|
+
conn.send({ t: "pong" });
|
|
461
680
|
} catch {
|
|
462
681
|
}
|
|
463
682
|
return;
|
|
464
683
|
}
|
|
465
684
|
if (msg.t === "end") {
|
|
685
|
+
clearWatchdog();
|
|
466
686
|
await writeQueue;
|
|
467
687
|
if (total && received < total) {
|
|
468
688
|
const err = new DropgateNetworkError(
|
|
@@ -474,16 +694,23 @@ async function startP2PReceive(opts) {
|
|
|
474
694
|
}
|
|
475
695
|
throw err;
|
|
476
696
|
}
|
|
477
|
-
onComplete?.({ received, total });
|
|
478
697
|
try {
|
|
479
698
|
conn.send({ t: "ack", phase: "end", received, total });
|
|
480
699
|
} catch {
|
|
481
700
|
}
|
|
701
|
+
safeComplete({ received, total });
|
|
482
702
|
return;
|
|
483
703
|
}
|
|
484
704
|
if (msg.t === "error") {
|
|
485
705
|
throw new DropgateNetworkError(msg.message || "Sender reported an error.");
|
|
486
706
|
}
|
|
707
|
+
if (msg.t === "cancelled") {
|
|
708
|
+
if (state === "cancelled" || state === "closed" || state === "completed") return;
|
|
709
|
+
state = "cancelled";
|
|
710
|
+
onCancel?.({ cancelledBy: "sender", message: msg.message });
|
|
711
|
+
cleanup();
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
487
714
|
return;
|
|
488
715
|
}
|
|
489
716
|
let bufPromise;
|
|
@@ -505,7 +732,7 @@ async function startP2PReceive(opts) {
|
|
|
505
732
|
}
|
|
506
733
|
received += buf.byteLength;
|
|
507
734
|
const percent = total ? Math.min(100, received / total * 100) : 0;
|
|
508
|
-
onProgress?.({ received, total, percent });
|
|
735
|
+
onProgress?.({ processedBytes: received, totalBytes: total, percent });
|
|
509
736
|
const now = Date.now();
|
|
510
737
|
if (received === total || now - lastProgressSentAt >= progressIntervalMs) {
|
|
511
738
|
lastProgressSentAt = now;
|
|
@@ -522,21 +749,38 @@ async function startP2PReceive(opts) {
|
|
|
522
749
|
});
|
|
523
750
|
} catch {
|
|
524
751
|
}
|
|
525
|
-
|
|
526
|
-
stop();
|
|
752
|
+
safeError(err);
|
|
527
753
|
});
|
|
528
754
|
} catch (err) {
|
|
529
|
-
|
|
530
|
-
stop();
|
|
755
|
+
safeError(err);
|
|
531
756
|
}
|
|
532
757
|
});
|
|
533
758
|
conn.on("close", () => {
|
|
534
|
-
if (
|
|
759
|
+
if (state === "closed" || state === "completed" || state === "cancelled") {
|
|
760
|
+
cleanup();
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
if (state === "transferring") {
|
|
764
|
+
state = "cancelled";
|
|
765
|
+
onCancel?.({ cancelledBy: "sender" });
|
|
766
|
+
cleanup();
|
|
767
|
+
} else if (state === "negotiating") {
|
|
768
|
+
state = "closed";
|
|
769
|
+
cleanup();
|
|
535
770
|
onDisconnect?.();
|
|
771
|
+
} else {
|
|
772
|
+
safeError(new DropgateNetworkError("Sender disconnected before file details were received."));
|
|
536
773
|
}
|
|
537
774
|
});
|
|
538
775
|
});
|
|
539
|
-
return {
|
|
776
|
+
return {
|
|
777
|
+
peer,
|
|
778
|
+
stop,
|
|
779
|
+
getStatus: () => state,
|
|
780
|
+
getBytesReceived: () => received,
|
|
781
|
+
getTotalBytes: () => total,
|
|
782
|
+
getSessionId: () => currentSessionId
|
|
783
|
+
};
|
|
540
784
|
}
|
|
541
785
|
// Annotate the CommonJS export names for ESM import in node:
|
|
542
786
|
0 && (module.exports = {
|
|
@@ -546,6 +790,7 @@ async function startP2PReceive(opts) {
|
|
|
546
790
|
isLocalhostHostname,
|
|
547
791
|
isP2PCodeLike,
|
|
548
792
|
isSecureContextForP2P,
|
|
793
|
+
resolvePeerConfig,
|
|
549
794
|
startP2PReceive,
|
|
550
795
|
startP2PSend
|
|
551
796
|
});
|