@dropgate/core 2.1.0 → 2.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  <div align="center">
10
10
 
11
11
  ![license](https://img.shields.io/badge/license-Apache--2.0-blue?style=flat-square)
12
- ![version](https://img.shields.io/badge/version-2.1.0-brightgreen?style=flat-square)
12
+ ![version](https://img.shields.io/badge/version-2.2.0-brightgreen?style=flat-square)
13
13
  ![typescript](https://img.shields.io/badge/TypeScript-5.0+-blue?style=flat-square)
14
14
 
15
15
  [![discord](https://img.shields.io/discord/667479986214666272?logo=discord&logoColor=white&style=flat-square)](https://diamonddigital.dev/discord)
@@ -52,7 +52,7 @@ The package ships with multiple build targets:
52
52
  ```javascript
53
53
  import { DropgateClient } from '@dropgate/core';
54
54
 
55
- const client = new DropgateClient({ clientVersion: '2.1.0' });
55
+ const client = new DropgateClient({ clientVersion: '2.2.0' });
56
56
 
57
57
  const result = await client.uploadFile({
58
58
  host: 'dropgate.link',
@@ -90,7 +90,7 @@ console.log('P2P enabled:', serverInfo.capabilities?.p2p?.enabled);
90
90
  ```javascript
91
91
  import { DropgateClient } from '@dropgate/core';
92
92
 
93
- const client = new DropgateClient({ clientVersion: '2.1.0' });
93
+ const client = new DropgateClient({ clientVersion: '2.2.0' });
94
94
 
95
95
  // checkCompatibility fetches server info internally and compares versions
96
96
  const compat = await client.checkCompatibility({
@@ -202,7 +202,7 @@ const session = await startP2PReceive({
202
202
  ```javascript
203
203
  import { DropgateClient } from '@dropgate/core';
204
204
 
205
- const client = new DropgateClient({ clientVersion: '2.1.0' });
205
+ const client = new DropgateClient({ clientVersion: '2.2.0' });
206
206
 
207
207
  // Download with streaming (for large files)
208
208
  const result = await client.downloadFile({
@@ -1,3 +1,3 @@
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);})();
1
+ "use strict";var DropgateCore=(()=>{var je=Object.defineProperty;var Qe=Object.getOwnPropertyDescriptor;var et=Object.getOwnPropertyNames;var tt=Object.prototype.hasOwnProperty;var rt=(r,e)=>{for(var t in e)je(r,t,{get:e[t],enumerable:!0})},nt=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of et(e))!tt.call(r,o)&&o!==t&&je(r,o,{get:()=>e[o],enumerable:!(n=Qe(e,o))||n.enumerable});return r};var ot=r=>nt(je({},"__esModule",{value:!0}),r);var ct={};rt(ct,{AES_GCM_IV_BYTES:()=>ue,AES_GCM_TAG_BYTES:()=>He,DEFAULT_CHUNK_SIZE:()=>Ce,DropgateAbortError:()=>J,DropgateClient:()=>Ie,DropgateError:()=>L,DropgateNetworkError:()=>M,DropgateProtocolError:()=>V,DropgateTimeoutError:()=>ae,DropgateValidationError:()=>f,ENCRYPTION_OVERHEAD_PER_CHUNK:()=>Ae,arrayBufferToBase64:()=>le,base64ToBytes:()=>Ye,buildBaseUrl:()=>xe,buildPeerOptions:()=>se,bytesToBase64:()=>_e,createPeerWithRetries:()=>ve,decryptChunk:()=>ne,decryptFilenameFromBase64:()=>ge,encryptFilenameToBase64:()=>Oe,encryptToBlob:()=>we,estimateTotalUploadSizeBytes:()=>Te,exportKeyBase64:()=>Fe,fetchJson:()=>te,generateAesGcmKey:()=>Re,generateP2PCode:()=>be,getDefaultBase64:()=>Q,getDefaultCrypto:()=>ce,getDefaultFetch:()=>me,getServerInfo:()=>$e,importKeyFromBase64:()=>ye,isLocalhostHostname:()=>Me,isP2PCodeLike:()=>Pe,isSecureContextForP2P:()=>ze,lifetimeToMs:()=>qe,makeAbortSignal:()=>re,parseSemverMajorMinor:()=>he,parseServerUrl:()=>Xe,resolvePeerConfig:()=>oe,sha256Hex:()=>ke,sleep:()=>pe,startP2PReceive:()=>Ge,startP2PSend:()=>Le,validatePlainFilename:()=>Ee});var Ce=5242880,ue=12,He=16,Ae=28;var L=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 L{constructor(e,t={}){super(e,{...t,code:t.code||"VALIDATION_ERROR"})}},M=class extends L{constructor(e,t={}){super(e,{...t,code:t.code||"NETWORK_ERROR"})}},V=class extends L{constructor(e,t={}){super(e,{...t,code:t.code||"PROTOCOL_ERROR"})}},J=class extends L{constructor(e="Operation aborted"){super(e,{code:"ABORT_ERROR"}),this.name="AbortError"}},ae=class extends L{constructor(e="Request timed out"){super(e,{code:"TIMEOUT_ERROR"}),this.name="TimeoutError"}};function Q(){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 n=0;n<e.length;n++)t[n]=e.charCodeAt(n);return t}};throw new Error("No Base64 implementation available. Provide a Base64Adapter via options.")}function ce(){return globalThis.crypto}function me(){return globalThis.fetch?.bind(globalThis)}var Ne=null;function We(r){return r||(Ne||(Ne=Q()),Ne)}function _e(r,e){return We(e).encode(r)}function le(r,e){return _e(new Uint8Array(r),e)}function Ye(r,e){return We(e).decode(r)}var st={minutes:6e4,hours:36e5,days:864e5};function qe(r,e){let t=String(e||"").toLowerCase(),n=Number(r);if(t==="unlimited"||!Number.isFinite(n)||n<=0)return 0;let o=st[t];return o?Math.round(n*o):0}function he(r){let e=String(r||"").split(".").map(o=>Number(o)),t=Number.isFinite(e[0])?e[0]:0,n=Number.isFinite(e[1])?e[1]:0;return{major:t,minor:n}}function Ee(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 Xe(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 xe(r){let{host:e,port:t,secure:n}=r;if(!e||typeof e!="string")throw new f("Server host is required.");let o=n===!1?"http":"https",s=t?`:${t}`:"";return`${o}://${e}${s}`}function pe(r,e){return new Promise((t,n)=>{if(e?.aborted)return n(e.reason||new J);let o=setTimeout(t,r);e&&e.addEventListener("abort",()=>{clearTimeout(o),n(e.reason||new J)},{once:!0})})}function re(r,e){let t=new AbortController,n=null,o=s=>{t.signal.aborted||t.abort(s)};return r&&(r.aborted?o(r.reason):r.addEventListener("abort",()=>o(r.reason),{once:!0})),Number.isFinite(e)&&e>0&&(n=setTimeout(()=>{o(new ae)},e)),{signal:t.signal,cleanup:()=>{n&&clearTimeout(n)}}}async function te(r,e,t={}){let{timeoutMs:n,signal:o,...s}=t,{signal:p,cleanup:d}=re(o,n);try{let i=await r(e,{...s,signal:p}),u=await i.text(),h=null;try{h=u?JSON.parse(u):null}catch{}return{res:i,json:h,text:u}}finally{d()}}async function ye(r,e,t){let o=(t||Q()).decode(e),s=new Uint8Array(o).buffer;return r.subtle.importKey("raw",s,{name:"AES-GCM"},!0,["decrypt"])}async function ne(r,e,t){let n=e.slice(0,12),o=e.slice(12);return r.subtle.decrypt({name:"AES-GCM",iv:n},t,o)}async function ge(r,e,t,n){let s=(n||Q()).decode(e),p=await ne(r,s,t);return new TextDecoder().decode(p)}async function ke(r,e){let t=await r.subtle.digest("SHA-256",e),n=new Uint8Array(t),o="";for(let s=0;s<n.length;s++)o+=n[s].toString(16).padStart(2,"0");return o}async function Re(r){return r.subtle.generateKey({name:"AES-GCM",length:256},!0,["encrypt","decrypt"])}async function Fe(r,e){let t=await r.subtle.exportKey("raw",e);return le(t)}async function we(r,e,t){let n=r.getRandomValues(new Uint8Array(12)),o=await r.subtle.encrypt({name:"AES-GCM",iv:n},t,e);return new Blob([n,new Uint8Array(o)])}async function Oe(r,e,t){let n=new TextEncoder().encode(String(e)),s=await(await we(r,n.buffer,t)).arrayBuffer();return le(s)}function Te(r,e,t){let n=Number(r)||0;return t?n+(Number(e)||0)*28:n}async function $e(r){let{host:e,port:t,secure:n,timeoutMs:o=5e3,signal:s,fetchFn:p}=r,d=p||me();if(!d)throw new f("No fetch() implementation found.");let i=xe({host:e,port:t,secure:n});try{let{res:u,json:h}=await te(d,`${i}/api/info`,{method:"GET",timeoutMs:o,signal:s,headers:{Accept:"application/json"}});if(u.ok&&h&&typeof h=="object"&&"version"in h)return{baseUrl:i,serverInfo:h};throw new V(`Server info request failed (status ${u.status}).`)}catch(u){throw u instanceof L?u:new M("Could not reach server /api/info.",{cause:u})}}var Ie=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||me();if(!t)throw new f("No fetch() implementation found.");this.fetchFn=t;let n=e.cryptoObj||ce();if(!n)throw new f("No crypto implementation found.");this.cryptoObj=n,this.base64=e.base64||Q(),this.logger=e.logger||null}async resolveShareTarget(e,t){let{timeoutMs:n=5e3,signal:o}=t,s=await this.checkCompatibility(t);if(!s.compatible)throw new f(s.message);let{baseUrl:p}=s,{res:d,json:i}=await te(this.fetchFn,`${p}/api/resolve`,{method:"POST",timeoutMs:n,signal:o,headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({value:e})});if(!d.ok){let u=(i&&typeof i=="object"&&"error"in i?i.error:null)||`Share lookup failed (status ${d.status}).`;throw new V(u,{details:i})}return i||{valid:!1,reason:"Unknown response."}}async checkCompatibility(e){let t,n;try{let i=await $e({...e,fetchFn:this.fetchFn});t=i.baseUrl,n=i.serverInfo}catch(i){throw i instanceof L?i:new M("Could not connect to the server.",{cause:i})}let o=String(n?.version||"0.0.0"),s=String(this.clientVersion||"0.0.0"),p=he(s),d=he(o);return p.major!==d.major?{compatible:!1,clientVersion:s,serverVersion:o,message:`Incompatible versions. Client v${s}, Server v${o}${n?.name?` (${n.name})`:""}.`,serverInfo:n,baseUrl:t}:p.minor>d.minor?{compatible:!0,clientVersion:s,serverVersion:o,message:`Client (v${s}) is newer than Server (v${o})${n?.name?` (${n.name})`:""}. Some features may not work.`,serverInfo:n,baseUrl:t}:{compatible:!0,clientVersion:s,serverVersion:o,message:`Server: v${o}, Client: v${s}${n?.name?` (${n.name})`:""}.`,serverInfo:n,baseUrl:t}}validateUploadInputs(e){let{file:t,lifetimeMs:n,encrypt:o,serverInfo:s}=e,p=s?.capabilities?.upload;if(!p||!p.enabled)throw new f("Server does not support file uploads.");let d=Number(t?.size||0);if(!t||!Number.isFinite(d)||d<=0)throw new f("File is missing or invalid.");let i=Number(p.maxSizeMB);if(Number.isFinite(i)&&i>0){let S=i*1e3*1e3,k=Math.ceil(d/this.chunkSize);if(Te(d,k,!!o)>S){let G=o?`File too large once encryption overhead is included. Server limit: ${i} MB.`:`File too large. Server limit: ${i} MB.`;throw new f(G)}}let u=Number(p.maxLifetimeHours),h=Number(n);if(!Number.isFinite(h)||h<0||!Number.isInteger(h))throw new f("Invalid lifetime. Must be a non-negative integer (milliseconds).");if(Number.isFinite(u)&&u>0){let S=Math.round(u*60*60*1e3);if(h===0)throw new f(`Server does not allow unlimited file lifetime. Max: ${u} hours.`);if(h>S)throw new f(`File lifetime too long. Server limit: ${u} hours.`)}if(o&&!p.e2ee)throw new f("Server does not support end-to-end encryption.");return!0}async uploadFile(e){let{host:t,port:n,secure:o,file:s,lifetimeMs:p,encrypt:d,filenameOverride:i,onProgress:u,onCancel:h,signal:S,timeouts:k={},retry:O={}}=e,G=S?null:new AbortController,_=S||G?.signal,R="initializing",Y=null,B=null,F=(async()=>{try{let w=y=>{try{u&&u(y)}catch{}};if(!this.cryptoObj?.subtle)throw new f("Web Crypto API not available (crypto.subtle).");let T=s.size;w({phase:"server-info",text:"Checking server...",percent:0,processedBytes:0,totalBytes:T});let C=await this.checkCompatibility({host:t,port:n,secure:o,timeoutMs:k.serverInfoMs??5e3,signal:_}),{baseUrl:A,serverInfo:m}=C;if(w({phase:"server-compat",text:C.message,percent:0,processedBytes:0,totalBytes:T}),!C.compatible)throw new f(C.message);let P=i??s.name??"file";d||Ee(P),this.validateUploadInputs({file:s,lifetimeMs:p,encrypt:d,serverInfo:m});let l=null,D=null,U=P;if(d){w({phase:"crypto",text:"Generating encryption key...",percent:0,processedBytes:0,totalBytes:T});try{l=await Re(this.cryptoObj),D=await Fe(this.cryptoObj,l),U=await Oe(this.cryptoObj,P,l)}catch(y){throw new L("Failed to prepare encryption.",{code:"CRYPTO_PREP_FAILED",cause:y})}}let g=Math.ceil(s.size/this.chunkSize),j=Te(s.size,g,d);w({phase:"init",text:"Reserving server storage...",percent:0,processedBytes:0,totalBytes:T});let W={filename:U,lifetime:p,isEncrypted:!!d,totalSize:j,totalChunks:g},c=await te(this.fetchFn,`${A}/upload/init`,{method:"POST",timeoutMs:k.initMs??15e3,signal:_,headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(W)});if(!c.res.ok){let a=c.json?.error||`Server initialisation failed: ${c.res.status}`;throw new V(a,{details:c.json||c.text})}let E=c.json?.uploadId;if(!E||typeof E!="string")throw new V("Server did not return a valid uploadId.");Y=E,B=A,R="uploading";let K=Number.isFinite(O.retries)?O.retries:5,ee=Number.isFinite(O.backoffMs)?O.backoffMs:1e3,H=Number.isFinite(O.maxBackoffMs)?O.maxBackoffMs:3e4;for(let y=0;y<g;y++){if(_?.aborted)throw _.reason||new J;let a=y*this.chunkSize,z=Math.min(a+this.chunkSize,s.size),Z=s.slice(a,z),de=y/g*100,De=y*this.chunkSize;w({phase:"chunk",text:`Uploading chunk ${y+1} of ${g}...`,percent:de,processedBytes:De,totalBytes:T,chunkIndex:y,totalChunks:g});let I=await Z.arrayBuffer(),x;if(d&&l?x=await we(this.cryptoObj,I,l):x=new Blob([I]),x.size>5243904)throw new f("Chunk too large (client-side). Check chunk size settings.");let Ue=await x.arrayBuffer(),fe=await ke(this.cryptoObj,Ue),Se={"Content-Type":"application/octet-stream","X-Upload-ID":E,"X-Chunk-Index":String(y),"X-Chunk-Hash":fe},ie=`${A}/upload/chunk`;await this.attemptChunkUpload(ie,{method:"POST",headers:Se,body:x},{retries:K,backoffMs:ee,maxBackoffMs:H,timeoutMs:k.chunkMs??6e4,signal:_,progress:w,chunkIndex:y,totalChunks:g,chunkSize:this.chunkSize,fileSizeBytes:T})}w({phase:"complete",text:"Finalising upload...",percent:100,processedBytes:T,totalBytes:T}),R="completing";let q=await te(this.fetchFn,`${A}/upload/complete`,{method:"POST",timeoutMs:k.completeMs??3e4,signal:_,headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({uploadId:E})});if(!q.res.ok){let a=q.json?.error||"Finalisation failed.";throw new V(a,{details:q.json||q.text})}let b=q.json?.id;if(!b||typeof b!="string")throw new V("Server did not return a valid file id.");let N=`${A}/${b}`;return d&&D&&(N+=`#${D}`),w({phase:"done",text:"Upload successful!",percent:100,processedBytes:T,totalBytes:T}),R="completed",{downloadUrl:N,fileId:b,uploadId:E,baseUrl:A,...d&&D?{keyB64:D}:{}}}catch(w){throw w instanceof Error&&(w.name==="AbortError"||w.message?.includes("abort"))?(R="cancelled",h?.()):R="error",w}})(),$=async(w,T)=>{try{await te(this.fetchFn,`${T}/upload/cancel`,{method:"POST",timeoutMs:5e3,headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({uploadId:w})})}catch{}};return{result:F,cancel:w=>{R==="completed"||R==="cancelled"||(R="cancelled",Y&&B&&$(Y,B).catch(()=>{}),G?.abort(new J(w||"Upload cancelled by user.")))},getStatus:()=>R}}async downloadFile(e){let{host:t,port:n,secure:o,fileId:s,keyB64:p,onProgress:d,onData:i,signal:u,timeoutMs:h=6e4}=e,S=l=>{try{d&&d(l)}catch{}};if(!s||typeof s!="string")throw new f("File ID is required.");S({phase:"server-info",text:"Checking server...",processedBytes:0,totalBytes:0,percent:0});let k=await this.checkCompatibility({host:t,port:n,secure:o,timeoutMs:h,signal:u}),{baseUrl:O}=k;if(S({phase:"server-compat",text:k.message,processedBytes:0,totalBytes:0,percent:0}),!k.compatible)throw new f(k.message);S({phase:"metadata",text:"Fetching file info...",processedBytes:0,totalBytes:0,percent:0});let{signal:G,cleanup:_}=re(u,h),R;try{let l=await this.fetchFn(`${O}/api/file/${s}/meta`,{method:"GET",headers:{Accept:"application/json"},signal:G});if(!l.ok)throw l.status===404?new V("File not found or has expired."):new V(`Failed to fetch file metadata (status ${l.status}).`);R=await l.json()}catch(l){throw l instanceof L?l:l instanceof Error&&l.name==="AbortError"?new J("Download cancelled."):new M("Could not fetch file metadata.",{cause:l})}finally{_()}let Y=!!R.isEncrypted,B=R.sizeBytes||0;if(!i&&B>104857600){let l=Math.round(B/1048576),D=Math.round(104857600/(1024*1024));throw new f(`File is too large (${l}MB) to download without streaming. Provide an onData callback to stream files larger than ${D}MB.`)}let F,$;if(Y){if(!p)throw new f("Decryption key is required for encrypted files.");if(!this.cryptoObj?.subtle)throw new f("Web Crypto API not available for decryption.");S({phase:"decrypting",text:"Preparing decryption...",processedBytes:0,totalBytes:0,percent:0});try{$=await ye(this.cryptoObj,p,this.base64),F=await ge(this.cryptoObj,R.encryptedFilename,$,this.base64)}catch(l){throw new L("Failed to decrypt filename. Invalid key or corrupted data.",{code:"DECRYPT_FILENAME_FAILED",cause:l})}}else F=R.filename||"file";S({phase:"downloading",text:"Starting download...",percent:0,processedBytes:0,totalBytes:B});let{signal:w,cleanup:T}=re(u,h),C=0,A=[],m=!i;try{let l=await this.fetchFn(`${O}/api/file/${s}`,{method:"GET",signal:w});if(!l.ok)throw new V(`Download failed (status ${l.status}).`);if(!l.body)throw new V("Streaming response not available.");let D=l.body.getReader();if(Y&&$){let U=this.chunkSize+28,g=[],j=0,W=()=>{if(g.length===0)return new Uint8Array(0);if(g.length===1){let E=g[0];return g.length=0,j=0,E}let c=new Uint8Array(j),v=0;for(let E of g)c.set(E,v),v+=E.length;return g.length=0,j=0,c};for(;;){if(u?.aborted)throw new J("Download cancelled.");let{done:c,value:v}=await D.read();if(c)break;for(g.push(v),j+=v.length;j>=U;){let K=W(),ee=K.subarray(0,U);if(K.length>U){let X=K.subarray(U);g.push(X),j=X.length}let H=await ne(this.cryptoObj,ee,$),q=new Uint8Array(H);m?A.push(q):await i(q)}C+=v.length;let E=B>0?Math.round(C/B*100):0;S({phase:"decrypting",text:`Downloading & decrypting... (${E}%)`,percent:E,processedBytes:C,totalBytes:B})}if(j>0){let c=W(),v=await ne(this.cryptoObj,c,$),E=new Uint8Array(v);m?A.push(E):await i(E)}}else for(;;){if(u?.aborted)throw new J("Download cancelled.");let{done:U,value:g}=await D.read();if(U)break;m?A.push(g):await i(g),C+=g.length;let j=B>0?Math.round(C/B*100):0;S({phase:"downloading",text:`Downloading... (${j}%)`,percent:j,processedBytes:C,totalBytes:B})}}catch(l){throw l instanceof L?l:l instanceof Error&&l.name==="AbortError"?new J("Download cancelled."):new M("Download failed.",{cause:l})}finally{T()}S({phase:"complete",text:"Download complete!",percent:100,processedBytes:C,totalBytes:B});let P;if(m&&A.length>0){let l=A.reduce((U,g)=>U+g.length,0);P=new Uint8Array(l);let D=0;for(let U of A)P.set(U,D),D+=U.length}return{filename:F,receivedBytes:C,wasEncrypted:Y,...P?{data:P}:{}}}async attemptChunkUpload(e,t,n){let{retries:o,backoffMs:s,maxBackoffMs:p,timeoutMs:d,signal:i,progress:u,chunkIndex:h,totalChunks:S,chunkSize:k,fileSizeBytes:O}=n,G=o,_=s,R=o;for(;;){if(i?.aborted)throw i.reason||new J;let{signal:Y,cleanup:B}=re(i,d);try{let F=await this.fetchFn(e,{...t,signal:Y});if(F.ok)return;let $=await F.text().catch(()=>"");throw new V(`Chunk ${h+1} failed (HTTP ${F.status}).`,{details:{status:F.status,bodySnippet:$.slice(0,120)}})}catch(F){if(B(),F instanceof Error&&(F.name==="AbortError"||F.code==="ABORT_ERR"))throw F;if(i?.aborted)throw i.reason||new J;if(G<=0)throw F instanceof L?F:new M("Chunk upload failed.",{cause:F});let $=R-G+1,w=h*k,T=h/S*100,C=_,A=100;for(;C>0;){let m=(C/1e3).toFixed(1);u({phase:"retry-wait",text:`Chunk upload failed. Retrying in ${m}s... (${$}/${R})`,percent:T,processedBytes:w,totalBytes:O,chunkIndex:h,totalChunks:S}),await pe(Math.min(A,C),i),C-=A}u({phase:"retry",text:`Chunk upload failed. Retrying now... (${$}/${R})`,percent:T,processedBytes:w,totalBytes:O,chunkIndex:h,totalChunks:S}),G-=1,_=Math.min(_*2,p);continue}finally{B()}}}};function Me(r){let e=String(r||"").toLowerCase();return e==="localhost"||e==="127.0.0.1"||e==="::1"}function ze(r,e){return!!e||Me(r||"")}function be(r){let e=r||ce(),t="ABCDEFGHJKLMNPQRSTUVWXYZ";if(e){let s=new Uint8Array(8);e.getRandomValues(s);let p="";for(let i=0;i<4;i++)p+=t[s[i]%t.length];let d="";for(let i=4;i<8;i++)d+=(s[i]%10).toString();return`${p}-${d}`}let n="";for(let s=0;s<4;s++)n+=t[Math.floor(Math.random()*t.length)];let o="";for(let s=0;s<4;s++)o+=Math.floor(Math.random()*10);return`${n}-${o}`}function Pe(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 se(r={}){let{host:e,port:t,peerjsPath:n="/peerjs",secure:o=!1,iceServers:s=[]}=r,p={host:e,path:n,secure:o,config:{iceServers:s},debug:0};return t&&(p.port=t),p}async function ve(r){let{code:e,codeGenerator:t,maxAttempts:n,buildPeer:o,onCode:s}=r,p=e||t(),d=null,i=null;for(let u=0;u<n;u++){s?.(p,u);try{return d=await new Promise((h,S)=>{let k=o(p);k.on("open",()=>h(k)),k.on("error",O=>{try{k.destroy()}catch{}S(O)})}),{peer:d,code:p}}catch(h){i=h,p=t()}}throw i||new M("Could not establish PeerJS connection.")}function at(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}async function Le(r){let{file:e,Peer:t,serverInfo:n,host:o,port:s,peerjsPath:p,secure:d=!1,iceServers:i,codeGenerator:u,cryptoObj:h,maxAttempts:S=4,chunkSize:k=256*1024,endAckTimeoutMs:O=15e3,bufferHighWaterMark:G=8*1024*1024,bufferLowWaterMark:_=2*1024*1024,heartbeatIntervalMs:R=5e3,onCode:Y,onStatus:B,onProgress:F,onComplete:$,onError:w,onDisconnect:T,onCancel:C}=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=n?.capabilities?.p2p;if(n&&!A?.enabled)throw new f("Direct transfer is disabled on this server.");let{path:m,iceServers:P}=oe({peerjsPath:p,iceServers:i},A),l=se({host:o,port:s,peerjsPath:m,secure:d,iceServers:P}),D=u||(()=>be(h)),U=a=>new t(a,l),{peer:g,code:j}=await ve({code:null,codeGenerator:D,maxAttempts:S,buildPeer:U,onCode:Y}),W=at(),c="listening",v=null,E=0,K=null,ee=a=>{let z=Number.isFinite(a.total)&&a.total>0?a.total:e.size,Z=Math.min(Number(a.received)||0,z||0),de=z?Z/z*100:0;F?.({processedBytes:Z,totalBytes:z,percent:de})},H=a=>{c==="closed"||c==="completed"||c==="cancelled"||(c="closed",w?.(a),X())},q=()=>{c==="finishing"&&(c="completed",$?.(),X())},X=()=>{K&&(clearInterval(K),K=null),typeof window<"u"&&window.removeEventListener("beforeunload",b);try{v?.close()}catch{}try{g.destroy()}catch{}},b=()=>{try{v?.send({t:"error",message:"Sender closed the connection."})}catch{}N()};typeof window<"u"&&window.addEventListener("beforeunload",b);let N=()=>{if(c==="closed"||c==="cancelled")return;let a=c==="transferring"||c==="finishing";c="cancelled";try{v&&v.open&&v.send({t:"cancelled",message:"Sender cancelled the transfer."})}catch{}a&&C&&C({cancelledBy:"sender"}),X()},y=()=>c==="closed"||c==="cancelled";return g.on("connection",a=>{if(c==="closed")return;if(v){let I=v.open!==!1;if(I&&c==="transferring"){try{a.send({t:"error",message:"Transfer already in progress."})}catch{}try{a.close()}catch{}return}else if(I){try{a.send({t:"error",message:"Another receiver is already connected."})}catch{}try{a.close()}catch{}return}else{try{v.close()}catch{}v=null,c="listening",E=0}}v=a,c="negotiating",B?.({phase:"waiting",message:"Connected. Waiting for receiver to accept..."});let z=null,Z=null,de=new Promise(I=>{z=I}),De=new Promise(I=>{Z=I});a.on("data",I=>{if(!I||typeof I!="object"||I instanceof ArrayBuffer||ArrayBuffer.isView(I))return;let x=I;if(x.t){if(x.t==="ready"){B?.({phase:"transferring",message:"Receiver accepted. Starting transfer..."}),z?.();return}if(x.t==="progress"){ee({received:x.received||0,total:x.total||0});return}if(x.t==="ack"&&x.phase==="end"){Z?.(x);return}if(x.t!=="pong"){if(x.t==="error"){H(new M(x.message||"Receiver reported an error."));return}if(x.t==="cancelled"){if(c==="cancelled"||c==="closed"||c==="completed")return;c="cancelled",C?.({cancelledBy:"receiver",message:x.message}),X()}}}}),a.on("open",async()=>{try{if(y())return;a.send({t:"meta",sessionId:W,name:e.name,size:e.size,mime:e.type||"application/octet-stream"});let I=e.size,x=a._dc;if(x&&Number.isFinite(_))try{x.bufferedAmountLowThreshold=_}catch{}if(await de,y())return;R>0&&(K=setInterval(()=>{if(c==="transferring"||c==="finishing")try{a.send({t:"ping"})}catch{}},R)),c="transferring";for(let Be=0;Be<I;Be+=k){if(y())return;let Ve=await e.slice(Be,Be+k).arrayBuffer();if(y())return;if(a.send(Ve),E+=Ve.byteLength,x)for(;x.bufferedAmount>G;)await new Promise(Je=>{let Ze=setTimeout(Je,60);try{x.addEventListener("bufferedamountlow",()=>{clearTimeout(Ze),Je()},{once:!0})}catch{}})}if(y())return;c="finishing",a.send({t:"end"});let Ue=Number.isFinite(O)?Math.max(O,Math.ceil(e.size/(1024*1024))*1e3):null,fe=await Promise.race([De,pe(Ue||15e3).catch(()=>null)]);if(y())return;if(!fe||typeof fe!="object")throw new M("Receiver did not confirm completion.");let Se=fe,ie=Number(Se.total)||e.size,Ke=Number(Se.received)||0;if(ie&&Ke<ie)throw new M("Receiver reported an incomplete transfer.");ee({received:Ke||ie,total:ie}),q()}catch(I){H(I)}}),a.on("error",I=>{H(I)}),a.on("close",()=>{if(c==="closed"||c==="completed"||c==="cancelled"){X();return}c==="transferring"||c==="finishing"?(c="cancelled",C?.({cancelledBy:"receiver"}),X()):(v=null,c="listening",E=0,T?.())})}),{peer:g,code:j,sessionId:W,stop:N,getStatus:()=>c,getBytesSent:()=>E,getConnectedPeerId:()=>v&&v.peer||null}}async function Ge(r){let{code:e,Peer:t,serverInfo:n,host:o,port:s,peerjsPath:p,secure:d=!1,iceServers:i,autoReady:u=!0,watchdogTimeoutMs:h=15e3,onStatus:S,onMeta:k,onData:O,onProgress:G,onComplete:_,onError:R,onDisconnect:Y,onCancel:B}=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 F=n?.capabilities?.p2p;if(n&&!F?.enabled)throw new f("Direct transfer is disabled on this server.");let $=String(e).trim().replace(/\s+/g,"").toUpperCase();if(!Pe($))throw new f("Invalid direct transfer code.");let{path:w,iceServers:T}=oe({peerjsPath:p,iceServers:i},F),C=se({host:o,port:s,peerjsPath:w,secure:d,iceServers:T}),A=new t(void 0,C),m="initializing",P=0,l=0,D=null,U=0,g=120,j=Promise.resolve(),W=null,c=null,v=()=>{h<=0||(W&&clearTimeout(W),W=setTimeout(()=>{m==="transferring"&&K(new M("Connection timed out (no data received)."))},h))},E=()=>{W&&(clearTimeout(W),W=null)},K=b=>{m==="closed"||m==="completed"||m==="cancelled"||(m="closed",R?.(b),H())},ee=b=>{m==="transferring"&&(m="completed",_?.(b),H())},H=()=>{E(),typeof window<"u"&&window.removeEventListener("beforeunload",q);try{A.destroy()}catch{}},q=()=>{try{c?.send({t:"error",message:"Receiver closed the connection."})}catch{}X()};typeof window<"u"&&window.addEventListener("beforeunload",q);let X=()=>{if(m==="closed"||m==="cancelled")return;let b=m==="transferring";m="cancelled";try{c&&c.open&&c.send({t:"cancelled",message:"Receiver cancelled the transfer."})}catch{}b&&B&&B({cancelledBy:"receiver"}),H()};return A.on("error",b=>{K(b)}),A.on("open",()=>{m="connecting";let b=A.connect($,{reliable:!0});c=b,b.on("open",()=>{m="negotiating",S?.({phase:"connected",message:"Waiting for file details..."})}),b.on("data",async N=>{try{if(v(),N&&typeof N=="object"&&!(N instanceof ArrayBuffer)&&!ArrayBuffer.isView(N)){let a=N;if(a.t==="meta"){if(D&&a.sessionId&&a.sessionId!==D){try{b.send({t:"error",message:"Busy with another session."})}catch{}return}a.sessionId&&(D=a.sessionId);let z=String(a.name||"file");P=Number(a.size)||0,l=0,j=Promise.resolve();let Z=()=>{m="transferring",v();try{b.send({t:"ready"})}catch{}};u?(k?.({name:z,total:P}),G?.({processedBytes:l,totalBytes:P,percent:0}),Z()):(k?.({name:z,total:P,sendReady:Z}),G?.({processedBytes:l,totalBytes:P,percent:0}));return}if(a.t==="ping"){try{b.send({t:"pong"})}catch{}return}if(a.t==="end"){if(E(),await j,P&&l<P){let z=new M("Transfer ended before the full file was received.");try{b.send({t:"error",message:z.message})}catch{}throw z}try{b.send({t:"ack",phase:"end",received:l,total:P})}catch{}ee({received:l,total:P});return}if(a.t==="error")throw new M(a.message||"Sender reported an error.");if(a.t==="cancelled"){if(m==="cancelled"||m==="closed"||m==="completed")return;m="cancelled",B?.({cancelledBy:"sender",message:a.message}),H();return}return}let y;if(N instanceof ArrayBuffer)y=Promise.resolve(new Uint8Array(N));else if(ArrayBuffer.isView(N))y=Promise.resolve(new Uint8Array(N.buffer,N.byteOffset,N.byteLength));else if(typeof Blob<"u"&&N instanceof Blob)y=N.arrayBuffer().then(a=>new Uint8Array(a));else return;j=j.then(async()=>{let a=await y;O&&await O(a),l+=a.byteLength;let z=P?Math.min(100,l/P*100):0;G?.({processedBytes:l,totalBytes:P,percent:z});let Z=Date.now();if(l===P||Z-U>=g){U=Z;try{b.send({t:"progress",received:l,total:P})}catch{}}}).catch(a=>{try{b.send({t:"error",message:a?.message||"Receiver write failed."})}catch{}K(a)})}catch(y){K(y)}}),b.on("close",()=>{if(m==="closed"||m==="completed"||m==="cancelled"){H();return}m==="transferring"?(m="cancelled",B?.({cancelledBy:"sender"}),H()):m==="negotiating"?(m="closed",H(),Y?.()):K(new M("Sender disconnected before file details were received."))})}),{peer:A,stop:X,getStatus:()=>m,getBytesReceived:()=>l,getTotalBytes:()=>P,getSessionId:()=>D}}return ot(ct);})();
2
2
  if(typeof window!=="undefined"){window.DropgateCore=DropgateCore;}
3
3
  //# sourceMappingURL=index.browser.js.map