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