@dropgate/core 2.0.0-beta.2 → 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/index.js
CHANGED
|
@@ -309,6 +309,37 @@ function estimateTotalUploadSizeBytes(fileSizeBytes, totalChunks, isEncrypted) {
|
|
|
309
309
|
if (!isEncrypted) return base;
|
|
310
310
|
return base + (Number(totalChunks) || 0) * ENCRYPTION_OVERHEAD_PER_CHUNK;
|
|
311
311
|
}
|
|
312
|
+
async function getServerInfo(opts) {
|
|
313
|
+
const { host, port, secure, timeoutMs = 5e3, signal, fetchFn: customFetch } = opts;
|
|
314
|
+
const fetchFn = customFetch || getDefaultFetch();
|
|
315
|
+
if (!fetchFn) {
|
|
316
|
+
throw new DropgateValidationError("No fetch() implementation found.");
|
|
317
|
+
}
|
|
318
|
+
const baseUrl = buildBaseUrl({ host, port, secure });
|
|
319
|
+
try {
|
|
320
|
+
const { res, json } = await fetchJson(
|
|
321
|
+
fetchFn,
|
|
322
|
+
`${baseUrl}/api/info`,
|
|
323
|
+
{
|
|
324
|
+
method: "GET",
|
|
325
|
+
timeoutMs,
|
|
326
|
+
signal,
|
|
327
|
+
headers: { Accept: "application/json" }
|
|
328
|
+
}
|
|
329
|
+
);
|
|
330
|
+
if (res.ok && json && typeof json === "object" && "version" in json) {
|
|
331
|
+
return { baseUrl, serverInfo: json };
|
|
332
|
+
}
|
|
333
|
+
throw new DropgateProtocolError(
|
|
334
|
+
`Server info request failed (status ${res.status}).`
|
|
335
|
+
);
|
|
336
|
+
} catch (err) {
|
|
337
|
+
if (err instanceof DropgateError) throw err;
|
|
338
|
+
throw new DropgateNetworkError("Could not reach server /api/info.", {
|
|
339
|
+
cause: err
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
312
343
|
var DropgateClient = class {
|
|
313
344
|
/**
|
|
314
345
|
* Create a new DropgateClient instance.
|
|
@@ -336,40 +367,6 @@ var DropgateClient = class {
|
|
|
336
367
|
this.base64 = opts.base64 || getDefaultBase64();
|
|
337
368
|
this.logger = opts.logger || null;
|
|
338
369
|
}
|
|
339
|
-
/**
|
|
340
|
-
* Fetch server information from the /api/info endpoint.
|
|
341
|
-
* @param opts - Server target and request options.
|
|
342
|
-
* @returns The server base URL and server info object.
|
|
343
|
-
* @throws {DropgateNetworkError} If the server cannot be reached.
|
|
344
|
-
* @throws {DropgateProtocolError} If the server returns an invalid response.
|
|
345
|
-
*/
|
|
346
|
-
async getServerInfo(opts) {
|
|
347
|
-
const { host, port, secure, timeoutMs = 5e3, signal } = opts;
|
|
348
|
-
const baseUrl = buildBaseUrl({ host, port, secure });
|
|
349
|
-
try {
|
|
350
|
-
const { res, json } = await fetchJson(
|
|
351
|
-
this.fetchFn,
|
|
352
|
-
`${baseUrl}/api/info`,
|
|
353
|
-
{
|
|
354
|
-
method: "GET",
|
|
355
|
-
timeoutMs,
|
|
356
|
-
signal,
|
|
357
|
-
headers: { Accept: "application/json" }
|
|
358
|
-
}
|
|
359
|
-
);
|
|
360
|
-
if (res.ok && json && typeof json === "object" && "version" in json) {
|
|
361
|
-
return { baseUrl, serverInfo: json };
|
|
362
|
-
}
|
|
363
|
-
throw new DropgateProtocolError(
|
|
364
|
-
`Server info request failed (status ${res.status}).`
|
|
365
|
-
);
|
|
366
|
-
} catch (err) {
|
|
367
|
-
if (err instanceof DropgateError) throw err;
|
|
368
|
-
throw new DropgateNetworkError("Could not reach server /api/info.", {
|
|
369
|
-
cause: err
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
370
|
/**
|
|
374
371
|
* Resolve a user-entered sharing code or URL via the server.
|
|
375
372
|
* @param value - The sharing code or URL to resolve.
|
|
@@ -378,8 +375,12 @@ var DropgateClient = class {
|
|
|
378
375
|
* @throws {DropgateProtocolError} If the share lookup fails.
|
|
379
376
|
*/
|
|
380
377
|
async resolveShareTarget(value, opts) {
|
|
381
|
-
const {
|
|
382
|
-
const
|
|
378
|
+
const { timeoutMs = 5e3, signal } = opts;
|
|
379
|
+
const compat = await this.checkCompatibility(opts);
|
|
380
|
+
if (!compat.compatible) {
|
|
381
|
+
throw new DropgateValidationError(compat.message);
|
|
382
|
+
}
|
|
383
|
+
const { baseUrl } = compat;
|
|
383
384
|
const { res, json } = await fetchJson(
|
|
384
385
|
this.fetchFn,
|
|
385
386
|
`${baseUrl}/api/resolve`,
|
|
@@ -402,10 +403,25 @@ var DropgateClient = class {
|
|
|
402
403
|
}
|
|
403
404
|
/**
|
|
404
405
|
* Check version compatibility between this client and a server.
|
|
405
|
-
*
|
|
406
|
-
* @
|
|
406
|
+
* Fetches server info internally using getServerInfo.
|
|
407
|
+
* @param opts - Server target and request options.
|
|
408
|
+
* @returns Compatibility result with status, message, and server info.
|
|
409
|
+
* @throws {DropgateNetworkError} If the server cannot be reached.
|
|
410
|
+
* @throws {DropgateProtocolError} If the server returns an invalid response.
|
|
407
411
|
*/
|
|
408
|
-
checkCompatibility(
|
|
412
|
+
async checkCompatibility(opts) {
|
|
413
|
+
let baseUrl;
|
|
414
|
+
let serverInfo;
|
|
415
|
+
try {
|
|
416
|
+
const result = await getServerInfo({ ...opts, fetchFn: this.fetchFn });
|
|
417
|
+
baseUrl = result.baseUrl;
|
|
418
|
+
serverInfo = result.serverInfo;
|
|
419
|
+
} catch (err) {
|
|
420
|
+
if (err instanceof DropgateError) throw err;
|
|
421
|
+
throw new DropgateNetworkError("Could not connect to the server.", {
|
|
422
|
+
cause: err
|
|
423
|
+
});
|
|
424
|
+
}
|
|
409
425
|
const serverVersion = String(serverInfo?.version || "0.0.0");
|
|
410
426
|
const clientVersion = String(this.clientVersion || "0.0.0");
|
|
411
427
|
const c = parseSemverMajorMinor(clientVersion);
|
|
@@ -415,7 +431,9 @@ var DropgateClient = class {
|
|
|
415
431
|
compatible: false,
|
|
416
432
|
clientVersion,
|
|
417
433
|
serverVersion,
|
|
418
|
-
message: `Incompatible versions. Client v${clientVersion}, Server v${serverVersion}${serverInfo?.name ? ` (${serverInfo.name})` : ""}
|
|
434
|
+
message: `Incompatible versions. Client v${clientVersion}, Server v${serverVersion}${serverInfo?.name ? ` (${serverInfo.name})` : ""}.`,
|
|
435
|
+
serverInfo,
|
|
436
|
+
baseUrl
|
|
419
437
|
};
|
|
420
438
|
}
|
|
421
439
|
if (c.minor > s.minor) {
|
|
@@ -423,14 +441,18 @@ var DropgateClient = class {
|
|
|
423
441
|
compatible: true,
|
|
424
442
|
clientVersion,
|
|
425
443
|
serverVersion,
|
|
426
|
-
message: `Client (v${clientVersion}) is newer than Server (v${serverVersion})${serverInfo?.name ? ` (${serverInfo.name})` : ""}. Some features may not work
|
|
444
|
+
message: `Client (v${clientVersion}) is newer than Server (v${serverVersion})${serverInfo?.name ? ` (${serverInfo.name})` : ""}. Some features may not work.`,
|
|
445
|
+
serverInfo,
|
|
446
|
+
baseUrl
|
|
427
447
|
};
|
|
428
448
|
}
|
|
429
449
|
return {
|
|
430
450
|
compatible: true,
|
|
431
451
|
clientVersion,
|
|
432
452
|
serverVersion,
|
|
433
|
-
message: `Server: v${serverVersion}, Client: v${clientVersion}${serverInfo?.name ? ` (${serverInfo.name})` : ""}
|
|
453
|
+
message: `Server: v${serverVersion}, Client: v${clientVersion}${serverInfo?.name ? ` (${serverInfo.name})` : ""}.`,
|
|
454
|
+
serverInfo,
|
|
455
|
+
baseUrl
|
|
434
456
|
};
|
|
435
457
|
}
|
|
436
458
|
/**
|
|
@@ -524,27 +546,17 @@ var DropgateClient = class {
|
|
|
524
546
|
"Web Crypto API not available (crypto.subtle)."
|
|
525
547
|
);
|
|
526
548
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
baseUrl = res.baseUrl;
|
|
539
|
-
serverInfo = res.serverInfo;
|
|
540
|
-
} catch (err) {
|
|
541
|
-
if (err instanceof DropgateError) throw err;
|
|
542
|
-
throw new DropgateNetworkError("Could not connect to the server.", {
|
|
543
|
-
cause: err
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
const compat = this.checkCompatibility(serverInfo);
|
|
547
|
-
progress({ phase: "server-compat", text: compat.message });
|
|
549
|
+
const fileSizeBytes = file.size;
|
|
550
|
+
progress({ phase: "server-info", text: "Checking server...", percent: 0, processedBytes: 0, totalBytes: fileSizeBytes });
|
|
551
|
+
const compat = await this.checkCompatibility({
|
|
552
|
+
host,
|
|
553
|
+
port,
|
|
554
|
+
secure,
|
|
555
|
+
timeoutMs: timeouts.serverInfoMs ?? 5e3,
|
|
556
|
+
signal
|
|
557
|
+
});
|
|
558
|
+
const { baseUrl, serverInfo } = compat;
|
|
559
|
+
progress({ phase: "server-compat", text: compat.message, percent: 0, processedBytes: 0, totalBytes: fileSizeBytes });
|
|
548
560
|
if (!compat.compatible) {
|
|
549
561
|
throw new DropgateValidationError(compat.message);
|
|
550
562
|
}
|
|
@@ -557,7 +569,7 @@ var DropgateClient = class {
|
|
|
557
569
|
let keyB64 = null;
|
|
558
570
|
let transmittedFilename = filename;
|
|
559
571
|
if (encrypt) {
|
|
560
|
-
progress({ phase: "crypto", text: "Generating encryption key..." });
|
|
572
|
+
progress({ phase: "crypto", text: "Generating encryption key...", percent: 0, processedBytes: 0, totalBytes: fileSizeBytes });
|
|
561
573
|
try {
|
|
562
574
|
cryptoKey = await generateAesGcmKey(this.cryptoObj);
|
|
563
575
|
keyB64 = await exportKeyBase64(this.cryptoObj, cryptoKey);
|
|
@@ -579,7 +591,7 @@ var DropgateClient = class {
|
|
|
579
591
|
totalChunks,
|
|
580
592
|
encrypt
|
|
581
593
|
);
|
|
582
|
-
progress({ phase: "init", text: "Reserving server storage..." });
|
|
594
|
+
progress({ phase: "init", text: "Reserving server storage...", percent: 0, processedBytes: 0, totalBytes: fileSizeBytes });
|
|
583
595
|
const initPayload = {
|
|
584
596
|
filename: transmittedFilename,
|
|
585
597
|
lifetime: lifetimeMs,
|
|
@@ -622,10 +634,13 @@ var DropgateClient = class {
|
|
|
622
634
|
const end = Math.min(start + this.chunkSize, file.size);
|
|
623
635
|
let chunkBlob = file.slice(start, end);
|
|
624
636
|
const percentComplete = i / totalChunks * 100;
|
|
637
|
+
const processedBytes = i * this.chunkSize;
|
|
625
638
|
progress({
|
|
626
639
|
phase: "chunk",
|
|
627
640
|
text: `Uploading chunk ${i + 1} of ${totalChunks}...`,
|
|
628
641
|
percent: percentComplete,
|
|
642
|
+
processedBytes,
|
|
643
|
+
totalBytes: fileSizeBytes,
|
|
629
644
|
chunkIndex: i,
|
|
630
645
|
totalChunks
|
|
631
646
|
});
|
|
@@ -665,11 +680,13 @@ var DropgateClient = class {
|
|
|
665
680
|
signal,
|
|
666
681
|
progress,
|
|
667
682
|
chunkIndex: i,
|
|
668
|
-
totalChunks
|
|
683
|
+
totalChunks,
|
|
684
|
+
chunkSize: this.chunkSize,
|
|
685
|
+
fileSizeBytes
|
|
669
686
|
}
|
|
670
687
|
);
|
|
671
688
|
}
|
|
672
|
-
progress({ phase: "complete", text: "Finalising upload...", percent: 100 });
|
|
689
|
+
progress({ phase: "complete", text: "Finalising upload...", percent: 100, processedBytes: fileSizeBytes, totalBytes: fileSizeBytes });
|
|
673
690
|
const completeRes = await fetchJson(
|
|
674
691
|
this.fetchFn,
|
|
675
692
|
`${baseUrl}/upload/complete`,
|
|
@@ -702,7 +719,7 @@ var DropgateClient = class {
|
|
|
702
719
|
if (encrypt && keyB64) {
|
|
703
720
|
downloadUrl += `#${keyB64}`;
|
|
704
721
|
}
|
|
705
|
-
progress({ phase: "done", text: "Upload successful!", percent: 100 });
|
|
722
|
+
progress({ phase: "done", text: "Upload successful!", percent: 100, processedBytes: fileSizeBytes, totalBytes: fileSizeBytes });
|
|
706
723
|
return {
|
|
707
724
|
downloadUrl,
|
|
708
725
|
fileId,
|
|
@@ -748,8 +765,20 @@ var DropgateClient = class {
|
|
|
748
765
|
if (!fileId || typeof fileId !== "string") {
|
|
749
766
|
throw new DropgateValidationError("File ID is required.");
|
|
750
767
|
}
|
|
751
|
-
|
|
752
|
-
|
|
768
|
+
progress({ phase: "server-info", text: "Checking server...", processedBytes: 0, totalBytes: 0, percent: 0 });
|
|
769
|
+
const compat = await this.checkCompatibility({
|
|
770
|
+
host,
|
|
771
|
+
port,
|
|
772
|
+
secure,
|
|
773
|
+
timeoutMs,
|
|
774
|
+
signal
|
|
775
|
+
});
|
|
776
|
+
const { baseUrl } = compat;
|
|
777
|
+
progress({ phase: "server-compat", text: compat.message, processedBytes: 0, totalBytes: 0, percent: 0 });
|
|
778
|
+
if (!compat.compatible) {
|
|
779
|
+
throw new DropgateValidationError(compat.message);
|
|
780
|
+
}
|
|
781
|
+
progress({ phase: "metadata", text: "Fetching file info...", processedBytes: 0, totalBytes: 0, percent: 0 });
|
|
753
782
|
const { signal: metaSignal, cleanup: metaCleanup } = makeAbortSignal(signal, timeoutMs);
|
|
754
783
|
let metadata;
|
|
755
784
|
try {
|
|
@@ -792,7 +821,7 @@ var DropgateClient = class {
|
|
|
792
821
|
if (!this.cryptoObj?.subtle) {
|
|
793
822
|
throw new DropgateValidationError("Web Crypto API not available for decryption.");
|
|
794
823
|
}
|
|
795
|
-
progress({ phase: "decrypting", text: "Preparing decryption...",
|
|
824
|
+
progress({ phase: "decrypting", text: "Preparing decryption...", processedBytes: 0, totalBytes: 0, percent: 0 });
|
|
796
825
|
try {
|
|
797
826
|
cryptoKey = await importKeyFromBase64(this.cryptoObj, keyB64, this.base64);
|
|
798
827
|
filename = await decryptFilenameFromBase64(
|
|
@@ -810,7 +839,7 @@ var DropgateClient = class {
|
|
|
810
839
|
} else {
|
|
811
840
|
filename = metadata.filename || "file";
|
|
812
841
|
}
|
|
813
|
-
progress({ phase: "downloading", text: "Starting download...", percent: 0,
|
|
842
|
+
progress({ phase: "downloading", text: "Starting download...", percent: 0, processedBytes: 0, totalBytes });
|
|
814
843
|
const { signal: downloadSignal, cleanup: downloadCleanup } = makeAbortSignal(signal, timeoutMs);
|
|
815
844
|
let receivedBytes = 0;
|
|
816
845
|
const dataChunks = [];
|
|
@@ -879,7 +908,7 @@ var DropgateClient = class {
|
|
|
879
908
|
phase: "decrypting",
|
|
880
909
|
text: `Downloading & decrypting... (${percent}%)`,
|
|
881
910
|
percent,
|
|
882
|
-
receivedBytes,
|
|
911
|
+
processedBytes: receivedBytes,
|
|
883
912
|
totalBytes
|
|
884
913
|
});
|
|
885
914
|
}
|
|
@@ -911,7 +940,7 @@ var DropgateClient = class {
|
|
|
911
940
|
phase: "downloading",
|
|
912
941
|
text: `Downloading... (${percent}%)`,
|
|
913
942
|
percent,
|
|
914
|
-
receivedBytes,
|
|
943
|
+
processedBytes: receivedBytes,
|
|
915
944
|
totalBytes
|
|
916
945
|
});
|
|
917
946
|
}
|
|
@@ -925,7 +954,7 @@ var DropgateClient = class {
|
|
|
925
954
|
} finally {
|
|
926
955
|
downloadCleanup();
|
|
927
956
|
}
|
|
928
|
-
progress({ phase: "complete", text: "Download complete!", percent: 100, receivedBytes, totalBytes });
|
|
957
|
+
progress({ phase: "complete", text: "Download complete!", percent: 100, processedBytes: receivedBytes, totalBytes });
|
|
929
958
|
let data;
|
|
930
959
|
if (collectData && dataChunks.length > 0) {
|
|
931
960
|
const totalLength = dataChunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
@@ -952,7 +981,9 @@ var DropgateClient = class {
|
|
|
952
981
|
signal,
|
|
953
982
|
progress,
|
|
954
983
|
chunkIndex,
|
|
955
|
-
totalChunks
|
|
984
|
+
totalChunks,
|
|
985
|
+
chunkSize,
|
|
986
|
+
fileSizeBytes
|
|
956
987
|
} = opts;
|
|
957
988
|
let attemptsLeft = retries;
|
|
958
989
|
let currentBackoff = backoffMs;
|
|
@@ -985,6 +1016,8 @@ var DropgateClient = class {
|
|
|
985
1016
|
throw err instanceof DropgateError ? err : new DropgateNetworkError("Chunk upload failed.", { cause: err });
|
|
986
1017
|
}
|
|
987
1018
|
const attemptNumber = maxRetries - attemptsLeft + 1;
|
|
1019
|
+
const processedBytes = chunkIndex * chunkSize;
|
|
1020
|
+
const percent = chunkIndex / totalChunks * 100;
|
|
988
1021
|
let remaining = currentBackoff;
|
|
989
1022
|
const tick = 100;
|
|
990
1023
|
while (remaining > 0) {
|
|
@@ -992,6 +1025,9 @@ var DropgateClient = class {
|
|
|
992
1025
|
progress({
|
|
993
1026
|
phase: "retry-wait",
|
|
994
1027
|
text: `Chunk upload failed. Retrying in ${secondsLeft}s... (${attemptNumber}/${maxRetries})`,
|
|
1028
|
+
percent,
|
|
1029
|
+
processedBytes,
|
|
1030
|
+
totalBytes: fileSizeBytes,
|
|
995
1031
|
chunkIndex,
|
|
996
1032
|
totalChunks
|
|
997
1033
|
});
|
|
@@ -1001,6 +1037,9 @@ var DropgateClient = class {
|
|
|
1001
1037
|
progress({
|
|
1002
1038
|
phase: "retry",
|
|
1003
1039
|
text: `Chunk upload failed. Retrying now... (${attemptNumber}/${maxRetries})`,
|
|
1040
|
+
percent,
|
|
1041
|
+
processedBytes,
|
|
1042
|
+
totalBytes: fileSizeBytes,
|
|
1004
1043
|
chunkIndex,
|
|
1005
1044
|
totalChunks
|
|
1006
1045
|
});
|
|
@@ -1023,11 +1062,11 @@ function isSecureContextForP2P(hostname, isSecureContext) {
|
|
|
1023
1062
|
return Boolean(isSecureContext) || isLocalhostHostname(hostname || "");
|
|
1024
1063
|
}
|
|
1025
1064
|
function generateP2PCode(cryptoObj) {
|
|
1026
|
-
const
|
|
1065
|
+
const crypto2 = cryptoObj || getDefaultCrypto();
|
|
1027
1066
|
const letters = "ABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
1028
|
-
if (
|
|
1067
|
+
if (crypto2) {
|
|
1029
1068
|
const randomBytes = new Uint8Array(8);
|
|
1030
|
-
|
|
1069
|
+
crypto2.getRandomValues(randomBytes);
|
|
1031
1070
|
let letterPart = "";
|
|
1032
1071
|
for (let i = 0; i < 4; i++) {
|
|
1033
1072
|
letterPart += letters[randomBytes[i] % letters.length];
|
|
@@ -1053,8 +1092,14 @@ function isP2PCodeLike(code) {
|
|
|
1053
1092
|
}
|
|
1054
1093
|
|
|
1055
1094
|
// src/p2p/helpers.ts
|
|
1056
|
-
function
|
|
1057
|
-
|
|
1095
|
+
function resolvePeerConfig(userConfig, serverCaps) {
|
|
1096
|
+
return {
|
|
1097
|
+
path: userConfig.peerjsPath ?? serverCaps?.peerjsPath ?? "/peerjs",
|
|
1098
|
+
iceServers: userConfig.iceServers ?? serverCaps?.iceServers ?? []
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
function buildPeerOptions(config = {}) {
|
|
1102
|
+
const { host, port, peerjsPath = "/peerjs", secure = false, iceServers = [] } = config;
|
|
1058
1103
|
const peerOpts = {
|
|
1059
1104
|
host,
|
|
1060
1105
|
path: peerjsPath,
|
|
@@ -1096,6 +1141,12 @@ async function createPeerWithRetries(opts) {
|
|
|
1096
1141
|
}
|
|
1097
1142
|
|
|
1098
1143
|
// src/p2p/send.ts
|
|
1144
|
+
function generateSessionId() {
|
|
1145
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
1146
|
+
return crypto.randomUUID();
|
|
1147
|
+
}
|
|
1148
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
1149
|
+
}
|
|
1099
1150
|
async function startP2PSend(opts) {
|
|
1100
1151
|
const {
|
|
1101
1152
|
file,
|
|
@@ -1110,15 +1161,16 @@ async function startP2PSend(opts) {
|
|
|
1110
1161
|
cryptoObj,
|
|
1111
1162
|
maxAttempts = 4,
|
|
1112
1163
|
chunkSize = 256 * 1024,
|
|
1113
|
-
readyTimeoutMs = 8e3,
|
|
1114
1164
|
endAckTimeoutMs = 15e3,
|
|
1115
1165
|
bufferHighWaterMark = 8 * 1024 * 1024,
|
|
1116
1166
|
bufferLowWaterMark = 2 * 1024 * 1024,
|
|
1167
|
+
heartbeatIntervalMs = 5e3,
|
|
1117
1168
|
onCode,
|
|
1118
1169
|
onStatus,
|
|
1119
1170
|
onProgress,
|
|
1120
1171
|
onComplete,
|
|
1121
|
-
onError
|
|
1172
|
+
onError,
|
|
1173
|
+
onDisconnect
|
|
1122
1174
|
} = opts;
|
|
1123
1175
|
if (!file) {
|
|
1124
1176
|
throw new DropgateValidationError("File is missing.");
|
|
@@ -1132,8 +1184,10 @@ async function startP2PSend(opts) {
|
|
|
1132
1184
|
if (serverInfo && !p2pCaps?.enabled) {
|
|
1133
1185
|
throw new DropgateValidationError("Direct transfer is disabled on this server.");
|
|
1134
1186
|
}
|
|
1135
|
-
const finalPath
|
|
1136
|
-
|
|
1187
|
+
const { path: finalPath, iceServers: finalIceServers } = resolvePeerConfig(
|
|
1188
|
+
{ peerjsPath, iceServers },
|
|
1189
|
+
p2pCaps
|
|
1190
|
+
);
|
|
1137
1191
|
const peerOpts = buildPeerOptions({
|
|
1138
1192
|
host,
|
|
1139
1193
|
port,
|
|
@@ -1150,18 +1204,37 @@ async function startP2PSend(opts) {
|
|
|
1150
1204
|
buildPeer,
|
|
1151
1205
|
onCode
|
|
1152
1206
|
});
|
|
1153
|
-
|
|
1207
|
+
const sessionId = generateSessionId();
|
|
1208
|
+
let state = "listening";
|
|
1154
1209
|
let activeConn = null;
|
|
1155
|
-
let
|
|
1156
|
-
let
|
|
1210
|
+
let sentBytes = 0;
|
|
1211
|
+
let heartbeatTimer = null;
|
|
1157
1212
|
const reportProgress = (data) => {
|
|
1158
1213
|
const safeTotal = Number.isFinite(data.total) && data.total > 0 ? data.total : file.size;
|
|
1159
1214
|
const safeReceived = Math.min(Number(data.received) || 0, safeTotal || 0);
|
|
1160
1215
|
const percent = safeTotal ? safeReceived / safeTotal * 100 : 0;
|
|
1161
|
-
onProgress?.({
|
|
1216
|
+
onProgress?.({ processedBytes: safeReceived, totalBytes: safeTotal, percent });
|
|
1162
1217
|
};
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1218
|
+
const safeError = (err) => {
|
|
1219
|
+
if (state === "closed" || state === "completed") return;
|
|
1220
|
+
state = "closed";
|
|
1221
|
+
onError?.(err);
|
|
1222
|
+
cleanup();
|
|
1223
|
+
};
|
|
1224
|
+
const safeComplete = () => {
|
|
1225
|
+
if (state !== "finishing") return;
|
|
1226
|
+
state = "completed";
|
|
1227
|
+
onComplete?.();
|
|
1228
|
+
cleanup();
|
|
1229
|
+
};
|
|
1230
|
+
const cleanup = () => {
|
|
1231
|
+
if (heartbeatTimer) {
|
|
1232
|
+
clearInterval(heartbeatTimer);
|
|
1233
|
+
heartbeatTimer = null;
|
|
1234
|
+
}
|
|
1235
|
+
if (typeof window !== "undefined") {
|
|
1236
|
+
window.removeEventListener("beforeunload", handleUnload);
|
|
1237
|
+
}
|
|
1165
1238
|
try {
|
|
1166
1239
|
activeConn?.close();
|
|
1167
1240
|
} catch {
|
|
@@ -1171,21 +1244,59 @@ async function startP2PSend(opts) {
|
|
|
1171
1244
|
} catch {
|
|
1172
1245
|
}
|
|
1173
1246
|
};
|
|
1247
|
+
const handleUnload = () => {
|
|
1248
|
+
try {
|
|
1249
|
+
activeConn?.send({ t: "error", message: "Sender closed the connection." });
|
|
1250
|
+
} catch {
|
|
1251
|
+
}
|
|
1252
|
+
stop();
|
|
1253
|
+
};
|
|
1254
|
+
if (typeof window !== "undefined") {
|
|
1255
|
+
window.addEventListener("beforeunload", handleUnload);
|
|
1256
|
+
}
|
|
1257
|
+
const stop = () => {
|
|
1258
|
+
if (state === "closed") return;
|
|
1259
|
+
state = "closed";
|
|
1260
|
+
cleanup();
|
|
1261
|
+
};
|
|
1262
|
+
const isStopped = () => state === "closed";
|
|
1174
1263
|
peer.on("connection", (conn) => {
|
|
1175
|
-
if (
|
|
1264
|
+
if (state === "closed") return;
|
|
1176
1265
|
if (activeConn) {
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1266
|
+
const isOldConnOpen = activeConn.open !== false;
|
|
1267
|
+
if (isOldConnOpen && state === "transferring") {
|
|
1268
|
+
try {
|
|
1269
|
+
conn.send({ t: "error", message: "Transfer already in progress." });
|
|
1270
|
+
} catch {
|
|
1271
|
+
}
|
|
1272
|
+
try {
|
|
1273
|
+
conn.close();
|
|
1274
|
+
} catch {
|
|
1275
|
+
}
|
|
1276
|
+
return;
|
|
1277
|
+
} else if (!isOldConnOpen) {
|
|
1278
|
+
try {
|
|
1279
|
+
activeConn.close();
|
|
1280
|
+
} catch {
|
|
1281
|
+
}
|
|
1282
|
+
activeConn = null;
|
|
1283
|
+
state = "listening";
|
|
1284
|
+
sentBytes = 0;
|
|
1285
|
+
} else {
|
|
1286
|
+
try {
|
|
1287
|
+
conn.send({ t: "error", message: "Another receiver is already connected." });
|
|
1288
|
+
} catch {
|
|
1289
|
+
}
|
|
1290
|
+
try {
|
|
1291
|
+
conn.close();
|
|
1292
|
+
} catch {
|
|
1293
|
+
}
|
|
1294
|
+
return;
|
|
1184
1295
|
}
|
|
1185
|
-
return;
|
|
1186
1296
|
}
|
|
1187
1297
|
activeConn = conn;
|
|
1188
|
-
|
|
1298
|
+
state = "negotiating";
|
|
1299
|
+
onStatus?.({ phase: "waiting", message: "Connected. Waiting for receiver to accept..." });
|
|
1189
1300
|
let readyResolve = null;
|
|
1190
1301
|
let ackResolve = null;
|
|
1191
1302
|
const readyPromise = new Promise((resolve) => {
|
|
@@ -1201,6 +1312,7 @@ async function startP2PSend(opts) {
|
|
|
1201
1312
|
const msg = data;
|
|
1202
1313
|
if (!msg.t) return;
|
|
1203
1314
|
if (msg.t === "ready") {
|
|
1315
|
+
onStatus?.({ phase: "transferring", message: "Receiver accepted. Starting transfer..." });
|
|
1204
1316
|
readyResolve?.();
|
|
1205
1317
|
return;
|
|
1206
1318
|
}
|
|
@@ -1212,22 +1324,23 @@ async function startP2PSend(opts) {
|
|
|
1212
1324
|
ackResolve?.(msg);
|
|
1213
1325
|
return;
|
|
1214
1326
|
}
|
|
1327
|
+
if (msg.t === "pong") {
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1215
1330
|
if (msg.t === "error") {
|
|
1216
|
-
|
|
1217
|
-
stop();
|
|
1331
|
+
safeError(new DropgateNetworkError(msg.message || "Receiver reported an error."));
|
|
1218
1332
|
}
|
|
1219
1333
|
});
|
|
1220
1334
|
conn.on("open", async () => {
|
|
1221
1335
|
try {
|
|
1222
|
-
|
|
1223
|
-
if (stopped) return;
|
|
1336
|
+
if (isStopped()) return;
|
|
1224
1337
|
conn.send({
|
|
1225
1338
|
t: "meta",
|
|
1339
|
+
sessionId,
|
|
1226
1340
|
name: file.name,
|
|
1227
1341
|
size: file.size,
|
|
1228
1342
|
mime: file.type || "application/octet-stream"
|
|
1229
1343
|
});
|
|
1230
|
-
let sent = 0;
|
|
1231
1344
|
const total = file.size;
|
|
1232
1345
|
const dc = conn._dc;
|
|
1233
1346
|
if (dc && Number.isFinite(bufferLowWaterMark)) {
|
|
@@ -1236,13 +1349,25 @@ async function startP2PSend(opts) {
|
|
|
1236
1349
|
} catch {
|
|
1237
1350
|
}
|
|
1238
1351
|
}
|
|
1239
|
-
await
|
|
1352
|
+
await readyPromise;
|
|
1353
|
+
if (isStopped()) return;
|
|
1354
|
+
if (heartbeatIntervalMs > 0) {
|
|
1355
|
+
heartbeatTimer = setInterval(() => {
|
|
1356
|
+
if (state === "transferring" || state === "finishing") {
|
|
1357
|
+
try {
|
|
1358
|
+
conn.send({ t: "ping" });
|
|
1359
|
+
} catch {
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}, heartbeatIntervalMs);
|
|
1363
|
+
}
|
|
1364
|
+
state = "transferring";
|
|
1240
1365
|
for (let offset = 0; offset < total; offset += chunkSize) {
|
|
1241
|
-
if (
|
|
1366
|
+
if (isStopped()) return;
|
|
1242
1367
|
const slice = file.slice(offset, offset + chunkSize);
|
|
1243
1368
|
const buf = await slice.arrayBuffer();
|
|
1244
1369
|
conn.send(buf);
|
|
1245
|
-
|
|
1370
|
+
sentBytes += buf.byteLength;
|
|
1246
1371
|
if (dc) {
|
|
1247
1372
|
while (dc.bufferedAmount > bufferHighWaterMark) {
|
|
1248
1373
|
await new Promise((resolve) => {
|
|
@@ -1262,13 +1387,15 @@ async function startP2PSend(opts) {
|
|
|
1262
1387
|
}
|
|
1263
1388
|
}
|
|
1264
1389
|
}
|
|
1265
|
-
if (
|
|
1390
|
+
if (isStopped()) return;
|
|
1391
|
+
state = "finishing";
|
|
1266
1392
|
conn.send({ t: "end" });
|
|
1267
1393
|
const ackTimeoutMs = Number.isFinite(endAckTimeoutMs) ? Math.max(endAckTimeoutMs, Math.ceil(file.size / (1024 * 1024)) * 1e3) : null;
|
|
1268
1394
|
const ackResult = await Promise.race([
|
|
1269
1395
|
ackPromise,
|
|
1270
1396
|
sleep(ackTimeoutMs || 15e3).catch(() => null)
|
|
1271
1397
|
]);
|
|
1398
|
+
if (isStopped()) return;
|
|
1272
1399
|
if (!ackResult || typeof ackResult !== "object") {
|
|
1273
1400
|
throw new DropgateNetworkError("Receiver did not confirm completion.");
|
|
1274
1401
|
}
|
|
@@ -1279,29 +1406,43 @@ async function startP2PSend(opts) {
|
|
|
1279
1406
|
throw new DropgateNetworkError("Receiver reported an incomplete transfer.");
|
|
1280
1407
|
}
|
|
1281
1408
|
reportProgress({ received: ackReceived || ackTotal, total: ackTotal });
|
|
1282
|
-
|
|
1283
|
-
transferActive = false;
|
|
1284
|
-
onComplete?.();
|
|
1285
|
-
stop();
|
|
1409
|
+
safeComplete();
|
|
1286
1410
|
} catch (err) {
|
|
1287
|
-
|
|
1288
|
-
stop();
|
|
1411
|
+
safeError(err);
|
|
1289
1412
|
}
|
|
1290
1413
|
});
|
|
1291
1414
|
conn.on("error", (err) => {
|
|
1292
|
-
|
|
1293
|
-
stop();
|
|
1415
|
+
safeError(err);
|
|
1294
1416
|
});
|
|
1295
1417
|
conn.on("close", () => {
|
|
1296
|
-
if (
|
|
1297
|
-
|
|
1418
|
+
if (state === "closed" || state === "completed") {
|
|
1419
|
+
cleanup();
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
if (state === "transferring" || state === "finishing") {
|
|
1423
|
+
safeError(
|
|
1298
1424
|
new DropgateNetworkError("Receiver disconnected before transfer completed.")
|
|
1299
1425
|
);
|
|
1426
|
+
} else {
|
|
1427
|
+
activeConn = null;
|
|
1428
|
+
state = "listening";
|
|
1429
|
+
sentBytes = 0;
|
|
1430
|
+
onDisconnect?.();
|
|
1300
1431
|
}
|
|
1301
|
-
stop();
|
|
1302
1432
|
});
|
|
1303
1433
|
});
|
|
1304
|
-
return {
|
|
1434
|
+
return {
|
|
1435
|
+
peer,
|
|
1436
|
+
code,
|
|
1437
|
+
sessionId,
|
|
1438
|
+
stop,
|
|
1439
|
+
getStatus: () => state,
|
|
1440
|
+
getBytesSent: () => sentBytes,
|
|
1441
|
+
getConnectedPeerId: () => {
|
|
1442
|
+
if (!activeConn) return null;
|
|
1443
|
+
return activeConn.peer || null;
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1305
1446
|
}
|
|
1306
1447
|
|
|
1307
1448
|
// src/p2p/receive.ts
|
|
@@ -1315,6 +1456,8 @@ async function startP2PReceive(opts) {
|
|
|
1315
1456
|
peerjsPath,
|
|
1316
1457
|
secure = false,
|
|
1317
1458
|
iceServers,
|
|
1459
|
+
autoReady = true,
|
|
1460
|
+
watchdogTimeoutMs = 15e3,
|
|
1318
1461
|
onStatus,
|
|
1319
1462
|
onMeta,
|
|
1320
1463
|
onData,
|
|
@@ -1339,8 +1482,10 @@ async function startP2PReceive(opts) {
|
|
|
1339
1482
|
if (!isP2PCodeLike(normalizedCode)) {
|
|
1340
1483
|
throw new DropgateValidationError("Invalid direct transfer code.");
|
|
1341
1484
|
}
|
|
1342
|
-
const finalPath
|
|
1343
|
-
|
|
1485
|
+
const { path: finalPath, iceServers: finalIceServers } = resolvePeerConfig(
|
|
1486
|
+
{ peerjsPath, iceServers },
|
|
1487
|
+
p2pCaps
|
|
1488
|
+
);
|
|
1344
1489
|
const peerOpts = buildPeerOptions({
|
|
1345
1490
|
host,
|
|
1346
1491
|
port,
|
|
@@ -1349,44 +1494,127 @@ async function startP2PReceive(opts) {
|
|
|
1349
1494
|
iceServers: finalIceServers
|
|
1350
1495
|
});
|
|
1351
1496
|
const peer = new Peer(void 0, peerOpts);
|
|
1497
|
+
let state = "initializing";
|
|
1352
1498
|
let total = 0;
|
|
1353
1499
|
let received = 0;
|
|
1500
|
+
let currentSessionId = null;
|
|
1354
1501
|
let lastProgressSentAt = 0;
|
|
1355
1502
|
const progressIntervalMs = 120;
|
|
1356
1503
|
let writeQueue = Promise.resolve();
|
|
1357
|
-
|
|
1504
|
+
let watchdogTimer = null;
|
|
1505
|
+
let activeConn = null;
|
|
1506
|
+
const resetWatchdog = () => {
|
|
1507
|
+
if (watchdogTimeoutMs <= 0) return;
|
|
1508
|
+
if (watchdogTimer) {
|
|
1509
|
+
clearTimeout(watchdogTimer);
|
|
1510
|
+
}
|
|
1511
|
+
watchdogTimer = setTimeout(() => {
|
|
1512
|
+
if (state === "transferring") {
|
|
1513
|
+
safeError(new DropgateNetworkError("Connection timed out (no data received)."));
|
|
1514
|
+
}
|
|
1515
|
+
}, watchdogTimeoutMs);
|
|
1516
|
+
};
|
|
1517
|
+
const clearWatchdog = () => {
|
|
1518
|
+
if (watchdogTimer) {
|
|
1519
|
+
clearTimeout(watchdogTimer);
|
|
1520
|
+
watchdogTimer = null;
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
const safeError = (err) => {
|
|
1524
|
+
if (state === "closed" || state === "completed") return;
|
|
1525
|
+
state = "closed";
|
|
1526
|
+
onError?.(err);
|
|
1527
|
+
cleanup();
|
|
1528
|
+
};
|
|
1529
|
+
const safeComplete = (completeData) => {
|
|
1530
|
+
if (state !== "transferring") return;
|
|
1531
|
+
state = "completed";
|
|
1532
|
+
onComplete?.(completeData);
|
|
1533
|
+
cleanup();
|
|
1534
|
+
};
|
|
1535
|
+
const cleanup = () => {
|
|
1536
|
+
clearWatchdog();
|
|
1537
|
+
if (typeof window !== "undefined") {
|
|
1538
|
+
window.removeEventListener("beforeunload", handleUnload);
|
|
1539
|
+
}
|
|
1358
1540
|
try {
|
|
1359
1541
|
peer.destroy();
|
|
1360
1542
|
} catch {
|
|
1361
1543
|
}
|
|
1362
1544
|
};
|
|
1363
|
-
|
|
1364
|
-
|
|
1545
|
+
const handleUnload = () => {
|
|
1546
|
+
try {
|
|
1547
|
+
activeConn?.send({ t: "error", message: "Receiver closed the connection." });
|
|
1548
|
+
} catch {
|
|
1549
|
+
}
|
|
1365
1550
|
stop();
|
|
1551
|
+
};
|
|
1552
|
+
if (typeof window !== "undefined") {
|
|
1553
|
+
window.addEventListener("beforeunload", handleUnload);
|
|
1554
|
+
}
|
|
1555
|
+
const stop = () => {
|
|
1556
|
+
if (state === "closed") return;
|
|
1557
|
+
state = "closed";
|
|
1558
|
+
cleanup();
|
|
1559
|
+
};
|
|
1560
|
+
peer.on("error", (err) => {
|
|
1561
|
+
safeError(err);
|
|
1366
1562
|
});
|
|
1367
1563
|
peer.on("open", () => {
|
|
1564
|
+
state = "connecting";
|
|
1368
1565
|
const conn = peer.connect(normalizedCode, { reliable: true });
|
|
1566
|
+
activeConn = conn;
|
|
1369
1567
|
conn.on("open", () => {
|
|
1568
|
+
state = "negotiating";
|
|
1370
1569
|
onStatus?.({ phase: "connected", message: "Waiting for file details..." });
|
|
1371
1570
|
});
|
|
1372
1571
|
conn.on("data", async (data) => {
|
|
1373
1572
|
try {
|
|
1573
|
+
resetWatchdog();
|
|
1374
1574
|
if (data && typeof data === "object" && !(data instanceof ArrayBuffer) && !ArrayBuffer.isView(data)) {
|
|
1375
1575
|
const msg = data;
|
|
1376
1576
|
if (msg.t === "meta") {
|
|
1577
|
+
if (currentSessionId && msg.sessionId && msg.sessionId !== currentSessionId) {
|
|
1578
|
+
try {
|
|
1579
|
+
conn.send({ t: "error", message: "Busy with another session." });
|
|
1580
|
+
} catch {
|
|
1581
|
+
}
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
if (msg.sessionId) {
|
|
1585
|
+
currentSessionId = msg.sessionId;
|
|
1586
|
+
}
|
|
1377
1587
|
const name = String(msg.name || "file");
|
|
1378
1588
|
total = Number(msg.size) || 0;
|
|
1379
1589
|
received = 0;
|
|
1380
1590
|
writeQueue = Promise.resolve();
|
|
1381
|
-
|
|
1382
|
-
|
|
1591
|
+
const sendReady = () => {
|
|
1592
|
+
state = "transferring";
|
|
1593
|
+
resetWatchdog();
|
|
1594
|
+
try {
|
|
1595
|
+
conn.send({ t: "ready" });
|
|
1596
|
+
} catch {
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
if (autoReady) {
|
|
1600
|
+
onMeta?.({ name, total });
|
|
1601
|
+
onProgress?.({ processedBytes: received, totalBytes: total, percent: 0 });
|
|
1602
|
+
sendReady();
|
|
1603
|
+
} else {
|
|
1604
|
+
onMeta?.({ name, total, sendReady });
|
|
1605
|
+
onProgress?.({ processedBytes: received, totalBytes: total, percent: 0 });
|
|
1606
|
+
}
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
if (msg.t === "ping") {
|
|
1383
1610
|
try {
|
|
1384
|
-
conn.send({ t: "
|
|
1611
|
+
conn.send({ t: "pong" });
|
|
1385
1612
|
} catch {
|
|
1386
1613
|
}
|
|
1387
1614
|
return;
|
|
1388
1615
|
}
|
|
1389
1616
|
if (msg.t === "end") {
|
|
1617
|
+
clearWatchdog();
|
|
1390
1618
|
await writeQueue;
|
|
1391
1619
|
if (total && received < total) {
|
|
1392
1620
|
const err = new DropgateNetworkError(
|
|
@@ -1398,11 +1626,11 @@ async function startP2PReceive(opts) {
|
|
|
1398
1626
|
}
|
|
1399
1627
|
throw err;
|
|
1400
1628
|
}
|
|
1401
|
-
onComplete?.({ received, total });
|
|
1402
1629
|
try {
|
|
1403
1630
|
conn.send({ t: "ack", phase: "end", received, total });
|
|
1404
1631
|
} catch {
|
|
1405
1632
|
}
|
|
1633
|
+
safeComplete({ received, total });
|
|
1406
1634
|
return;
|
|
1407
1635
|
}
|
|
1408
1636
|
if (msg.t === "error") {
|
|
@@ -1429,7 +1657,7 @@ async function startP2PReceive(opts) {
|
|
|
1429
1657
|
}
|
|
1430
1658
|
received += buf.byteLength;
|
|
1431
1659
|
const percent = total ? Math.min(100, received / total * 100) : 0;
|
|
1432
|
-
onProgress?.({ received, total, percent });
|
|
1660
|
+
onProgress?.({ processedBytes: received, totalBytes: total, percent });
|
|
1433
1661
|
const now = Date.now();
|
|
1434
1662
|
if (received === total || now - lastProgressSentAt >= progressIntervalMs) {
|
|
1435
1663
|
lastProgressSentAt = now;
|
|
@@ -1446,21 +1674,36 @@ async function startP2PReceive(opts) {
|
|
|
1446
1674
|
});
|
|
1447
1675
|
} catch {
|
|
1448
1676
|
}
|
|
1449
|
-
|
|
1450
|
-
stop();
|
|
1677
|
+
safeError(err);
|
|
1451
1678
|
});
|
|
1452
1679
|
} catch (err) {
|
|
1453
|
-
|
|
1454
|
-
stop();
|
|
1680
|
+
safeError(err);
|
|
1455
1681
|
}
|
|
1456
1682
|
});
|
|
1457
1683
|
conn.on("close", () => {
|
|
1458
|
-
if (
|
|
1684
|
+
if (state === "closed" || state === "completed") {
|
|
1685
|
+
cleanup();
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
if (state === "transferring") {
|
|
1689
|
+
safeError(new DropgateNetworkError("Sender disconnected during transfer."));
|
|
1690
|
+
} else if (state === "negotiating") {
|
|
1691
|
+
state = "closed";
|
|
1692
|
+
cleanup();
|
|
1459
1693
|
onDisconnect?.();
|
|
1694
|
+
} else {
|
|
1695
|
+
safeError(new DropgateNetworkError("Sender disconnected before file details were received."));
|
|
1460
1696
|
}
|
|
1461
1697
|
});
|
|
1462
1698
|
});
|
|
1463
|
-
return {
|
|
1699
|
+
return {
|
|
1700
|
+
peer,
|
|
1701
|
+
stop,
|
|
1702
|
+
getStatus: () => state,
|
|
1703
|
+
getBytesReceived: () => received,
|
|
1704
|
+
getTotalBytes: () => total,
|
|
1705
|
+
getSessionId: () => currentSessionId
|
|
1706
|
+
};
|
|
1464
1707
|
}
|
|
1465
1708
|
export {
|
|
1466
1709
|
AES_GCM_IV_BYTES,
|
|
@@ -1492,6 +1735,7 @@ export {
|
|
|
1492
1735
|
getDefaultBase64,
|
|
1493
1736
|
getDefaultCrypto,
|
|
1494
1737
|
getDefaultFetch,
|
|
1738
|
+
getServerInfo,
|
|
1495
1739
|
importKeyFromBase64,
|
|
1496
1740
|
isLocalhostHostname,
|
|
1497
1741
|
isP2PCodeLike,
|
|
@@ -1500,6 +1744,7 @@ export {
|
|
|
1500
1744
|
makeAbortSignal,
|
|
1501
1745
|
parseSemverMajorMinor,
|
|
1502
1746
|
parseServerUrl,
|
|
1747
|
+
resolvePeerConfig,
|
|
1503
1748
|
sha256Hex,
|
|
1504
1749
|
sleep,
|
|
1505
1750
|
startP2PReceive,
|