@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/README.md
CHANGED
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
<div align="center">
|
|
10
10
|
|
|
11
11
|

|
|
12
|
-

|
|
13
13
|

|
|
14
14
|
|
|
15
|
+
[](https://diamonddigital.dev/discord)
|
|
16
|
+
[](https://www.buymeacoffee.com/willtda)
|
|
17
|
+
|
|
15
18
|
</div>
|
|
16
19
|
|
|
17
20
|
## đ Overview
|
|
@@ -49,7 +52,7 @@ The package ships with multiple build targets:
|
|
|
49
52
|
```javascript
|
|
50
53
|
import { DropgateClient } from '@dropgate/core';
|
|
51
54
|
|
|
52
|
-
const client = new DropgateClient({ clientVersion: '2.
|
|
55
|
+
const client = new DropgateClient({ clientVersion: '2.1.0' });
|
|
53
56
|
|
|
54
57
|
const result = await client.uploadFile({
|
|
55
58
|
host: 'dropgate.link',
|
|
@@ -69,11 +72,9 @@ console.log('Download URL:', result.downloadUrl);
|
|
|
69
72
|
### âšī¸ Getting Server Info
|
|
70
73
|
|
|
71
74
|
```javascript
|
|
72
|
-
import {
|
|
73
|
-
|
|
74
|
-
const client = new DropgateClient({ clientVersion: '2.0.0' });
|
|
75
|
+
import { getServerInfo } from '@dropgate/core';
|
|
75
76
|
|
|
76
|
-
const { serverInfo } = await
|
|
77
|
+
const { serverInfo } = await getServerInfo({
|
|
77
78
|
host: 'dropgate.link',
|
|
78
79
|
secure: true,
|
|
79
80
|
timeoutMs: 5000,
|
|
@@ -84,6 +85,28 @@ console.log('Upload enabled:', serverInfo.capabilities?.upload?.enabled);
|
|
|
84
85
|
console.log('P2P enabled:', serverInfo.capabilities?.p2p?.enabled);
|
|
85
86
|
```
|
|
86
87
|
|
|
88
|
+
### đ Checking Compatibility
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
import { DropgateClient } from '@dropgate/core';
|
|
92
|
+
|
|
93
|
+
const client = new DropgateClient({ clientVersion: '2.1.0' });
|
|
94
|
+
|
|
95
|
+
// checkCompatibility fetches server info internally and compares versions
|
|
96
|
+
const compat = await client.checkCompatibility({
|
|
97
|
+
host: 'dropgate.link',
|
|
98
|
+
secure: true,
|
|
99
|
+
timeoutMs: 5000,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
console.log('Compatible:', compat.compatible);
|
|
103
|
+
console.log('Message:', compat.message);
|
|
104
|
+
console.log('Server version:', compat.serverVersion);
|
|
105
|
+
console.log('Client version:', compat.clientVersion);
|
|
106
|
+
// Also returns serverInfo and baseUrl for convenience
|
|
107
|
+
console.log('Server capabilities:', compat.serverInfo.capabilities);
|
|
108
|
+
```
|
|
109
|
+
|
|
87
110
|
### đ¤ P2P File Transfer (Sender)
|
|
88
111
|
|
|
89
112
|
```javascript
|
|
@@ -99,7 +122,7 @@ const session = await startP2PSend({
|
|
|
99
122
|
port: 443,
|
|
100
123
|
secure: true,
|
|
101
124
|
onCode: (code) => console.log('Share this code:', code),
|
|
102
|
-
onProgress: ({
|
|
125
|
+
onProgress: ({ processedBytes, totalBytes, percent }) => {
|
|
103
126
|
console.log(`Sending: ${percent.toFixed(1)}%`);
|
|
104
127
|
},
|
|
105
128
|
onComplete: () => console.log('Transfer complete!'),
|
|
@@ -121,6 +144,7 @@ const session = await startP2PReceive({
|
|
|
121
144
|
code: 'ABCD-1234',
|
|
122
145
|
Peer,
|
|
123
146
|
host: 'dropgate.link',
|
|
147
|
+
port: 443,
|
|
124
148
|
secure: true,
|
|
125
149
|
onMeta: ({ name, total }) => {
|
|
126
150
|
console.log(`Receiving: ${name} (${total} bytes)`);
|
|
@@ -129,19 +153,56 @@ const session = await startP2PReceive({
|
|
|
129
153
|
// Consumer handles file writing (e.g., streamSaver, fs.write)
|
|
130
154
|
await writer.write(chunk);
|
|
131
155
|
},
|
|
132
|
-
onProgress: ({
|
|
156
|
+
onProgress: ({ processedBytes, totalBytes, percent }) => {
|
|
133
157
|
console.log(`Receiving: ${percent.toFixed(1)}%`);
|
|
134
158
|
},
|
|
135
159
|
onComplete: () => console.log('Transfer complete!'),
|
|
136
160
|
});
|
|
137
161
|
```
|
|
138
162
|
|
|
163
|
+
### đĨ P2P File Transfer with Preview (Receiver)
|
|
164
|
+
|
|
165
|
+
Use `autoReady: false` to show a file preview before starting the transfer:
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
import { startP2PReceive } from '@dropgate/core';
|
|
169
|
+
|
|
170
|
+
const Peer = await loadPeerJS();
|
|
171
|
+
let writer;
|
|
172
|
+
|
|
173
|
+
const session = await startP2PReceive({
|
|
174
|
+
code: 'ABCD-1234',
|
|
175
|
+
Peer,
|
|
176
|
+
host: 'dropgate.link',
|
|
177
|
+
secure: true,
|
|
178
|
+
autoReady: false, // Don't start transfer automatically
|
|
179
|
+
onMeta: ({ name, total, sendReady }) => {
|
|
180
|
+
// Show file preview to user
|
|
181
|
+
console.log(`File: ${name} (${total} bytes)`);
|
|
182
|
+
showPreviewUI(name, total);
|
|
183
|
+
|
|
184
|
+
// When user confirms, create writer and start transfer
|
|
185
|
+
confirmButton.onclick = () => {
|
|
186
|
+
writer = createWriteStream(name);
|
|
187
|
+
sendReady(); // Signal sender to begin transfer
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
onData: async (chunk) => {
|
|
191
|
+
await writer.write(chunk);
|
|
192
|
+
},
|
|
193
|
+
onComplete: () => {
|
|
194
|
+
writer.close();
|
|
195
|
+
console.log('Transfer complete!');
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
139
200
|
### âŦī¸ Downloading a File
|
|
140
201
|
|
|
141
202
|
```javascript
|
|
142
203
|
import { DropgateClient } from '@dropgate/core';
|
|
143
204
|
|
|
144
|
-
const client = new DropgateClient({ clientVersion: '2.
|
|
205
|
+
const client = new DropgateClient({ clientVersion: '2.1.0' });
|
|
145
206
|
|
|
146
207
|
// Download with streaming (for large files)
|
|
147
208
|
const result = await client.downloadFile({
|
|
@@ -150,8 +211,8 @@ const result = await client.downloadFile({
|
|
|
150
211
|
secure: true,
|
|
151
212
|
fileId: 'abc123',
|
|
152
213
|
keyB64: 'base64-key-from-url-hash', // Required for encrypted files
|
|
153
|
-
onProgress: ({ phase, percent,
|
|
154
|
-
console.log(`${phase}: ${percent}% (${
|
|
214
|
+
onProgress: ({ phase, percent, processedBytes, totalBytes }) => {
|
|
215
|
+
console.log(`${phase}: ${percent}% (${processedBytes}/${totalBytes})`);
|
|
155
216
|
},
|
|
156
217
|
onData: async (chunk) => {
|
|
157
218
|
// Consumer handles file writing (e.g., fs.write, streamSaver)
|
|
@@ -193,10 +254,9 @@ The main client class for interacting with Dropgate servers.
|
|
|
193
254
|
|
|
194
255
|
| Method | Description |
|
|
195
256
|
| --- | --- |
|
|
196
|
-
| `getServerInfo(opts)` | Fetch server info and capabilities |
|
|
197
257
|
| `uploadFile(opts)` | Upload a file with optional encryption |
|
|
198
258
|
| `downloadFile(opts)` | Download a file with optional decryption |
|
|
199
|
-
| `checkCompatibility(
|
|
259
|
+
| `checkCompatibility(opts)` | Fetch server info and check client/server version compatibility |
|
|
200
260
|
| `validateUploadInputs(opts)` | Validate file and settings before upload |
|
|
201
261
|
| `resolveShareTarget(value, opts)` | Resolve a sharing code via the server |
|
|
202
262
|
|
|
@@ -215,6 +275,7 @@ The main client class for interacting with Dropgate servers.
|
|
|
215
275
|
|
|
216
276
|
| Function | Description |
|
|
217
277
|
| --- | --- |
|
|
278
|
+
| `getServerInfo(opts)` | Fetch server info and capabilities |
|
|
218
279
|
| `parseServerUrl(urlStr)` | Parse a URL string into host/port/secure |
|
|
219
280
|
| `buildBaseUrl(opts)` | Build a URL from host/port/secure |
|
|
220
281
|
| `lifetimeToMs(value, unit)` | Convert lifetime to milliseconds |
|
|
@@ -240,7 +301,7 @@ For browser environments, you can use the IIFE bundle:
|
|
|
240
301
|
```html
|
|
241
302
|
<script src="/path/to/dropgate-core.browser.js"></script>
|
|
242
303
|
<script>
|
|
243
|
-
const { DropgateClient, startP2PSend } = DropgateCore;
|
|
304
|
+
const { DropgateClient, getServerInfo, startP2PSend } = DropgateCore;
|
|
244
305
|
// ...
|
|
245
306
|
</script>
|
|
246
307
|
```
|
|
@@ -249,7 +310,7 @@ Or as an ES module:
|
|
|
249
310
|
|
|
250
311
|
```html
|
|
251
312
|
<script type="module">
|
|
252
|
-
import { DropgateClient } from '/path/to/dropgate-core.js';
|
|
313
|
+
import { DropgateClient, getServerInfo } from '/path/to/dropgate-core.js';
|
|
253
314
|
// ...
|
|
254
315
|
</script>
|
|
255
316
|
```
|
package/dist/index.browser.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var DropgateCore=(()=>{var ke=Object.defineProperty;var Ke=Object.getOwnPropertyDescriptor;var Ve=Object.getOwnPropertyNames;var He=Object.prototype.hasOwnProperty;var Je=(r,e)=>{for(var t in e)ke(r,t,{get:e[t],enumerable:!0})},We=(r,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Ve(e))!He.call(r,n)&&n!==t&&ke(r,n,{get:()=>e[n],enumerable:!(o=Ke(e,n))||o.enumerable});return r};var Ye=r=>We(ke({},"__esModule",{value:!0}),r);var Ze={};Je(Ze,{AES_GCM_IV_BYTES:()=>se,AES_GCM_TAG_BYTES:()=>Ne,DEFAULT_CHUNK_SIZE:()=>he,DropgateAbortError:()=>$,DropgateClient:()=>Ae,DropgateError:()=>U,DropgateNetworkError:()=>F,DropgateProtocolError:()=>N,DropgateTimeoutError:()=>te,DropgateValidationError:()=>u,ENCRYPTION_OVERHEAD_PER_CHUNK:()=>ye,arrayBufferToBase64:()=>oe,base64ToBytes:()=>$e,buildBaseUrl:()=>ne,buildPeerOptions:()=>ee,bytesToBase64:()=>Oe,createPeerWithRetries:()=>fe,decryptChunk:()=>Q,decryptFilenameFromBase64:()=>ce,encryptFilenameToBase64:()=>Se,encryptToBlob:()=>le,estimateTotalUploadSizeBytes:()=>Ce,exportKeyBase64:()=>ve,fetchJson:()=>Z,generateAesGcmKey:()=>Pe,generateP2PCode:()=>pe,getDefaultBase64:()=>J,getDefaultCrypto:()=>re,getDefaultFetch:()=>ge,importKeyFromBase64:()=>ae,isLocalhostHostname:()=>xe,isP2PCodeLike:()=>ue,isSecureContextForP2P:()=>Re,lifetimeToMs:()=>Le,makeAbortSignal:()=>X,parseSemverMajorMinor:()=>ie,parseServerUrl:()=>ze,sha256Hex:()=>be,sleep:()=>q,startP2PReceive:()=>Te,startP2PSend:()=>Me,validatePlainFilename:()=>we});var he=5242880,se=12,Ne=16,ye=28;var U=class extends Error{constructor(e,t={}){super(e),this.name=this.constructor.name,this.code=t.code||"DROPGATE_ERROR",this.details=t.details,t.cause!==void 0&&Object.defineProperty(this,"cause",{value:t.cause,writable:!1,enumerable:!1,configurable:!0})}},u=class extends U{constructor(e,t={}){super(e,{...t,code:t.code||"VALIDATION_ERROR"})}},F=class extends U{constructor(e,t={}){super(e,{...t,code:t.code||"NETWORK_ERROR"})}},N=class extends U{constructor(e,t={}){super(e,{...t,code:t.code||"PROTOCOL_ERROR"})}},$=class extends U{constructor(e="Operation aborted"){super(e,{code:"ABORT_ERROR"}),this.name="AbortError"}},te=class extends U{constructor(e="Request timed out"){super(e,{code:"TIMEOUT_ERROR"}),this.name="TimeoutError"}};function J(){if(typeof Buffer<"u"&&typeof Buffer.from=="function")return{encode(r){return Buffer.from(r).toString("base64")},decode(r){return new Uint8Array(Buffer.from(r,"base64"))}};if(typeof btoa=="function"&&typeof atob=="function")return{encode(r){let e="";for(let t=0;t<r.length;t++)e+=String.fromCharCode(r[t]);return btoa(e)},decode(r){let e=atob(r),t=new Uint8Array(e.length);for(let o=0;o<e.length;o++)t[o]=e.charCodeAt(o);return t}};throw new Error("No Base64 implementation available. Provide a Base64Adapter via options.")}function re(){return globalThis.crypto}function ge(){return globalThis.fetch?.bind(globalThis)}var Fe=null;function _e(r){return r||(Fe||(Fe=J()),Fe)}function Oe(r,e){return _e(e).encode(r)}function oe(r,e){return Oe(new Uint8Array(r),e)}function $e(r,e){return _e(e).decode(r)}var qe={minutes:6e4,hours:36e5,days:864e5};function Le(r,e){let t=String(e||"").toLowerCase(),o=Number(r);if(t==="unlimited"||!Number.isFinite(o)||o<=0)return 0;let n=qe[t];return n?Math.round(o*n):0}function ie(r){let e=String(r||"").split(".").map(n=>Number(n)),t=Number.isFinite(e[0])?e[0]:0,o=Number.isFinite(e[1])?e[1]:0;return{major:t,minor:o}}function we(r){if(typeof r!="string"||r.trim().length===0)throw new u("Invalid filename. Must be a non-empty string.");if(r.length>255||/[\/\\]/.test(r))throw new u("Invalid filename. Contains illegal characters or is too long.")}function ze(r){let e=r.trim();!e.startsWith("http://")&&!e.startsWith("https://")&&(e="https://"+e);let t=new URL(e);return{host:t.hostname,port:t.port?Number(t.port):void 0,secure:t.protocol==="https:"}}function ne(r){let{host:e,port:t,secure:o}=r;if(!e||typeof e!="string")throw new u("Server host is required.");let n=o===!1?"http":"https",s=t?`:${t}`:"";return`${n}://${e}${s}`}function q(r,e){return new Promise((t,o)=>{if(e?.aborted)return o(e.reason||new $);let n=setTimeout(t,r);e&&e.addEventListener("abort",()=>{clearTimeout(n),o(e.reason||new $)},{once:!0})})}function X(r,e){let t=new AbortController,o=null,n=s=>{t.signal.aborted||t.abort(s)};return r&&(r.aborted?n(r.reason):r.addEventListener("abort",()=>n(r.reason),{once:!0})),Number.isFinite(e)&&e>0&&(o=setTimeout(()=>{n(new te)},e)),{signal:t.signal,cleanup:()=>{o&&clearTimeout(o)}}}async function Z(r,e,t={}){let{timeoutMs:o,signal:n,...s}=t,{signal:l,cleanup:f}=X(n,o);try{let i=await r(e,{...s,signal:l}),p=await i.text(),c=null;try{c=p?JSON.parse(p):null}catch{}return{res:i,json:c,text:p}}finally{f()}}async function ae(r,e,t){let n=(t||J()).decode(e),s=new Uint8Array(n).buffer;return r.subtle.importKey("raw",s,{name:"AES-GCM"},!0,["decrypt"])}async function Q(r,e,t){let o=e.slice(0,12),n=e.slice(12);return r.subtle.decrypt({name:"AES-GCM",iv:o},t,n)}async function ce(r,e,t,o){let s=(o||J()).decode(e),l=await Q(r,s,t);return new TextDecoder().decode(l)}async function be(r,e){let t=await r.subtle.digest("SHA-256",e),o=new Uint8Array(t),n="";for(let s=0;s<o.length;s++)n+=o[s].toString(16).padStart(2,"0");return n}async function Pe(r){return r.subtle.generateKey({name:"AES-GCM",length:256},!0,["encrypt","decrypt"])}async function ve(r,e){let t=await r.subtle.exportKey("raw",e);return oe(t)}async function le(r,e,t){let o=r.getRandomValues(new Uint8Array(12)),n=await r.subtle.encrypt({name:"AES-GCM",iv:o},t,e);return new Blob([o,new Uint8Array(n)])}async function Se(r,e,t){let o=new TextEncoder().encode(String(e)),s=await(await le(r,o.buffer,t)).arrayBuffer();return oe(s)}function Ce(r,e,t){let o=Number(r)||0;return t?o+(Number(e)||0)*28:o}var Ae=class{constructor(e){if(!e||typeof e.clientVersion!="string")throw new u("DropgateClient requires clientVersion (string).");this.clientVersion=e.clientVersion,this.chunkSize=Number.isFinite(e.chunkSize)?e.chunkSize:5242880;let t=e.fetchFn||ge();if(!t)throw new u("No fetch() implementation found.");this.fetchFn=t;let o=e.cryptoObj||re();if(!o)throw new u("No crypto implementation found.");this.cryptoObj=o,this.base64=e.base64||J(),this.logger=e.logger||null}async getServerInfo(e){let{host:t,port:o,secure:n,timeoutMs:s=5e3,signal:l}=e,f=ne({host:t,port:o,secure:n});try{let{res:i,json:p}=await Z(this.fetchFn,`${f}/api/info`,{method:"GET",timeoutMs:s,signal:l,headers:{Accept:"application/json"}});if(i.ok&&p&&typeof p=="object"&&"version"in p)return{baseUrl:f,serverInfo:p};throw new N(`Server info request failed (status ${i.status}).`)}catch(i){throw i instanceof U?i:new F("Could not reach server /api/info.",{cause:i})}}async resolveShareTarget(e,t){let{host:o,port:n,secure:s,timeoutMs:l=5e3,signal:f}=t,i=ne({host:o,port:n,secure:s}),{res:p,json:c}=await Z(this.fetchFn,`${i}/api/resolve`,{method:"POST",timeoutMs:l,signal:f,headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({value:e})});if(!p.ok){let b=(c&&typeof c=="object"&&"error"in c?c.error:null)||`Share lookup failed (status ${p.status}).`;throw new N(b,{details:c})}return c||{valid:!1,reason:"Unknown response."}}checkCompatibility(e){let t=String(e?.version||"0.0.0"),o=String(this.clientVersion||"0.0.0"),n=ie(o),s=ie(t);return n.major!==s.major?{compatible:!1,clientVersion:o,serverVersion:t,message:`Incompatible versions. Client v${o}, Server v${t}${e?.name?` (${e.name})`:""}.`}:n.minor>s.minor?{compatible:!0,clientVersion:o,serverVersion:t,message:`Client (v${o}) is newer than Server (v${t})${e?.name?` (${e.name})`:""}. Some features may not work.`}:{compatible:!0,clientVersion:o,serverVersion:t,message:`Server: v${t}, Client: v${o}${e?.name?` (${e.name})`:""}.`}}validateUploadInputs(e){let{file:t,lifetimeMs:o,encrypt:n,serverInfo:s}=e,l=s?.capabilities?.upload;if(!l||!l.enabled)throw new u("Server does not support file uploads.");let f=Number(t?.size||0);if(!t||!Number.isFinite(f)||f<=0)throw new u("File is missing or invalid.");let i=Number(l.maxSizeMB);if(Number.isFinite(i)&&i>0){let b=i*1e3*1e3,P=Math.ceil(f/this.chunkSize);if(Ce(f,P,!!n)>b){let E=n?`File too large once encryption overhead is included. Server limit: ${i} MB.`:`File too large. Server limit: ${i} MB.`;throw new u(E)}}let p=Number(l.maxLifetimeHours),c=Number(o);if(!Number.isFinite(c)||c<0||!Number.isInteger(c))throw new u("Invalid lifetime. Must be a non-negative integer (milliseconds).");if(Number.isFinite(p)&&p>0){let b=Math.round(p*60*60*1e3);if(c===0)throw new u(`Server does not allow unlimited file lifetime. Max: ${p} hours.`);if(c>b)throw new u(`File lifetime too long. Server limit: ${p} hours.`)}if(n&&!l.e2ee)throw new u("Server does not support end-to-end encryption.");return!0}async uploadFile(e){let{host:t,port:o,secure:n,file:s,lifetimeMs:l,encrypt:f,filenameOverride:i,onProgress:p,signal:c,timeouts:b={},retry:P={}}=e,B=d=>{try{p&&p(d)}catch{}};if(!this.cryptoObj?.subtle)throw new u("Web Crypto API not available (crypto.subtle).");B({phase:"server-info",text:"Checking server..."});let E,_;try{let d=await this.getServerInfo({host:t,port:o,secure:n,timeoutMs:b.serverInfoMs??5e3,signal:c});E=d.baseUrl,_=d.serverInfo}catch(d){throw d instanceof U?d:new F("Could not connect to the server.",{cause:d})}let R=this.checkCompatibility(_);if(B({phase:"server-compat",text:R.message}),!R.compatible)throw new u(R.message);let m=i??s.name??"file";f||we(m),this.validateUploadInputs({file:s,lifetimeMs:l,encrypt:f,serverInfo:_});let O=null,k=null,z=m;if(f){B({phase:"crypto",text:"Generating encryption key..."});try{O=await Pe(this.cryptoObj),k=await ve(this.cryptoObj,O),z=await Se(this.cryptoObj,m,O)}catch(d){throw new U("Failed to prepare encryption.",{code:"CRYPTO_PREP_FAILED",cause:d})}}let S=Math.ceil(s.size/this.chunkSize),g=Ce(s.size,S,f);B({phase:"init",text:"Reserving server storage..."});let v={filename:z,lifetime:l,isEncrypted:!!f,totalSize:g,totalChunks:S},I=await Z(this.fetchFn,`${E}/upload/init`,{method:"POST",timeoutMs:b.initMs??15e3,signal:c,headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(v)});if(!I.res.ok){let T=I.json?.error||`Server initialisation failed: ${I.res.status}`;throw new N(T,{details:I.json||I.text})}let a=I.json?.uploadId;if(!a||typeof a!="string")throw new N("Server did not return a valid uploadId.");let j=Number.isFinite(P.retries)?P.retries:5,w=Number.isFinite(P.backoffMs)?P.backoffMs:1e3,h=Number.isFinite(P.maxBackoffMs)?P.maxBackoffMs:3e4;for(let d=0;d<S;d++){if(c?.aborted)throw c.reason||new $;let T=d*this.chunkSize,x=Math.min(T+this.chunkSize,s.size),G=s.slice(T,x),K=d/S*100;B({phase:"chunk",text:`Uploading chunk ${d+1} of ${S}...`,percent:K,chunkIndex:d,totalChunks:S});let H=await G.arrayBuffer(),W;if(f&&O?W=await le(this.cryptoObj,H,O):W=new Blob([H]),W.size>5243904)throw new u("Chunk too large (client-side). Check chunk size settings.");let D=await W.arrayBuffer(),L=await be(this.cryptoObj,D),Y={"Content-Type":"application/octet-stream","X-Upload-ID":a,"X-Chunk-Index":String(d),"X-Chunk-Hash":L},Be=`${E}/upload/chunk`;await this.attemptChunkUpload(Be,{method:"POST",headers:Y,body:W},{retries:j,backoffMs:w,maxBackoffMs:h,timeoutMs:b.chunkMs??6e4,signal:c,progress:B,chunkIndex:d,totalChunks:S})}B({phase:"complete",text:"Finalising upload...",percent:100});let y=await Z(this.fetchFn,`${E}/upload/complete`,{method:"POST",timeoutMs:b.completeMs??3e4,signal:c,headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({uploadId:a})});if(!y.res.ok){let T=y.json?.error||"Finalisation failed.";throw new N(T,{details:y.json||y.text})}let A=y.json?.id;if(!A||typeof A!="string")throw new N("Server did not return a valid file id.");let M=`${E}/${A}`;return f&&k&&(M+=`#${k}`),B({phase:"done",text:"Upload successful!",percent:100}),{downloadUrl:M,fileId:A,uploadId:a,baseUrl:E,...f&&k?{keyB64:k}:{}}}async downloadFile(e){let{host:t,port:o,secure:n,fileId:s,keyB64:l,onProgress:f,onData:i,signal:p,timeoutMs:c=6e4}=e,b=a=>{try{f&&f(a)}catch{}};if(!s||typeof s!="string")throw new u("File ID is required.");let P=ne({host:t,port:o,secure:n});b({phase:"metadata",text:"Fetching file info...",receivedBytes:0,totalBytes:0,percent:0});let{signal:B,cleanup:E}=X(p,c),_;try{let a=await this.fetchFn(`${P}/api/file/${s}/meta`,{method:"GET",headers:{Accept:"application/json"},signal:B});if(!a.ok)throw a.status===404?new N("File not found or has expired."):new N(`Failed to fetch file metadata (status ${a.status}).`);_=await a.json()}catch(a){throw a instanceof U?a:a instanceof Error&&a.name==="AbortError"?new $("Download cancelled."):new F("Could not fetch file metadata.",{cause:a})}finally{E()}let R=!!_.isEncrypted,m=_.sizeBytes||0;if(!i&&m>104857600){let a=Math.round(m/1048576),j=Math.round(104857600/(1024*1024));throw new u(`File is too large (${a}MB) to download without streaming. Provide an onData callback to stream files larger than ${j}MB.`)}let O,k;if(R){if(!l)throw new u("Decryption key is required for encrypted files.");if(!this.cryptoObj?.subtle)throw new u("Web Crypto API not available for decryption.");b({phase:"decrypting",text:"Preparing decryption...",receivedBytes:0,totalBytes:0,percent:0});try{k=await ae(this.cryptoObj,l,this.base64),O=await ce(this.cryptoObj,_.encryptedFilename,k,this.base64)}catch(a){throw new U("Failed to decrypt filename. Invalid key or corrupted data.",{code:"DECRYPT_FILENAME_FAILED",cause:a})}}else O=_.filename||"file";b({phase:"downloading",text:"Starting download...",percent:0,receivedBytes:0,totalBytes:m});let{signal:z,cleanup:S}=X(p,c),g=0,v=[],I=!i;try{let a=await this.fetchFn(`${P}/api/file/${s}`,{method:"GET",signal:z});if(!a.ok)throw new N(`Download failed (status ${a.status}).`);if(!a.body)throw new N("Streaming response not available.");let j=a.body.getReader();if(R&&k){let w=this.chunkSize+28,h=[],y=0,C=()=>{if(h.length===0)return new Uint8Array(0);if(h.length===1){let d=h[0];return h.length=0,y=0,d}let A=new Uint8Array(y),M=0;for(let d of h)A.set(d,M),M+=d.length;return h.length=0,y=0,A};for(;;){if(p?.aborted)throw new $("Download cancelled.");let{done:A,value:M}=await j.read();if(A)break;for(h.push(M),y+=M.length;y>=w;){let T=C(),x=T.subarray(0,w);if(T.length>w){let H=T.subarray(w);h.push(H),y=H.length}let G=await Q(this.cryptoObj,x,k),K=new Uint8Array(G);I?v.push(K):await i(K)}g+=M.length;let d=m>0?Math.round(g/m*100):0;b({phase:"decrypting",text:`Downloading & decrypting... (${d}%)`,percent:d,receivedBytes:g,totalBytes:m})}if(y>0){let A=C(),M=await Q(this.cryptoObj,A,k),d=new Uint8Array(M);I?v.push(d):await i(d)}}else for(;;){if(p?.aborted)throw new $("Download cancelled.");let{done:w,value:h}=await j.read();if(w)break;I?v.push(h):await i(h),g+=h.length;let y=m>0?Math.round(g/m*100):0;b({phase:"downloading",text:`Downloading... (${y}%)`,percent:y,receivedBytes:g,totalBytes:m})}}catch(a){throw a instanceof U?a:a instanceof Error&&a.name==="AbortError"?new $("Download cancelled."):new F("Download failed.",{cause:a})}finally{S()}b({phase:"complete",text:"Download complete!",percent:100,receivedBytes:g,totalBytes:m});let V;if(I&&v.length>0){let a=v.reduce((w,h)=>w+h.length,0);V=new Uint8Array(a);let j=0;for(let w of v)V.set(w,j),j+=w.length}return{filename:O,receivedBytes:g,wasEncrypted:R,...V?{data:V}:{}}}async attemptChunkUpload(e,t,o){let{retries:n,backoffMs:s,maxBackoffMs:l,timeoutMs:f,signal:i,progress:p,chunkIndex:c,totalChunks:b}=o,P=n,B=s,E=n;for(;;){if(i?.aborted)throw i.reason||new $;let{signal:_,cleanup:R}=X(i,f);try{let m=await this.fetchFn(e,{...t,signal:_});if(m.ok)return;let O=await m.text().catch(()=>"");throw new N(`Chunk ${c+1} failed (HTTP ${m.status}).`,{details:{status:m.status,bodySnippet:O.slice(0,120)}})}catch(m){if(R(),m instanceof Error&&(m.name==="AbortError"||m.code==="ABORT_ERR"))throw m;if(i?.aborted)throw i.reason||new $;if(P<=0)throw m instanceof U?m:new F("Chunk upload failed.",{cause:m});let O=E-P+1,k=B,z=100;for(;k>0;){let S=(k/1e3).toFixed(1);p({phase:"retry-wait",text:`Chunk upload failed. Retrying in ${S}s... (${O}/${E})`,chunkIndex:c,totalChunks:b}),await q(Math.min(z,k),i),k-=z}p({phase:"retry",text:`Chunk upload failed. Retrying now... (${O}/${E})`,chunkIndex:c,totalChunks:b}),P-=1,B=Math.min(B*2,l);continue}finally{R()}}}};function xe(r){let e=String(r||"").toLowerCase();return e==="localhost"||e==="127.0.0.1"||e==="::1"}function Re(r,e){return!!e||xe(r||"")}function pe(r){let e=r||re(),t="ABCDEFGHJKLMNPQRSTUVWXYZ";if(e){let s=new Uint8Array(8);e.getRandomValues(s);let l="";for(let i=0;i<4;i++)l+=t[s[i]%t.length];let f="";for(let i=4;i<8;i++)f+=(s[i]%10).toString();return`${l}-${f}`}let o="";for(let s=0;s<4;s++)o+=t[Math.floor(Math.random()*t.length)];let n="";for(let s=0;s<4;s++)n+=Math.floor(Math.random()*10);return`${o}-${n}`}function ue(r){return/^[A-Z]{4}-\d{4}$/.test(String(r||"").trim())}function ee(r={}){let{host:e,port:t,peerjsPath:o="/peerjs",secure:n=!1,iceServers:s=[]}=r,l={host:e,path:o,secure:n,config:{iceServers:s},debug:0};return t&&(l.port=t),l}async function fe(r){let{code:e,codeGenerator:t,maxAttempts:o,buildPeer:n,onCode:s}=r,l=e||t(),f=null,i=null;for(let p=0;p<o;p++){s?.(l,p);try{return f=await new Promise((c,b)=>{let P=n(l);P.on("open",()=>c(P)),P.on("error",B=>{try{P.destroy()}catch{}b(B)})}),{peer:f,code:l}}catch(c){i=c,l=t()}}throw i||new F("Could not establish PeerJS connection.")}async function Me(r){let{file:e,Peer:t,serverInfo:o,host:n,port:s,peerjsPath:l,secure:f=!1,iceServers:i,codeGenerator:p,cryptoObj:c,maxAttempts:b=4,chunkSize:P=256*1024,readyTimeoutMs:B=8e3,endAckTimeoutMs:E=15e3,bufferHighWaterMark:_=8*1024*1024,bufferLowWaterMark:R=2*1024*1024,onCode:m,onStatus:O,onProgress:k,onComplete:z,onError:S}=r;if(!e)throw new u("File is missing.");if(!t)throw new u("PeerJS Peer constructor is required. Install peerjs and pass it as the Peer option.");let g=o?.capabilities?.p2p;if(o&&!g?.enabled)throw new u("Direct transfer is disabled on this server.");let v=l??g?.peerjsPath??"/peerjs",I=i??g?.iceServers??[],V=ee({host:n,port:s,peerjsPath:v,secure:f,iceServers:I}),a=p||(()=>pe(c)),j=x=>new t(x,V),{peer:w,code:h}=await fe({code:null,codeGenerator:a,maxAttempts:b,buildPeer:j,onCode:m}),y=!1,C=null,A=!1,M=!1,d=x=>{let G=Number.isFinite(x.total)&&x.total>0?x.total:e.size,K=Math.min(Number(x.received)||0,G||0),H=G?K/G*100:0;k?.({sent:K,total:G,percent:H})},T=()=>{y=!0;try{C?.close()}catch{}try{w.destroy()}catch{}};return w.on("connection",x=>{if(y)return;if(C){try{x.send({t:"error",message:"Another receiver is already connected."})}catch{}try{x.close()}catch{}return}C=x,O?.({phase:"connected",message:"Connected. Starting transfer..."});let G=null,K=null,H=new Promise(D=>{G=D}),W=new Promise(D=>{K=D});x.on("data",D=>{if(!D||typeof D!="object"||D instanceof ArrayBuffer||ArrayBuffer.isView(D))return;let L=D;if(L.t){if(L.t==="ready"){G?.();return}if(L.t==="progress"){d({received:L.received||0,total:L.total||0});return}if(L.t==="ack"&&L.phase==="end"){K?.(L);return}L.t==="error"&&(S?.(new F(L.message||"Receiver reported an error.")),T())}}),x.on("open",async()=>{try{if(A=!0,y)return;x.send({t:"meta",name:e.name,size:e.size,mime:e.type||"application/octet-stream"});let D=0,L=e.size,Y=x._dc;if(Y&&Number.isFinite(R))try{Y.bufferedAmountLowThreshold=R}catch{}await Promise.race([H,q(B).catch(()=>null)]);for(let me=0;me<L;me+=P){if(y)return;let Ie=await e.slice(me,me+P).arrayBuffer();if(x.send(Ie),D+=Ie.byteLength,Y)for(;Y.bufferedAmount>_;)await new Promise(je=>{let Ge=setTimeout(je,60);try{Y.addEventListener("bufferedamountlow",()=>{clearTimeout(Ge),je()},{once:!0})}catch{}})}if(y)return;x.send({t:"end"});let Be=Number.isFinite(E)?Math.max(E,Math.ceil(e.size/(1024*1024))*1e3):null,Ee=await Promise.race([W,q(Be||15e3).catch(()=>null)]);if(!Ee||typeof Ee!="object")throw new F("Receiver did not confirm completion.");let De=Ee,de=Number(De.total)||e.size,Ue=Number(De.received)||0;if(de&&Ue<de)throw new F("Receiver reported an incomplete transfer.");d({received:Ue||de,total:de}),M=!0,A=!1,z?.(),T()}catch(D){S?.(D),T()}}),x.on("error",D=>{S?.(D),T()}),x.on("close",()=>{!M&&A&&!y&&S?.(new F("Receiver disconnected before transfer completed.")),T()})}),{peer:w,code:h,stop:T}}async function Te(r){let{code:e,Peer:t,serverInfo:o,host:n,port:s,peerjsPath:l,secure:f=!1,iceServers:i,onStatus:p,onMeta:c,onData:b,onProgress:P,onComplete:B,onError:E,onDisconnect:_}=r;if(!e)throw new u("No sharing code was provided.");if(!t)throw new u("PeerJS Peer constructor is required. Install peerjs and pass it as the Peer option.");let R=o?.capabilities?.p2p;if(o&&!R?.enabled)throw new u("Direct transfer is disabled on this server.");let m=String(e).trim().replace(/\s+/g,"").toUpperCase();if(!ue(m))throw new u("Invalid direct transfer code.");let O=l??R?.peerjsPath??"/peerjs",k=i??R?.iceServers??[],z=ee({host:n,port:s,peerjsPath:O,secure:f,iceServers:k}),S=new t(void 0,z),g=0,v=0,I=0,V=120,a=Promise.resolve(),j=()=>{try{S.destroy()}catch{}};return S.on("error",w=>{E?.(w),j()}),S.on("open",()=>{let w=S.connect(m,{reliable:!0});w.on("open",()=>{p?.({phase:"connected",message:"Waiting for file details..."})}),w.on("data",async h=>{try{if(h&&typeof h=="object"&&!(h instanceof ArrayBuffer)&&!ArrayBuffer.isView(h)){let C=h;if(C.t==="meta"){let A=String(C.name||"file");g=Number(C.size)||0,v=0,a=Promise.resolve(),c?.({name:A,total:g}),P?.({received:v,total:g,percent:0});try{w.send({t:"ready"})}catch{}return}if(C.t==="end"){if(await a,g&&v<g){let A=new F("Transfer ended before the full file was received.");try{w.send({t:"error",message:A.message})}catch{}throw A}B?.({received:v,total:g});try{w.send({t:"ack",phase:"end",received:v,total:g})}catch{}return}if(C.t==="error")throw new F(C.message||"Sender reported an error.");return}let y;if(h instanceof ArrayBuffer)y=Promise.resolve(new Uint8Array(h));else if(ArrayBuffer.isView(h))y=Promise.resolve(new Uint8Array(h.buffer,h.byteOffset,h.byteLength));else if(typeof Blob<"u"&&h instanceof Blob)y=h.arrayBuffer().then(C=>new Uint8Array(C));else return;a=a.then(async()=>{let C=await y;b&&await b(C),v+=C.byteLength;let A=g?Math.min(100,v/g*100):0;P?.({received:v,total:g,percent:A});let M=Date.now();if(v===g||M-I>=V){I=M;try{w.send({t:"progress",received:v,total:g})}catch{}}}).catch(C=>{try{w.send({t:"error",message:C?.message||"Receiver write failed."})}catch{}E?.(C),j()})}catch(y){E?.(y),j()}}),w.on("close",()=>{v>0&&g>0&&v<g&&_?.()})}),{peer:S,stop:j}}return Ye(Ze);})();
|
|
1
|
+
"use strict";var DropgateCore=(()=>{var Ie=Object.defineProperty;var Ze=Object.getOwnPropertyDescriptor;var Qe=Object.getOwnPropertyNames;var et=Object.prototype.hasOwnProperty;var tt=(r,e)=>{for(var t in e)Ie(r,t,{get:e[t],enumerable:!0})},rt=(r,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Qe(e))!et.call(r,n)&&n!==t&&Ie(r,n,{get:()=>e[n],enumerable:!(o=Ze(e,n))||o.enumerable});return r};var ot=r=>rt(Ie({},"__esModule",{value:!0}),r);var at={};tt(at,{AES_GCM_IV_BYTES:()=>pe,AES_GCM_TAG_BYTES:()=>Ke,DEFAULT_CHUNK_SIZE:()=>ve,DropgateAbortError:()=>V,DropgateClient:()=>Fe,DropgateError:()=>$,DropgateNetworkError:()=>R,DropgateProtocolError:()=>z,DropgateTimeoutError:()=>se,DropgateValidationError:()=>f,ENCRYPTION_OVERHEAD_PER_CHUNK:()=>Se,arrayBufferToBase64:()=>ae,base64ToBytes:()=>He,buildBaseUrl:()=>Ce,buildPeerOptions:()=>ne,bytesToBase64:()=>De,createPeerWithRetries:()=>we,decryptChunk:()=>re,decryptFilenameFromBase64:()=>me,encryptFilenameToBase64:()=>ke,encryptToBlob:()=>he,estimateTotalUploadSizeBytes:()=>Re,exportKeyBase64:()=>xe,fetchJson:()=>te,generateAesGcmKey:()=>Ee,generateP2PCode:()=>ye,getDefaultBase64:()=>Z,getDefaultCrypto:()=>ie,getDefaultFetch:()=>de,getServerInfo:()=>Ue,importKeyFromBase64:()=>ue,isLocalhostHostname:()=>Oe,isP2PCodeLike:()=>ge,isSecureContextForP2P:()=>je,lifetimeToMs:()=>Je,makeAbortSignal:()=>ee,parseSemverMajorMinor:()=>fe,parseServerUrl:()=>We,resolvePeerConfig:()=>oe,sha256Hex:()=>Ae,sleep:()=>ce,startP2PReceive:()=>_e,startP2PSend:()=>Ne,validatePlainFilename:()=>Be});var ve=5242880,pe=12,Ke=16,Se=28;var $=class extends Error{constructor(e,t={}){super(e),this.name=this.constructor.name,this.code=t.code||"DROPGATE_ERROR",this.details=t.details,t.cause!==void 0&&Object.defineProperty(this,"cause",{value:t.cause,writable:!1,enumerable:!1,configurable:!0})}},f=class extends ${constructor(e,t={}){super(e,{...t,code:t.code||"VALIDATION_ERROR"})}},R=class extends ${constructor(e,t={}){super(e,{...t,code:t.code||"NETWORK_ERROR"})}},z=class extends ${constructor(e,t={}){super(e,{...t,code:t.code||"PROTOCOL_ERROR"})}},V=class extends ${constructor(e="Operation aborted"){super(e,{code:"ABORT_ERROR"}),this.name="AbortError"}},se=class extends ${constructor(e="Request timed out"){super(e,{code:"TIMEOUT_ERROR"}),this.name="TimeoutError"}};function Z(){if(typeof Buffer<"u"&&typeof Buffer.from=="function")return{encode(r){return Buffer.from(r).toString("base64")},decode(r){return new Uint8Array(Buffer.from(r,"base64"))}};if(typeof btoa=="function"&&typeof atob=="function")return{encode(r){let e="";for(let t=0;t<r.length;t++)e+=String.fromCharCode(r[t]);return btoa(e)},decode(r){let e=atob(r),t=new Uint8Array(e.length);for(let o=0;o<e.length;o++)t[o]=e.charCodeAt(o);return t}};throw new Error("No Base64 implementation available. Provide a Base64Adapter via options.")}function ie(){return globalThis.crypto}function de(){return globalThis.fetch?.bind(globalThis)}var Me=null;function Ve(r){return r||(Me||(Me=Z()),Me)}function De(r,e){return Ve(e).encode(r)}function ae(r,e){return De(new Uint8Array(r),e)}function He(r,e){return Ve(e).decode(r)}var nt={minutes:6e4,hours:36e5,days:864e5};function Je(r,e){let t=String(e||"").toLowerCase(),o=Number(r);if(t==="unlimited"||!Number.isFinite(o)||o<=0)return 0;let n=nt[t];return n?Math.round(o*n):0}function fe(r){let e=String(r||"").split(".").map(n=>Number(n)),t=Number.isFinite(e[0])?e[0]:0,o=Number.isFinite(e[1])?e[1]:0;return{major:t,minor:o}}function Be(r){if(typeof r!="string"||r.trim().length===0)throw new f("Invalid filename. Must be a non-empty string.");if(r.length>255||/[\/\\]/.test(r))throw new f("Invalid filename. Contains illegal characters or is too long.")}function We(r){let e=r.trim();!e.startsWith("http://")&&!e.startsWith("https://")&&(e="https://"+e);let t=new URL(e);return{host:t.hostname,port:t.port?Number(t.port):void 0,secure:t.protocol==="https:"}}function Ce(r){let{host:e,port:t,secure:o}=r;if(!e||typeof e!="string")throw new f("Server host is required.");let n=o===!1?"http":"https",s=t?`:${t}`:"";return`${n}://${e}${s}`}function ce(r,e){return new Promise((t,o)=>{if(e?.aborted)return o(e.reason||new V);let n=setTimeout(t,r);e&&e.addEventListener("abort",()=>{clearTimeout(n),o(e.reason||new V)},{once:!0})})}function ee(r,e){let t=new AbortController,o=null,n=s=>{t.signal.aborted||t.abort(s)};return r&&(r.aborted?n(r.reason):r.addEventListener("abort",()=>n(r.reason),{once:!0})),Number.isFinite(e)&&e>0&&(o=setTimeout(()=>{n(new se)},e)),{signal:t.signal,cleanup:()=>{o&&clearTimeout(o)}}}async function te(r,e,t={}){let{timeoutMs:o,signal:n,...s}=t,{signal:l,cleanup:p}=ee(n,o);try{let i=await r(e,{...s,signal:l}),u=await i.text(),d=null;try{d=u?JSON.parse(u):null}catch{}return{res:i,json:d,text:u}}finally{p()}}async function ue(r,e,t){let n=(t||Z()).decode(e),s=new Uint8Array(n).buffer;return r.subtle.importKey("raw",s,{name:"AES-GCM"},!0,["decrypt"])}async function re(r,e,t){let o=e.slice(0,12),n=e.slice(12);return r.subtle.decrypt({name:"AES-GCM",iv:o},t,n)}async function me(r,e,t,o){let s=(o||Z()).decode(e),l=await re(r,s,t);return new TextDecoder().decode(l)}async function Ae(r,e){let t=await r.subtle.digest("SHA-256",e),o=new Uint8Array(t),n="";for(let s=0;s<o.length;s++)n+=o[s].toString(16).padStart(2,"0");return n}async function Ee(r){return r.subtle.generateKey({name:"AES-GCM",length:256},!0,["encrypt","decrypt"])}async function xe(r,e){let t=await r.subtle.exportKey("raw",e);return ae(t)}async function he(r,e,t){let o=r.getRandomValues(new Uint8Array(12)),n=await r.subtle.encrypt({name:"AES-GCM",iv:o},t,e);return new Blob([o,new Uint8Array(n)])}async function ke(r,e,t){let o=new TextEncoder().encode(String(e)),s=await(await he(r,o.buffer,t)).arrayBuffer();return ae(s)}function Re(r,e,t){let o=Number(r)||0;return t?o+(Number(e)||0)*28:o}async function Ue(r){let{host:e,port:t,secure:o,timeoutMs:n=5e3,signal:s,fetchFn:l}=r,p=l||de();if(!p)throw new f("No fetch() implementation found.");let i=Ce({host:e,port:t,secure:o});try{let{res:u,json:d}=await te(p,`${i}/api/info`,{method:"GET",timeoutMs:n,signal:s,headers:{Accept:"application/json"}});if(u.ok&&d&&typeof d=="object"&&"version"in d)return{baseUrl:i,serverInfo:d};throw new z(`Server info request failed (status ${u.status}).`)}catch(u){throw u instanceof $?u:new R("Could not reach server /api/info.",{cause:u})}}var Fe=class{constructor(e){if(!e||typeof e.clientVersion!="string")throw new f("DropgateClient requires clientVersion (string).");this.clientVersion=e.clientVersion,this.chunkSize=Number.isFinite(e.chunkSize)?e.chunkSize:5242880;let t=e.fetchFn||de();if(!t)throw new f("No fetch() implementation found.");this.fetchFn=t;let o=e.cryptoObj||ie();if(!o)throw new f("No crypto implementation found.");this.cryptoObj=o,this.base64=e.base64||Z(),this.logger=e.logger||null}async resolveShareTarget(e,t){let{timeoutMs:o=5e3,signal:n}=t,s=await this.checkCompatibility(t);if(!s.compatible)throw new f(s.message);let{baseUrl:l}=s,{res:p,json:i}=await te(this.fetchFn,`${l}/api/resolve`,{method:"POST",timeoutMs:o,signal:n,headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({value:e})});if(!p.ok){let u=(i&&typeof i=="object"&&"error"in i?i.error:null)||`Share lookup failed (status ${p.status}).`;throw new z(u,{details:i})}return i||{valid:!1,reason:"Unknown response."}}async checkCompatibility(e){let t,o;try{let i=await Ue({...e,fetchFn:this.fetchFn});t=i.baseUrl,o=i.serverInfo}catch(i){throw i instanceof $?i:new R("Could not connect to the server.",{cause:i})}let n=String(o?.version||"0.0.0"),s=String(this.clientVersion||"0.0.0"),l=fe(s),p=fe(n);return l.major!==p.major?{compatible:!1,clientVersion:s,serverVersion:n,message:`Incompatible versions. Client v${s}, Server v${n}${o?.name?` (${o.name})`:""}.`,serverInfo:o,baseUrl:t}:l.minor>p.minor?{compatible:!0,clientVersion:s,serverVersion:n,message:`Client (v${s}) is newer than Server (v${n})${o?.name?` (${o.name})`:""}. Some features may not work.`,serverInfo:o,baseUrl:t}:{compatible:!0,clientVersion:s,serverVersion:n,message:`Server: v${n}, Client: v${s}${o?.name?` (${o.name})`:""}.`,serverInfo:o,baseUrl:t}}validateUploadInputs(e){let{file:t,lifetimeMs:o,encrypt:n,serverInfo:s}=e,l=s?.capabilities?.upload;if(!l||!l.enabled)throw new f("Server does not support file uploads.");let p=Number(t?.size||0);if(!t||!Number.isFinite(p)||p<=0)throw new f("File is missing or invalid.");let i=Number(l.maxSizeMB);if(Number.isFinite(i)&&i>0){let w=i*1e3*1e3,P=Math.ceil(p/this.chunkSize);if(Re(p,P,!!n)>w){let C=n?`File too large once encryption overhead is included. Server limit: ${i} MB.`:`File too large. Server limit: ${i} MB.`;throw new f(C)}}let u=Number(l.maxLifetimeHours),d=Number(o);if(!Number.isFinite(d)||d<0||!Number.isInteger(d))throw new f("Invalid lifetime. Must be a non-negative integer (milliseconds).");if(Number.isFinite(u)&&u>0){let w=Math.round(u*60*60*1e3);if(d===0)throw new f(`Server does not allow unlimited file lifetime. Max: ${u} hours.`);if(d>w)throw new f(`File lifetime too long. Server limit: ${u} hours.`)}if(n&&!l.e2ee)throw new f("Server does not support end-to-end encryption.");return!0}async uploadFile(e){let{host:t,port:o,secure:n,file:s,lifetimeMs:l,encrypt:p,filenameOverride:i,onProgress:u,signal:d,timeouts:w={},retry:P={}}=e,E=m=>{try{u&&u(m)}catch{}};if(!this.cryptoObj?.subtle)throw new f("Web Crypto API not available (crypto.subtle).");let C=s.size;E({phase:"server-info",text:"Checking server...",percent:0,processedBytes:0,totalBytes:C});let G=await this.checkCompatibility({host:t,port:o,secure:n,timeoutMs:w.serverInfoMs??5e3,signal:d}),{baseUrl:F,serverInfo:X}=G;if(E({phase:"server-compat",text:G.message,percent:0,processedBytes:0,totalBytes:C}),!G.compatible)throw new f(G.message);let x=i??s.name??"file";p||Be(x),this.validateUploadInputs({file:s,lifetimeMs:l,encrypt:p,serverInfo:X});let b=null,O=null,Y=x;if(p){E({phase:"crypto",text:"Generating encryption key...",percent:0,processedBytes:0,totalBytes:C});try{b=await Ee(this.cryptoObj),O=await xe(this.cryptoObj,b),Y=await ke(this.cryptoObj,x,b)}catch(m){throw new $("Failed to prepare encryption.",{code:"CRYPTO_PREP_FAILED",cause:m})}}let L=Math.ceil(s.size/this.chunkSize),A=Re(s.size,L,p);E({phase:"init",text:"Reserving server storage...",percent:0,processedBytes:0,totalBytes:C});let h={filename:Y,lifetime:l,isEncrypted:!!p,totalSize:A,totalChunks:L},y=await te(this.fetchFn,`${F}/upload/init`,{method:"POST",timeoutMs:w.initMs??15e3,signal:d,headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(h)});if(!y.res.ok){let K=y.json?.error||`Server initialisation failed: ${y.res.status}`;throw new z(K,{details:y.json||y.text})}let c=y.json?.uploadId;if(!c||typeof c!="string")throw new z("Server did not return a valid uploadId.");let H=Number.isFinite(P.retries)?P.retries:5,I=Number.isFinite(P.backoffMs)?P.backoffMs:1e3,k=Number.isFinite(P.maxBackoffMs)?P.maxBackoffMs:3e4;for(let m=0;m<L;m++){if(d?.aborted)throw d.reason||new V;let K=m*this.chunkSize,J=Math.min(K+this.chunkSize,s.size),Q=s.slice(K,J),q=m/L*100,B=m*this.chunkSize;E({phase:"chunk",text:`Uploading chunk ${m+1} of ${L}...`,percent:q,processedBytes:B,totalBytes:C,chunkIndex:m,totalChunks:L});let D=await Q.arrayBuffer(),U;if(p&&b?U=await he(this.cryptoObj,D,b):U=new Blob([D]),U.size>5243904)throw new f("Chunk too large (client-side). Check chunk size settings.");let a=await U.arrayBuffer(),N=await Ae(this.cryptoObj,a),W={"Content-Type":"application/octet-stream","X-Upload-ID":c,"X-Chunk-Index":String(m),"X-Chunk-Hash":N},le=`${F}/upload/chunk`;await this.attemptChunkUpload(le,{method:"POST",headers:W,body:U},{retries:H,backoffMs:I,maxBackoffMs:k,timeoutMs:w.chunkMs??6e4,signal:d,progress:E,chunkIndex:m,totalChunks:L,chunkSize:this.chunkSize,fileSizeBytes:C})}E({phase:"complete",text:"Finalising upload...",percent:100,processedBytes:C,totalBytes:C});let v=await te(this.fetchFn,`${F}/upload/complete`,{method:"POST",timeoutMs:w.completeMs??3e4,signal:d,headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({uploadId:c})});if(!v.res.ok){let K=v.json?.error||"Finalisation failed.";throw new z(K,{details:v.json||v.text})}let S=v.json?.id;if(!S||typeof S!="string")throw new z("Server did not return a valid file id.");let M=`${F}/${S}`;return p&&O&&(M+=`#${O}`),E({phase:"done",text:"Upload successful!",percent:100,processedBytes:C,totalBytes:C}),{downloadUrl:M,fileId:S,uploadId:c,baseUrl:F,...p&&O?{keyB64:O}:{}}}async downloadFile(e){let{host:t,port:o,secure:n,fileId:s,keyB64:l,onProgress:p,onData:i,signal:u,timeoutMs:d=6e4}=e,w=c=>{try{p&&p(c)}catch{}};if(!s||typeof s!="string")throw new f("File ID is required.");w({phase:"server-info",text:"Checking server...",processedBytes:0,totalBytes:0,percent:0});let P=await this.checkCompatibility({host:t,port:o,secure:n,timeoutMs:d,signal:u}),{baseUrl:E}=P;if(w({phase:"server-compat",text:P.message,processedBytes:0,totalBytes:0,percent:0}),!P.compatible)throw new f(P.message);w({phase:"metadata",text:"Fetching file info...",processedBytes:0,totalBytes:0,percent:0});let{signal:C,cleanup:G}=ee(u,d),F;try{let c=await this.fetchFn(`${E}/api/file/${s}/meta`,{method:"GET",headers:{Accept:"application/json"},signal:C});if(!c.ok)throw c.status===404?new z("File not found or has expired."):new z(`Failed to fetch file metadata (status ${c.status}).`);F=await c.json()}catch(c){throw c instanceof $?c:c instanceof Error&&c.name==="AbortError"?new V("Download cancelled."):new R("Could not fetch file metadata.",{cause:c})}finally{G()}let X=!!F.isEncrypted,x=F.sizeBytes||0;if(!i&&x>104857600){let c=Math.round(x/1048576),H=Math.round(104857600/(1024*1024));throw new f(`File is too large (${c}MB) to download without streaming. Provide an onData callback to stream files larger than ${H}MB.`)}let b,O;if(X){if(!l)throw new f("Decryption key is required for encrypted files.");if(!this.cryptoObj?.subtle)throw new f("Web Crypto API not available for decryption.");w({phase:"decrypting",text:"Preparing decryption...",processedBytes:0,totalBytes:0,percent:0});try{O=await ue(this.cryptoObj,l,this.base64),b=await me(this.cryptoObj,F.encryptedFilename,O,this.base64)}catch(c){throw new $("Failed to decrypt filename. Invalid key or corrupted data.",{code:"DECRYPT_FILENAME_FAILED",cause:c})}}else b=F.filename||"file";w({phase:"downloading",text:"Starting download...",percent:0,processedBytes:0,totalBytes:x});let{signal:Y,cleanup:L}=ee(u,d),A=0,h=[],y=!i;try{let c=await this.fetchFn(`${E}/api/file/${s}`,{method:"GET",signal:Y});if(!c.ok)throw new z(`Download failed (status ${c.status}).`);if(!c.body)throw new z("Streaming response not available.");let H=c.body.getReader();if(X&&O){let I=this.chunkSize+28,k=[],v=0,g=()=>{if(k.length===0)return new Uint8Array(0);if(k.length===1){let m=k[0];return k.length=0,v=0,m}let S=new Uint8Array(v),M=0;for(let m of k)S.set(m,M),M+=m.length;return k.length=0,v=0,S};for(;;){if(u?.aborted)throw new V("Download cancelled.");let{done:S,value:M}=await H.read();if(S)break;for(k.push(M),v+=M.length;v>=I;){let K=g(),J=K.subarray(0,I);if(K.length>I){let B=K.subarray(I);k.push(B),v=B.length}let Q=await re(this.cryptoObj,J,O),q=new Uint8Array(Q);y?h.push(q):await i(q)}A+=M.length;let m=x>0?Math.round(A/x*100):0;w({phase:"decrypting",text:`Downloading & decrypting... (${m}%)`,percent:m,processedBytes:A,totalBytes:x})}if(v>0){let S=g(),M=await re(this.cryptoObj,S,O),m=new Uint8Array(M);y?h.push(m):await i(m)}}else for(;;){if(u?.aborted)throw new V("Download cancelled.");let{done:I,value:k}=await H.read();if(I)break;y?h.push(k):await i(k),A+=k.length;let v=x>0?Math.round(A/x*100):0;w({phase:"downloading",text:`Downloading... (${v}%)`,percent:v,processedBytes:A,totalBytes:x})}}catch(c){throw c instanceof $?c:c instanceof Error&&c.name==="AbortError"?new V("Download cancelled."):new R("Download failed.",{cause:c})}finally{L()}w({phase:"complete",text:"Download complete!",percent:100,processedBytes:A,totalBytes:x});let T;if(y&&h.length>0){let c=h.reduce((I,k)=>I+k.length,0);T=new Uint8Array(c);let H=0;for(let I of h)T.set(I,H),H+=I.length}return{filename:b,receivedBytes:A,wasEncrypted:X,...T?{data:T}:{}}}async attemptChunkUpload(e,t,o){let{retries:n,backoffMs:s,maxBackoffMs:l,timeoutMs:p,signal:i,progress:u,chunkIndex:d,totalChunks:w,chunkSize:P,fileSizeBytes:E}=o,C=n,G=s,F=n;for(;;){if(i?.aborted)throw i.reason||new V;let{signal:X,cleanup:x}=ee(i,p);try{let b=await this.fetchFn(e,{...t,signal:X});if(b.ok)return;let O=await b.text().catch(()=>"");throw new z(`Chunk ${d+1} failed (HTTP ${b.status}).`,{details:{status:b.status,bodySnippet:O.slice(0,120)}})}catch(b){if(x(),b instanceof Error&&(b.name==="AbortError"||b.code==="ABORT_ERR"))throw b;if(i?.aborted)throw i.reason||new V;if(C<=0)throw b instanceof $?b:new R("Chunk upload failed.",{cause:b});let O=F-C+1,Y=d*P,L=d/w*100,A=G,h=100;for(;A>0;){let y=(A/1e3).toFixed(1);u({phase:"retry-wait",text:`Chunk upload failed. Retrying in ${y}s... (${O}/${F})`,percent:L,processedBytes:Y,totalBytes:E,chunkIndex:d,totalChunks:w}),await ce(Math.min(h,A),i),A-=h}u({phase:"retry",text:`Chunk upload failed. Retrying now... (${O}/${F})`,percent:L,processedBytes:Y,totalBytes:E,chunkIndex:d,totalChunks:w}),C-=1,G=Math.min(G*2,l);continue}finally{x()}}}};function Oe(r){let e=String(r||"").toLowerCase();return e==="localhost"||e==="127.0.0.1"||e==="::1"}function je(r,e){return!!e||Oe(r||"")}function ye(r){let e=r||ie(),t="ABCDEFGHJKLMNPQRSTUVWXYZ";if(e){let s=new Uint8Array(8);e.getRandomValues(s);let l="";for(let i=0;i<4;i++)l+=t[s[i]%t.length];let p="";for(let i=4;i<8;i++)p+=(s[i]%10).toString();return`${l}-${p}`}let o="";for(let s=0;s<4;s++)o+=t[Math.floor(Math.random()*t.length)];let n="";for(let s=0;s<4;s++)n+=Math.floor(Math.random()*10);return`${o}-${n}`}function ge(r){return/^[A-Z]{4}-\d{4}$/.test(String(r||"").trim())}function oe(r,e){return{path:r.peerjsPath??e?.peerjsPath??"/peerjs",iceServers:r.iceServers??e?.iceServers??[]}}function ne(r={}){let{host:e,port:t,peerjsPath:o="/peerjs",secure:n=!1,iceServers:s=[]}=r,l={host:e,path:o,secure:n,config:{iceServers:s},debug:0};return t&&(l.port=t),l}async function we(r){let{code:e,codeGenerator:t,maxAttempts:o,buildPeer:n,onCode:s}=r,l=e||t(),p=null,i=null;for(let u=0;u<o;u++){s?.(l,u);try{return p=await new Promise((d,w)=>{let P=n(l);P.on("open",()=>d(P)),P.on("error",E=>{try{P.destroy()}catch{}w(E)})}),{peer:p,code:l}}catch(d){i=d,l=t()}}throw i||new R("Could not establish PeerJS connection.")}function it(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}async function Ne(r){let{file:e,Peer:t,serverInfo:o,host:n,port:s,peerjsPath:l,secure:p=!1,iceServers:i,codeGenerator:u,cryptoObj:d,maxAttempts:w=4,chunkSize:P=256*1024,endAckTimeoutMs:E=15e3,bufferHighWaterMark:C=8*1024*1024,bufferLowWaterMark:G=2*1024*1024,heartbeatIntervalMs:F=5e3,onCode:X,onStatus:x,onProgress:b,onComplete:O,onError:Y,onDisconnect:L}=r;if(!e)throw new f("File is missing.");if(!t)throw new f("PeerJS Peer constructor is required. Install peerjs and pass it as the Peer option.");let A=o?.capabilities?.p2p;if(o&&!A?.enabled)throw new f("Direct transfer is disabled on this server.");let{path:h,iceServers:y}=oe({peerjsPath:l,iceServers:i},A),T=ne({host:n,port:s,peerjsPath:h,secure:p,iceServers:y}),c=u||(()=>ye(d)),H=a=>new t(a,T),{peer:I,code:k}=await we({code:null,codeGenerator:c,maxAttempts:w,buildPeer:H,onCode:X}),v=it(),g="listening",S=null,M=0,m=null,K=a=>{let N=Number.isFinite(a.total)&&a.total>0?a.total:e.size,W=Math.min(Number(a.received)||0,N||0),le=N?W/N*100:0;b?.({processedBytes:W,totalBytes:N,percent:le})},J=a=>{g==="closed"||g==="completed"||(g="closed",Y?.(a),q())},Q=()=>{g==="finishing"&&(g="completed",O?.(),q())},q=()=>{m&&(clearInterval(m),m=null),typeof window<"u"&&window.removeEventListener("beforeunload",B);try{S?.close()}catch{}try{I.destroy()}catch{}},B=()=>{try{S?.send({t:"error",message:"Sender closed the connection."})}catch{}D()};typeof window<"u"&&window.addEventListener("beforeunload",B);let D=()=>{g!=="closed"&&(g="closed",q())},U=()=>g==="closed";return I.on("connection",a=>{if(g==="closed")return;if(S){let j=S.open!==!1;if(j&&g==="transferring"){try{a.send({t:"error",message:"Transfer already in progress."})}catch{}try{a.close()}catch{}return}else if(j){try{a.send({t:"error",message:"Another receiver is already connected."})}catch{}try{a.close()}catch{}return}else{try{S.close()}catch{}S=null,g="listening",M=0}}S=a,g="negotiating",x?.({phase:"waiting",message:"Connected. Waiting for receiver to accept..."});let N=null,W=null,le=new Promise(j=>{N=j}),Ye=new Promise(j=>{W=j});a.on("data",j=>{if(!j||typeof j!="object"||j instanceof ArrayBuffer||ArrayBuffer.isView(j))return;let _=j;if(_.t){if(_.t==="ready"){x?.({phase:"transferring",message:"Receiver accepted. Starting transfer..."}),N?.();return}if(_.t==="progress"){K({received:_.received||0,total:_.total||0});return}if(_.t==="ack"&&_.phase==="end"){W?.(_);return}_.t!=="pong"&&_.t==="error"&&J(new R(_.message||"Receiver reported an error."))}}),a.on("open",async()=>{try{if(U())return;a.send({t:"meta",sessionId:v,name:e.name,size:e.size,mime:e.type||"application/octet-stream"});let j=e.size,_=a._dc;if(_&&Number.isFinite(G))try{_.bufferedAmountLowThreshold=G}catch{}if(await le,U())return;F>0&&(m=setInterval(()=>{if(g==="transferring"||g==="finishing")try{a.send({t:"ping"})}catch{}},F)),g="transferring";for(let Pe=0;Pe<j;Pe+=P){if(U())return;let ze=await e.slice(Pe,Pe+P).arrayBuffer();if(a.send(ze),M+=ze.byteLength,_)for(;_.bufferedAmount>C;)await new Promise(Ge=>{let Xe=setTimeout(Ge,60);try{_.addEventListener("bufferedamountlow",()=>{clearTimeout(Xe),Ge()},{once:!0})}catch{}})}if(U())return;g="finishing",a.send({t:"end"});let qe=Number.isFinite(E)?Math.max(E,Math.ceil(e.size/(1024*1024))*1e3):null,Te=await Promise.race([Ye,ce(qe||15e3).catch(()=>null)]);if(U())return;if(!Te||typeof Te!="object")throw new R("Receiver did not confirm completion.");let $e=Te,be=Number($e.total)||e.size,Le=Number($e.received)||0;if(be&&Le<be)throw new R("Receiver reported an incomplete transfer.");K({received:Le||be,total:be}),Q()}catch(j){J(j)}}),a.on("error",j=>{J(j)}),a.on("close",()=>{if(g==="closed"||g==="completed"){q();return}g==="transferring"||g==="finishing"?J(new R("Receiver disconnected before transfer completed.")):(S=null,g="listening",M=0,L?.())})}),{peer:I,code:k,sessionId:v,stop:D,getStatus:()=>g,getBytesSent:()=>M,getConnectedPeerId:()=>S&&S.peer||null}}async function _e(r){let{code:e,Peer:t,serverInfo:o,host:n,port:s,peerjsPath:l,secure:p=!1,iceServers:i,autoReady:u=!0,watchdogTimeoutMs:d=15e3,onStatus:w,onMeta:P,onData:E,onProgress:C,onComplete:G,onError:F,onDisconnect:X}=r;if(!e)throw new f("No sharing code was provided.");if(!t)throw new f("PeerJS Peer constructor is required. Install peerjs and pass it as the Peer option.");let x=o?.capabilities?.p2p;if(o&&!x?.enabled)throw new f("Direct transfer is disabled on this server.");let b=String(e).trim().replace(/\s+/g,"").toUpperCase();if(!ge(b))throw new f("Invalid direct transfer code.");let{path:O,iceServers:Y}=oe({peerjsPath:l,iceServers:i},x),L=ne({host:n,port:s,peerjsPath:O,secure:p,iceServers:Y}),A=new t(void 0,L),h="initializing",y=0,T=0,c=null,H=0,I=120,k=Promise.resolve(),v=null,g=null,S=()=>{d<=0||(v&&clearTimeout(v),v=setTimeout(()=>{h==="transferring"&&m(new R("Connection timed out (no data received)."))},d))},M=()=>{v&&(clearTimeout(v),v=null)},m=B=>{h==="closed"||h==="completed"||(h="closed",F?.(B),J())},K=B=>{h==="transferring"&&(h="completed",G?.(B),J())},J=()=>{M(),typeof window<"u"&&window.removeEventListener("beforeunload",Q);try{A.destroy()}catch{}},Q=()=>{try{g?.send({t:"error",message:"Receiver closed the connection."})}catch{}q()};typeof window<"u"&&window.addEventListener("beforeunload",Q);let q=()=>{h!=="closed"&&(h="closed",J())};return A.on("error",B=>{m(B)}),A.on("open",()=>{h="connecting";let B=A.connect(b,{reliable:!0});g=B,B.on("open",()=>{h="negotiating",w?.({phase:"connected",message:"Waiting for file details..."})}),B.on("data",async D=>{try{if(S(),D&&typeof D=="object"&&!(D instanceof ArrayBuffer)&&!ArrayBuffer.isView(D)){let a=D;if(a.t==="meta"){if(c&&a.sessionId&&a.sessionId!==c){try{B.send({t:"error",message:"Busy with another session."})}catch{}return}a.sessionId&&(c=a.sessionId);let N=String(a.name||"file");y=Number(a.size)||0,T=0,k=Promise.resolve();let W=()=>{h="transferring",S();try{B.send({t:"ready"})}catch{}};u?(P?.({name:N,total:y}),C?.({processedBytes:T,totalBytes:y,percent:0}),W()):(P?.({name:N,total:y,sendReady:W}),C?.({processedBytes:T,totalBytes:y,percent:0}));return}if(a.t==="ping"){try{B.send({t:"pong"})}catch{}return}if(a.t==="end"){if(M(),await k,y&&T<y){let N=new R("Transfer ended before the full file was received.");try{B.send({t:"error",message:N.message})}catch{}throw N}try{B.send({t:"ack",phase:"end",received:T,total:y})}catch{}K({received:T,total:y});return}if(a.t==="error")throw new R(a.message||"Sender reported an error.");return}let U;if(D instanceof ArrayBuffer)U=Promise.resolve(new Uint8Array(D));else if(ArrayBuffer.isView(D))U=Promise.resolve(new Uint8Array(D.buffer,D.byteOffset,D.byteLength));else if(typeof Blob<"u"&&D instanceof Blob)U=D.arrayBuffer().then(a=>new Uint8Array(a));else return;k=k.then(async()=>{let a=await U;E&&await E(a),T+=a.byteLength;let N=y?Math.min(100,T/y*100):0;C?.({processedBytes:T,totalBytes:y,percent:N});let W=Date.now();if(T===y||W-H>=I){H=W;try{B.send({t:"progress",received:T,total:y})}catch{}}}).catch(a=>{try{B.send({t:"error",message:a?.message||"Receiver write failed."})}catch{}m(a)})}catch(U){m(U)}}),B.on("close",()=>{if(h==="closed"||h==="completed"){J();return}h==="transferring"?m(new R("Sender disconnected during transfer.")):h==="negotiating"?(h="closed",J(),X?.()):m(new R("Sender disconnected before file details were received."))})}),{peer:A,stop:q,getStatus:()=>h,getBytesReceived:()=>T,getTotalBytes:()=>y,getSessionId:()=>c}}return ot(at);})();
|
|
2
2
|
if(typeof window!=="undefined"){window.DropgateCore=DropgateCore;}
|
|
3
3
|
//# sourceMappingURL=index.browser.js.map
|