@dropgate/core 2.0.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 ADDED
@@ -0,0 +1,288 @@
1
+ <div align="center">
2
+ <img alt="Dropgate Logo" src="./dropgate.png" style="width:100px;height:auto;margin-bottom:1rem;" />
3
+
4
+ # @dropgate/core
5
+
6
+ <p style="margin-bottom:1rem;">A headless, environment-agnostic TypeScript library for Dropgate file sharing operations.</p>
7
+ </div>
8
+
9
+ <div align="center">
10
+
11
+ ![license](https://img.shields.io/badge/license-Apache--2.0-blue?style=flat-square)
12
+ ![version](https://img.shields.io/badge/version-2.0.0-beta.1-brightgreen?style=flat-square)
13
+ ![typescript](https://img.shields.io/badge/TypeScript-5.0+-blue?style=flat-square)
14
+
15
+ </div>
16
+
17
+ ## 🌍 Overview
18
+
19
+ **@dropgate/core** is the universal client library for Dropgate. It provides all the core functionality for:
20
+
21
+ - Uploading files to Dropgate servers (with optional E2EE)
22
+ - Downloading files from Dropgate servers
23
+ - Direct peer-to-peer file transfers (P2P)
24
+ - Server capability detection and version checking
25
+ - Utility functions for URL parsing/building, lifetime conversions, and more
26
+
27
+ This package is **headless** and **environment-agnostic** — it contains no DOM manipulation, no browser-specific APIs, and no Node.js-specific code. All environment-specific concerns (loading PeerJS, handling file streams, etc.) are handled by the consumer.
28
+
29
+ ## đŸ“Ļ Installation
30
+
31
+ ```bash
32
+ npm install @dropgate/core
33
+ ```
34
+
35
+ ## 🔨 Builds
36
+
37
+ The package ships with multiple build targets:
38
+
39
+ | Format | File | Use Case |
40
+ | --- | --- | --- |
41
+ | ESM | `dist/index.js` | Modern bundlers, Node.js 18+ |
42
+ | CJS | `dist/index.cjs` | Legacy Node.js, CommonJS |
43
+ | Browser IIFE | `dist/index.browser.js` | `<script>` tag, exposes `DropgateCore` global |
44
+
45
+ ## 🚀 Quick Start
46
+
47
+ ### âŦ†ī¸ Uploading a File
48
+
49
+ ```javascript
50
+ import { DropgateClient } from '@dropgate/core';
51
+
52
+ const client = new DropgateClient({ clientVersion: '2.0.0' });
53
+
54
+ const result = await client.uploadFile({
55
+ host: 'dropgate.link',
56
+ port: 443,
57
+ secure: true,
58
+ file: myFile, // File or Blob
59
+ lifetimeMs: 3600000, // 1 hour
60
+ encrypt: true,
61
+ onProgress: ({ phase, text, percent }) => {
62
+ console.log(`${phase}: ${text} (${percent ? percent : 0}%)`);
63
+ },
64
+ });
65
+
66
+ console.log('Download URL:', result.downloadUrl);
67
+ ```
68
+
69
+ ### â„šī¸ Getting Server Info
70
+
71
+ ```javascript
72
+ import { DropgateClient } from '@dropgate/core';
73
+
74
+ const client = new DropgateClient({ clientVersion: '2.0.0' });
75
+
76
+ const { serverInfo } = await client.getServerInfo({
77
+ host: 'dropgate.link',
78
+ secure: true,
79
+ timeoutMs: 5000,
80
+ });
81
+
82
+ console.log('Server version:', serverInfo.version);
83
+ console.log('Upload enabled:', serverInfo.capabilities?.upload?.enabled);
84
+ console.log('P2P enabled:', serverInfo.capabilities?.p2p?.enabled);
85
+ ```
86
+
87
+ ### 📤 P2P File Transfer (Sender)
88
+
89
+ ```javascript
90
+ import { startP2PSend } from '@dropgate/core';
91
+
92
+ // Consumer must provide PeerJS Peer constructor
93
+ const Peer = await loadPeerJS(); // Your loader function
94
+
95
+ const session = await startP2PSend({
96
+ file: myFile,
97
+ Peer,
98
+ host: 'dropgate.link',
99
+ port: 443,
100
+ secure: true,
101
+ onCode: (code) => console.log('Share this code:', code),
102
+ onProgress: ({ sent, total, percent }) => {
103
+ console.log(`Sending: ${percent.toFixed(1)}%`);
104
+ },
105
+ onComplete: () => console.log('Transfer complete!'),
106
+ onError: (err) => console.error('Error:', err),
107
+ });
108
+
109
+ // To cancel:
110
+ // session.stop();
111
+ ```
112
+
113
+ ### đŸ“Ĩ P2P File Transfer (Receiver)
114
+
115
+ ```javascript
116
+ import { startP2PReceive } from '@dropgate/core';
117
+
118
+ const Peer = await loadPeerJS();
119
+
120
+ const session = await startP2PReceive({
121
+ code: 'ABCD-1234',
122
+ Peer,
123
+ host: 'dropgate.link',
124
+ secure: true,
125
+ onMeta: ({ name, total }) => {
126
+ console.log(`Receiving: ${name} (${total} bytes)`);
127
+ },
128
+ onData: async (chunk) => {
129
+ // Consumer handles file writing (e.g., streamSaver, fs.write)
130
+ await writer.write(chunk);
131
+ },
132
+ onProgress: ({ received, total, percent }) => {
133
+ console.log(`Receiving: ${percent.toFixed(1)}%`);
134
+ },
135
+ onComplete: () => console.log('Transfer complete!'),
136
+ });
137
+ ```
138
+
139
+ ### âŦ‡ī¸ Downloading a File
140
+
141
+ ```javascript
142
+ import { DropgateClient } from '@dropgate/core';
143
+
144
+ const client = new DropgateClient({ clientVersion: '2.0.0' });
145
+
146
+ // Download with streaming (for large files)
147
+ const result = await client.downloadFile({
148
+ host: 'dropgate.link',
149
+ port: 443,
150
+ secure: true,
151
+ fileId: 'abc123',
152
+ keyB64: 'base64-key-from-url-hash', // Required for encrypted files
153
+ onProgress: ({ phase, percent, receivedBytes, totalBytes }) => {
154
+ console.log(`${phase}: ${percent}% (${receivedBytes}/${totalBytes})`);
155
+ },
156
+ onData: async (chunk) => {
157
+ // Consumer handles file writing (e.g., fs.write, streamSaver)
158
+ await writer.write(chunk);
159
+ },
160
+ });
161
+
162
+ console.log('Downloaded:', result.filename);
163
+
164
+ // Or download to memory (for small files)
165
+ const memoryResult = await client.downloadFile({
166
+ host: 'dropgate.link',
167
+ secure: true,
168
+ fileId: 'abc123',
169
+ });
170
+
171
+ // memoryResult.data contains the complete file as Uint8Array
172
+ console.log('File size:', memoryResult.data?.length);
173
+ ```
174
+
175
+ ## 📚 API Reference
176
+
177
+ ### 🔌 DropgateClient
178
+
179
+ The main client class for interacting with Dropgate servers.
180
+
181
+ #### âš™ī¸ Constructor Options
182
+
183
+ | Option | Type | Required | Description |
184
+ | --- | --- | --- | --- |
185
+ | `clientVersion` | `string` | Yes | Client version for compatibility checking |
186
+ | `chunkSize` | `number` | No | Upload chunk size (default: 5MB) |
187
+ | `fetchFn` | `FetchFn` | No | Custom fetch implementation |
188
+ | `cryptoObj` | `CryptoAdapter` | No | Custom crypto implementation |
189
+ | `base64` | `Base64Adapter` | No | Custom base64 encoder/decoder |
190
+ | `logger` | `LoggerFn` | No | Custom logger function |
191
+
192
+ #### đŸ› ī¸ Methods
193
+
194
+ | Method | Description |
195
+ | --- | --- |
196
+ | `getServerInfo(opts)` | Fetch server info and capabilities |
197
+ | `uploadFile(opts)` | Upload a file with optional encryption |
198
+ | `downloadFile(opts)` | Download a file with optional decryption |
199
+ | `checkCompatibility(serverInfo)` | Check client/server version compatibility |
200
+ | `validateUploadInputs(opts)` | Validate file and settings before upload |
201
+ | `resolveShareTarget(value, opts)` | Resolve a sharing code via the server |
202
+
203
+ ### 🔄 P2P Functions
204
+
205
+ | Function | Description |
206
+ | --- | --- |
207
+ | `startP2PSend(opts)` | Start a P2P send session |
208
+ | `startP2PReceive(opts)` | Start a P2P receive session |
209
+ | `generateP2PCode(cryptoObj?)` | Generate a secure sharing code |
210
+ | `isP2PCodeLike(code)` | Check if a string looks like a P2P code |
211
+ | `isSecureContextForP2P(hostname, isSecureContext)` | Check if P2P is allowed |
212
+ | `isLocalhostHostname(hostname)` | Check if hostname is localhost |
213
+
214
+ ### 🧰 Utility Functions
215
+
216
+ | Function | Description |
217
+ | --- | --- |
218
+ | `parseServerUrl(urlStr)` | Parse a URL string into host/port/secure |
219
+ | `buildBaseUrl(opts)` | Build a URL from host/port/secure |
220
+ | `lifetimeToMs(value, unit)` | Convert lifetime to milliseconds |
221
+ | `estimateTotalUploadSizeBytes(...)` | Estimate upload size with encryption overhead |
222
+ | `bytesToBase64(bytes)` | Convert bytes to base64 |
223
+ | `base64ToBytes(b64)` | Convert base64 to bytes |
224
+
225
+ ### âš ī¸ Error Classes
226
+
227
+ | Class | Description |
228
+ | --- | --- |
229
+ | `DropgateError` | Base error class |
230
+ | `DropgateValidationError` | Input validation errors |
231
+ | `DropgateNetworkError` | Network/connection errors |
232
+ | `DropgateProtocolError` | Server protocol errors |
233
+ | `DropgateAbortError` | Operation aborted |
234
+ | `DropgateTimeoutError` | Operation timed out |
235
+
236
+ ## 🌐 Browser Usage
237
+
238
+ For browser environments, you can use the IIFE bundle:
239
+
240
+ ```html
241
+ <script src="/path/to/dropgate-core.browser.js"></script>
242
+ <script>
243
+ const { DropgateClient, startP2PSend } = DropgateCore;
244
+ // ...
245
+ </script>
246
+ ```
247
+
248
+ Or as an ES module:
249
+
250
+ ```html
251
+ <script type="module">
252
+ import { DropgateClient } from '/path/to/dropgate-core.js';
253
+ // ...
254
+ </script>
255
+ ```
256
+
257
+ ## 📋 P2P Consumer Responsibilities
258
+
259
+ The P2P functions are designed to be **headless**. The consumer is responsible for:
260
+
261
+ 1. **Loading PeerJS**: Provide the `Peer` constructor to P2P functions
262
+ 2. **File Writing**: Handle received chunks via `onData` callback (e.g., using streamSaver)
263
+ 3. **UI Updates**: React to callbacks (`onProgress`, `onStatus`, etc.)
264
+
265
+ This design allows the library to work in any environment (browser, Electron, Node.js with WebRTC).
266
+
267
+ ## 📜 License
268
+
269
+ Licensed under the **Apache-2.0 License**.
270
+ See the [LICENSE](./LICENSE) file for details.
271
+
272
+ ## 📖 Acknowledgements
273
+
274
+ * Logo designed by [TheFuturisticIdiot](https://youtube.com/TheFuturisticIdiot)
275
+ * Built with [TypeScript](https://www.typescriptlang.org/)
276
+ * Inspired by the growing need for privacy-respecting, open file transfer tools
277
+
278
+ ## 🙂 Contact Us
279
+
280
+ * **Need help or want to chat?** [Join our Discord Server](https://diamonddigital.dev/discord)
281
+ * **Found a bug?** [Open an issue](https://github.com/WillTDA/Dropgate/issues)
282
+ * **Have a suggestion?** [Submit a feature request](https://github.com/WillTDA/Dropgate/issues/new?labels=enhancement)
283
+
284
+ <div align="center">
285
+ <a href="https://diamonddigital.dev/">
286
+ <strong>Created and maintained by</strong>
287
+ <img align="center" alt="Diamond Digital Development Logo" src="https://diamonddigital.dev/img/png/ddd_logo_text_transparent.png" style="width:25%;height:auto" /></a>
288
+ </div>
@@ -0,0 +1,3 @@
1
+ "use strict";var DropgateCore=(()=>{var ke=Object.defineProperty;var Ke=Object.getOwnPropertyDescriptor;var Ve=Object.getOwnPropertyNames;var He=Object.prototype.hasOwnProperty;var Je=(r,e)=>{for(var t in e)ke(r,t,{get:e[t],enumerable:!0})},We=(r,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Ve(e))!He.call(r,n)&&n!==t&&ke(r,n,{get:()=>e[n],enumerable:!(o=Ke(e,n))||o.enumerable});return r};var Ye=r=>We(ke({},"__esModule",{value:!0}),r);var Ze={};Je(Ze,{AES_GCM_IV_BYTES:()=>se,AES_GCM_TAG_BYTES:()=>Ne,DEFAULT_CHUNK_SIZE:()=>he,DropgateAbortError:()=>$,DropgateClient:()=>Ae,DropgateError:()=>U,DropgateNetworkError:()=>F,DropgateProtocolError:()=>N,DropgateTimeoutError:()=>te,DropgateValidationError:()=>u,ENCRYPTION_OVERHEAD_PER_CHUNK:()=>ye,arrayBufferToBase64:()=>oe,base64ToBytes:()=>$e,buildBaseUrl:()=>ne,buildPeerOptions:()=>ee,bytesToBase64:()=>Oe,createPeerWithRetries:()=>fe,decryptChunk:()=>Q,decryptFilenameFromBase64:()=>ce,encryptFilenameToBase64:()=>Se,encryptToBlob:()=>le,estimateTotalUploadSizeBytes:()=>Ce,exportKeyBase64:()=>ve,fetchJson:()=>Z,generateAesGcmKey:()=>Pe,generateP2PCode:()=>pe,getDefaultBase64:()=>J,getDefaultCrypto:()=>re,getDefaultFetch:()=>ge,importKeyFromBase64:()=>ae,isLocalhostHostname:()=>xe,isP2PCodeLike:()=>ue,isSecureContextForP2P:()=>Re,lifetimeToMs:()=>Le,makeAbortSignal:()=>X,parseSemverMajorMinor:()=>ie,parseServerUrl:()=>ze,sha256Hex:()=>be,sleep:()=>q,startP2PReceive:()=>Te,startP2PSend:()=>Me,validatePlainFilename:()=>we});var he=5242880,se=12,Ne=16,ye=28;var U=class extends Error{constructor(e,t={}){super(e),this.name=this.constructor.name,this.code=t.code||"DROPGATE_ERROR",this.details=t.details,t.cause!==void 0&&Object.defineProperty(this,"cause",{value:t.cause,writable:!1,enumerable:!1,configurable:!0})}},u=class extends U{constructor(e,t={}){super(e,{...t,code:t.code||"VALIDATION_ERROR"})}},F=class extends U{constructor(e,t={}){super(e,{...t,code:t.code||"NETWORK_ERROR"})}},N=class extends U{constructor(e,t={}){super(e,{...t,code:t.code||"PROTOCOL_ERROR"})}},$=class extends U{constructor(e="Operation aborted"){super(e,{code:"ABORT_ERROR"}),this.name="AbortError"}},te=class extends U{constructor(e="Request timed out"){super(e,{code:"TIMEOUT_ERROR"}),this.name="TimeoutError"}};function J(){if(typeof Buffer<"u"&&typeof Buffer.from=="function")return{encode(r){return Buffer.from(r).toString("base64")},decode(r){return new Uint8Array(Buffer.from(r,"base64"))}};if(typeof btoa=="function"&&typeof atob=="function")return{encode(r){let e="";for(let t=0;t<r.length;t++)e+=String.fromCharCode(r[t]);return btoa(e)},decode(r){let e=atob(r),t=new Uint8Array(e.length);for(let o=0;o<e.length;o++)t[o]=e.charCodeAt(o);return t}};throw new Error("No Base64 implementation available. Provide a Base64Adapter via options.")}function re(){return globalThis.crypto}function ge(){return globalThis.fetch?.bind(globalThis)}var Fe=null;function _e(r){return r||(Fe||(Fe=J()),Fe)}function Oe(r,e){return _e(e).encode(r)}function oe(r,e){return Oe(new Uint8Array(r),e)}function $e(r,e){return _e(e).decode(r)}var qe={minutes:6e4,hours:36e5,days:864e5};function Le(r,e){let t=String(e||"").toLowerCase(),o=Number(r);if(t==="unlimited"||!Number.isFinite(o)||o<=0)return 0;let n=qe[t];return n?Math.round(o*n):0}function ie(r){let e=String(r||"").split(".").map(n=>Number(n)),t=Number.isFinite(e[0])?e[0]:0,o=Number.isFinite(e[1])?e[1]:0;return{major:t,minor:o}}function we(r){if(typeof r!="string"||r.trim().length===0)throw new u("Invalid filename. Must be a non-empty string.");if(r.length>255||/[\/\\]/.test(r))throw new u("Invalid filename. Contains illegal characters or is too long.")}function ze(r){let e=r.trim();!e.startsWith("http://")&&!e.startsWith("https://")&&(e="https://"+e);let t=new URL(e);return{host:t.hostname,port:t.port?Number(t.port):void 0,secure:t.protocol==="https:"}}function ne(r){let{host:e,port:t,secure:o}=r;if(!e||typeof e!="string")throw new u("Server host is required.");let n=o===!1?"http":"https",s=t?`:${t}`:"";return`${n}://${e}${s}`}function q(r,e){return new Promise((t,o)=>{if(e?.aborted)return o(e.reason||new $);let n=setTimeout(t,r);e&&e.addEventListener("abort",()=>{clearTimeout(n),o(e.reason||new $)},{once:!0})})}function X(r,e){let t=new AbortController,o=null,n=s=>{t.signal.aborted||t.abort(s)};return r&&(r.aborted?n(r.reason):r.addEventListener("abort",()=>n(r.reason),{once:!0})),Number.isFinite(e)&&e>0&&(o=setTimeout(()=>{n(new te)},e)),{signal:t.signal,cleanup:()=>{o&&clearTimeout(o)}}}async function Z(r,e,t={}){let{timeoutMs:o,signal:n,...s}=t,{signal:l,cleanup:f}=X(n,o);try{let i=await r(e,{...s,signal:l}),p=await i.text(),c=null;try{c=p?JSON.parse(p):null}catch{}return{res:i,json:c,text:p}}finally{f()}}async function ae(r,e,t){let n=(t||J()).decode(e),s=new Uint8Array(n).buffer;return r.subtle.importKey("raw",s,{name:"AES-GCM"},!0,["decrypt"])}async function Q(r,e,t){let o=e.slice(0,12),n=e.slice(12);return r.subtle.decrypt({name:"AES-GCM",iv:o},t,n)}async function ce(r,e,t,o){let s=(o||J()).decode(e),l=await Q(r,s,t);return new TextDecoder().decode(l)}async function be(r,e){let t=await r.subtle.digest("SHA-256",e),o=new Uint8Array(t),n="";for(let s=0;s<o.length;s++)n+=o[s].toString(16).padStart(2,"0");return n}async function Pe(r){return r.subtle.generateKey({name:"AES-GCM",length:256},!0,["encrypt","decrypt"])}async function ve(r,e){let t=await r.subtle.exportKey("raw",e);return oe(t)}async function le(r,e,t){let o=r.getRandomValues(new Uint8Array(12)),n=await r.subtle.encrypt({name:"AES-GCM",iv:o},t,e);return new Blob([o,new Uint8Array(n)])}async function Se(r,e,t){let o=new TextEncoder().encode(String(e)),s=await(await le(r,o.buffer,t)).arrayBuffer();return oe(s)}function Ce(r,e,t){let o=Number(r)||0;return t?o+(Number(e)||0)*28:o}var Ae=class{constructor(e){if(!e||typeof e.clientVersion!="string")throw new u("DropgateClient requires clientVersion (string).");this.clientVersion=e.clientVersion,this.chunkSize=Number.isFinite(e.chunkSize)?e.chunkSize:5242880;let t=e.fetchFn||ge();if(!t)throw new u("No fetch() implementation found.");this.fetchFn=t;let o=e.cryptoObj||re();if(!o)throw new u("No crypto implementation found.");this.cryptoObj=o,this.base64=e.base64||J(),this.logger=e.logger||null}async getServerInfo(e){let{host:t,port:o,secure:n,timeoutMs:s=5e3,signal:l}=e,f=ne({host:t,port:o,secure:n});try{let{res:i,json:p}=await Z(this.fetchFn,`${f}/api/info`,{method:"GET",timeoutMs:s,signal:l,headers:{Accept:"application/json"}});if(i.ok&&p&&typeof p=="object"&&"version"in p)return{baseUrl:f,serverInfo:p};throw new N(`Server info request failed (status ${i.status}).`)}catch(i){throw i instanceof U?i:new F("Could not reach server /api/info.",{cause:i})}}async resolveShareTarget(e,t){let{host:o,port:n,secure:s,timeoutMs:l=5e3,signal:f}=t,i=ne({host:o,port:n,secure:s}),{res:p,json:c}=await Z(this.fetchFn,`${i}/api/resolve`,{method:"POST",timeoutMs:l,signal:f,headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({value:e})});if(!p.ok){let b=(c&&typeof c=="object"&&"error"in c?c.error:null)||`Share lookup failed (status ${p.status}).`;throw new N(b,{details:c})}return c||{valid:!1,reason:"Unknown response."}}checkCompatibility(e){let t=String(e?.version||"0.0.0"),o=String(this.clientVersion||"0.0.0"),n=ie(o),s=ie(t);return n.major!==s.major?{compatible:!1,clientVersion:o,serverVersion:t,message:`Incompatible versions. Client v${o}, Server v${t}${e?.name?` (${e.name})`:""}.`}:n.minor>s.minor?{compatible:!0,clientVersion:o,serverVersion:t,message:`Client (v${o}) is newer than Server (v${t})${e?.name?` (${e.name})`:""}. Some features may not work.`}:{compatible:!0,clientVersion:o,serverVersion:t,message:`Server: v${t}, Client: v${o}${e?.name?` (${e.name})`:""}.`}}validateUploadInputs(e){let{file:t,lifetimeMs:o,encrypt:n,serverInfo:s}=e,l=s?.capabilities?.upload;if(!l||!l.enabled)throw new u("Server does not support file uploads.");let f=Number(t?.size||0);if(!t||!Number.isFinite(f)||f<=0)throw new u("File is missing or invalid.");let i=Number(l.maxSizeMB);if(Number.isFinite(i)&&i>0){let b=i*1e3*1e3,P=Math.ceil(f/this.chunkSize);if(Ce(f,P,!!n)>b){let E=n?`File too large once encryption overhead is included. Server limit: ${i} MB.`:`File too large. Server limit: ${i} MB.`;throw new u(E)}}let p=Number(l.maxLifetimeHours),c=Number(o);if(!Number.isFinite(c)||c<0||!Number.isInteger(c))throw new u("Invalid lifetime. Must be a non-negative integer (milliseconds).");if(Number.isFinite(p)&&p>0){let b=Math.round(p*60*60*1e3);if(c===0)throw new u(`Server does not allow unlimited file lifetime. Max: ${p} hours.`);if(c>b)throw new u(`File lifetime too long. Server limit: ${p} hours.`)}if(n&&!l.e2ee)throw new u("Server does not support end-to-end encryption.");return!0}async uploadFile(e){let{host:t,port:o,secure:n,file:s,lifetimeMs:l,encrypt:f,filenameOverride:i,onProgress:p,signal:c,timeouts:b={},retry:P={}}=e,B=d=>{try{p&&p(d)}catch{}};if(!this.cryptoObj?.subtle)throw new u("Web Crypto API not available (crypto.subtle).");B({phase:"server-info",text:"Checking server..."});let E,_;try{let d=await this.getServerInfo({host:t,port:o,secure:n,timeoutMs:b.serverInfoMs??5e3,signal:c});E=d.baseUrl,_=d.serverInfo}catch(d){throw d instanceof U?d:new F("Could not connect to the server.",{cause:d})}let R=this.checkCompatibility(_);if(B({phase:"server-compat",text:R.message}),!R.compatible)throw new u(R.message);let m=i??s.name??"file";f||we(m),this.validateUploadInputs({file:s,lifetimeMs:l,encrypt:f,serverInfo:_});let O=null,k=null,z=m;if(f){B({phase:"crypto",text:"Generating encryption key..."});try{O=await Pe(this.cryptoObj),k=await ve(this.cryptoObj,O),z=await Se(this.cryptoObj,m,O)}catch(d){throw new U("Failed to prepare encryption.",{code:"CRYPTO_PREP_FAILED",cause:d})}}let S=Math.ceil(s.size/this.chunkSize),g=Ce(s.size,S,f);B({phase:"init",text:"Reserving server storage..."});let v={filename:z,lifetime:l,isEncrypted:!!f,totalSize:g,totalChunks:S},I=await Z(this.fetchFn,`${E}/upload/init`,{method:"POST",timeoutMs:b.initMs??15e3,signal:c,headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(v)});if(!I.res.ok){let T=I.json?.error||`Server initialisation failed: ${I.res.status}`;throw new N(T,{details:I.json||I.text})}let a=I.json?.uploadId;if(!a||typeof a!="string")throw new N("Server did not return a valid uploadId.");let j=Number.isFinite(P.retries)?P.retries:5,w=Number.isFinite(P.backoffMs)?P.backoffMs:1e3,h=Number.isFinite(P.maxBackoffMs)?P.maxBackoffMs:3e4;for(let d=0;d<S;d++){if(c?.aborted)throw c.reason||new $;let T=d*this.chunkSize,x=Math.min(T+this.chunkSize,s.size),G=s.slice(T,x),K=d/S*100;B({phase:"chunk",text:`Uploading chunk ${d+1} of ${S}...`,percent:K,chunkIndex:d,totalChunks:S});let H=await G.arrayBuffer(),W;if(f&&O?W=await le(this.cryptoObj,H,O):W=new Blob([H]),W.size>5243904)throw new u("Chunk too large (client-side). Check chunk size settings.");let D=await W.arrayBuffer(),L=await be(this.cryptoObj,D),Y={"Content-Type":"application/octet-stream","X-Upload-ID":a,"X-Chunk-Index":String(d),"X-Chunk-Hash":L},Be=`${E}/upload/chunk`;await this.attemptChunkUpload(Be,{method:"POST",headers:Y,body:W},{retries:j,backoffMs:w,maxBackoffMs:h,timeoutMs:b.chunkMs??6e4,signal:c,progress:B,chunkIndex:d,totalChunks:S})}B({phase:"complete",text:"Finalising upload...",percent:100});let y=await Z(this.fetchFn,`${E}/upload/complete`,{method:"POST",timeoutMs:b.completeMs??3e4,signal:c,headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({uploadId:a})});if(!y.res.ok){let T=y.json?.error||"Finalisation failed.";throw new N(T,{details:y.json||y.text})}let A=y.json?.id;if(!A||typeof A!="string")throw new N("Server did not return a valid file id.");let M=`${E}/${A}`;return f&&k&&(M+=`#${k}`),B({phase:"done",text:"Upload successful!",percent:100}),{downloadUrl:M,fileId:A,uploadId:a,baseUrl:E,...f&&k?{keyB64:k}:{}}}async downloadFile(e){let{host:t,port:o,secure:n,fileId:s,keyB64:l,onProgress:f,onData:i,signal:p,timeoutMs:c=6e4}=e,b=a=>{try{f&&f(a)}catch{}};if(!s||typeof s!="string")throw new u("File ID is required.");let P=ne({host:t,port:o,secure:n});b({phase:"metadata",text:"Fetching file info...",receivedBytes:0,totalBytes:0,percent:0});let{signal:B,cleanup:E}=X(p,c),_;try{let a=await this.fetchFn(`${P}/api/file/${s}/meta`,{method:"GET",headers:{Accept:"application/json"},signal:B});if(!a.ok)throw a.status===404?new N("File not found or has expired."):new N(`Failed to fetch file metadata (status ${a.status}).`);_=await a.json()}catch(a){throw a instanceof U?a:a instanceof Error&&a.name==="AbortError"?new $("Download cancelled."):new F("Could not fetch file metadata.",{cause:a})}finally{E()}let R=!!_.isEncrypted,m=_.sizeBytes||0;if(!i&&m>104857600){let a=Math.round(m/1048576),j=Math.round(104857600/(1024*1024));throw new u(`File is too large (${a}MB) to download without streaming. Provide an onData callback to stream files larger than ${j}MB.`)}let O,k;if(R){if(!l)throw new u("Decryption key is required for encrypted files.");if(!this.cryptoObj?.subtle)throw new u("Web Crypto API not available for decryption.");b({phase:"decrypting",text:"Preparing decryption...",receivedBytes:0,totalBytes:0,percent:0});try{k=await ae(this.cryptoObj,l,this.base64),O=await ce(this.cryptoObj,_.encryptedFilename,k,this.base64)}catch(a){throw new U("Failed to decrypt filename. Invalid key or corrupted data.",{code:"DECRYPT_FILENAME_FAILED",cause:a})}}else O=_.filename||"file";b({phase:"downloading",text:"Starting download...",percent:0,receivedBytes:0,totalBytes:m});let{signal:z,cleanup:S}=X(p,c),g=0,v=[],I=!i;try{let a=await this.fetchFn(`${P}/api/file/${s}`,{method:"GET",signal:z});if(!a.ok)throw new N(`Download failed (status ${a.status}).`);if(!a.body)throw new N("Streaming response not available.");let j=a.body.getReader();if(R&&k){let w=this.chunkSize+28,h=[],y=0,C=()=>{if(h.length===0)return new Uint8Array(0);if(h.length===1){let d=h[0];return h.length=0,y=0,d}let A=new Uint8Array(y),M=0;for(let d of h)A.set(d,M),M+=d.length;return h.length=0,y=0,A};for(;;){if(p?.aborted)throw new $("Download cancelled.");let{done:A,value:M}=await j.read();if(A)break;for(h.push(M),y+=M.length;y>=w;){let T=C(),x=T.subarray(0,w);if(T.length>w){let H=T.subarray(w);h.push(H),y=H.length}let G=await Q(this.cryptoObj,x,k),K=new Uint8Array(G);I?v.push(K):await i(K)}g+=M.length;let d=m>0?Math.round(g/m*100):0;b({phase:"decrypting",text:`Downloading & decrypting... (${d}%)`,percent:d,receivedBytes:g,totalBytes:m})}if(y>0){let A=C(),M=await Q(this.cryptoObj,A,k),d=new Uint8Array(M);I?v.push(d):await i(d)}}else for(;;){if(p?.aborted)throw new $("Download cancelled.");let{done:w,value:h}=await j.read();if(w)break;I?v.push(h):await i(h),g+=h.length;let y=m>0?Math.round(g/m*100):0;b({phase:"downloading",text:`Downloading... (${y}%)`,percent:y,receivedBytes:g,totalBytes:m})}}catch(a){throw a instanceof U?a:a instanceof Error&&a.name==="AbortError"?new $("Download cancelled."):new F("Download failed.",{cause:a})}finally{S()}b({phase:"complete",text:"Download complete!",percent:100,receivedBytes:g,totalBytes:m});let V;if(I&&v.length>0){let a=v.reduce((w,h)=>w+h.length,0);V=new Uint8Array(a);let j=0;for(let w of v)V.set(w,j),j+=w.length}return{filename:O,receivedBytes:g,wasEncrypted:R,...V?{data:V}:{}}}async attemptChunkUpload(e,t,o){let{retries:n,backoffMs:s,maxBackoffMs:l,timeoutMs:f,signal:i,progress:p,chunkIndex:c,totalChunks:b}=o,P=n,B=s,E=n;for(;;){if(i?.aborted)throw i.reason||new $;let{signal:_,cleanup:R}=X(i,f);try{let m=await this.fetchFn(e,{...t,signal:_});if(m.ok)return;let O=await m.text().catch(()=>"");throw new N(`Chunk ${c+1} failed (HTTP ${m.status}).`,{details:{status:m.status,bodySnippet:O.slice(0,120)}})}catch(m){if(R(),m instanceof Error&&(m.name==="AbortError"||m.code==="ABORT_ERR"))throw m;if(i?.aborted)throw i.reason||new $;if(P<=0)throw m instanceof U?m:new F("Chunk upload failed.",{cause:m});let O=E-P+1,k=B,z=100;for(;k>0;){let S=(k/1e3).toFixed(1);p({phase:"retry-wait",text:`Chunk upload failed. Retrying in ${S}s... (${O}/${E})`,chunkIndex:c,totalChunks:b}),await q(Math.min(z,k),i),k-=z}p({phase:"retry",text:`Chunk upload failed. Retrying now... (${O}/${E})`,chunkIndex:c,totalChunks:b}),P-=1,B=Math.min(B*2,l);continue}finally{R()}}}};function xe(r){let e=String(r||"").toLowerCase();return e==="localhost"||e==="127.0.0.1"||e==="::1"}function Re(r,e){return!!e||xe(r||"")}function pe(r){let e=r||re(),t="ABCDEFGHJKLMNPQRSTUVWXYZ";if(e){let s=new Uint8Array(8);e.getRandomValues(s);let l="";for(let i=0;i<4;i++)l+=t[s[i]%t.length];let f="";for(let i=4;i<8;i++)f+=(s[i]%10).toString();return`${l}-${f}`}let o="";for(let s=0;s<4;s++)o+=t[Math.floor(Math.random()*t.length)];let n="";for(let s=0;s<4;s++)n+=Math.floor(Math.random()*10);return`${o}-${n}`}function ue(r){return/^[A-Z]{4}-\d{4}$/.test(String(r||"").trim())}function ee(r={}){let{host:e,port:t,peerjsPath:o="/peerjs",secure:n=!1,iceServers:s=[]}=r,l={host:e,path:o,secure:n,config:{iceServers:s},debug:0};return t&&(l.port=t),l}async function fe(r){let{code:e,codeGenerator:t,maxAttempts:o,buildPeer:n,onCode:s}=r,l=e||t(),f=null,i=null;for(let p=0;p<o;p++){s?.(l,p);try{return f=await new Promise((c,b)=>{let P=n(l);P.on("open",()=>c(P)),P.on("error",B=>{try{P.destroy()}catch{}b(B)})}),{peer:f,code:l}}catch(c){i=c,l=t()}}throw i||new F("Could not establish PeerJS connection.")}async function Me(r){let{file:e,Peer:t,serverInfo:o,host:n,port:s,peerjsPath:l,secure:f=!1,iceServers:i,codeGenerator:p,cryptoObj:c,maxAttempts:b=4,chunkSize:P=256*1024,readyTimeoutMs:B=8e3,endAckTimeoutMs:E=15e3,bufferHighWaterMark:_=8*1024*1024,bufferLowWaterMark:R=2*1024*1024,onCode:m,onStatus:O,onProgress:k,onComplete:z,onError:S}=r;if(!e)throw new u("File is missing.");if(!t)throw new u("PeerJS Peer constructor is required. Install peerjs and pass it as the Peer option.");let g=o?.capabilities?.p2p;if(o&&!g?.enabled)throw new u("Direct transfer is disabled on this server.");let v=l??g?.peerjsPath??"/peerjs",I=i??g?.iceServers??[],V=ee({host:n,port:s,peerjsPath:v,secure:f,iceServers:I}),a=p||(()=>pe(c)),j=x=>new t(x,V),{peer:w,code:h}=await fe({code:null,codeGenerator:a,maxAttempts:b,buildPeer:j,onCode:m}),y=!1,C=null,A=!1,M=!1,d=x=>{let G=Number.isFinite(x.total)&&x.total>0?x.total:e.size,K=Math.min(Number(x.received)||0,G||0),H=G?K/G*100:0;k?.({sent:K,total:G,percent:H})},T=()=>{y=!0;try{C?.close()}catch{}try{w.destroy()}catch{}};return w.on("connection",x=>{if(y)return;if(C){try{x.send({t:"error",message:"Another receiver is already connected."})}catch{}try{x.close()}catch{}return}C=x,O?.({phase:"connected",message:"Connected. Starting transfer..."});let G=null,K=null,H=new Promise(D=>{G=D}),W=new Promise(D=>{K=D});x.on("data",D=>{if(!D||typeof D!="object"||D instanceof ArrayBuffer||ArrayBuffer.isView(D))return;let L=D;if(L.t){if(L.t==="ready"){G?.();return}if(L.t==="progress"){d({received:L.received||0,total:L.total||0});return}if(L.t==="ack"&&L.phase==="end"){K?.(L);return}L.t==="error"&&(S?.(new F(L.message||"Receiver reported an error.")),T())}}),x.on("open",async()=>{try{if(A=!0,y)return;x.send({t:"meta",name:e.name,size:e.size,mime:e.type||"application/octet-stream"});let D=0,L=e.size,Y=x._dc;if(Y&&Number.isFinite(R))try{Y.bufferedAmountLowThreshold=R}catch{}await Promise.race([H,q(B).catch(()=>null)]);for(let me=0;me<L;me+=P){if(y)return;let Ie=await e.slice(me,me+P).arrayBuffer();if(x.send(Ie),D+=Ie.byteLength,Y)for(;Y.bufferedAmount>_;)await new Promise(je=>{let Ge=setTimeout(je,60);try{Y.addEventListener("bufferedamountlow",()=>{clearTimeout(Ge),je()},{once:!0})}catch{}})}if(y)return;x.send({t:"end"});let Be=Number.isFinite(E)?Math.max(E,Math.ceil(e.size/(1024*1024))*1e3):null,Ee=await Promise.race([W,q(Be||15e3).catch(()=>null)]);if(!Ee||typeof Ee!="object")throw new F("Receiver did not confirm completion.");let De=Ee,de=Number(De.total)||e.size,Ue=Number(De.received)||0;if(de&&Ue<de)throw new F("Receiver reported an incomplete transfer.");d({received:Ue||de,total:de}),M=!0,A=!1,z?.(),T()}catch(D){S?.(D),T()}}),x.on("error",D=>{S?.(D),T()}),x.on("close",()=>{!M&&A&&!y&&S?.(new F("Receiver disconnected before transfer completed.")),T()})}),{peer:w,code:h,stop:T}}async function Te(r){let{code:e,Peer:t,serverInfo:o,host:n,port:s,peerjsPath:l,secure:f=!1,iceServers:i,onStatus:p,onMeta:c,onData:b,onProgress:P,onComplete:B,onError:E,onDisconnect:_}=r;if(!e)throw new u("No sharing code was provided.");if(!t)throw new u("PeerJS Peer constructor is required. Install peerjs and pass it as the Peer option.");let R=o?.capabilities?.p2p;if(o&&!R?.enabled)throw new u("Direct transfer is disabled on this server.");let m=String(e).trim().replace(/\s+/g,"").toUpperCase();if(!ue(m))throw new u("Invalid direct transfer code.");let O=l??R?.peerjsPath??"/peerjs",k=i??R?.iceServers??[],z=ee({host:n,port:s,peerjsPath:O,secure:f,iceServers:k}),S=new t(void 0,z),g=0,v=0,I=0,V=120,a=Promise.resolve(),j=()=>{try{S.destroy()}catch{}};return S.on("error",w=>{E?.(w),j()}),S.on("open",()=>{let w=S.connect(m,{reliable:!0});w.on("open",()=>{p?.({phase:"connected",message:"Waiting for file details..."})}),w.on("data",async h=>{try{if(h&&typeof h=="object"&&!(h instanceof ArrayBuffer)&&!ArrayBuffer.isView(h)){let C=h;if(C.t==="meta"){let A=String(C.name||"file");g=Number(C.size)||0,v=0,a=Promise.resolve(),c?.({name:A,total:g}),P?.({received:v,total:g,percent:0});try{w.send({t:"ready"})}catch{}return}if(C.t==="end"){if(await a,g&&v<g){let A=new F("Transfer ended before the full file was received.");try{w.send({t:"error",message:A.message})}catch{}throw A}B?.({received:v,total:g});try{w.send({t:"ack",phase:"end",received:v,total:g})}catch{}return}if(C.t==="error")throw new F(C.message||"Sender reported an error.");return}let y;if(h instanceof ArrayBuffer)y=Promise.resolve(new Uint8Array(h));else if(ArrayBuffer.isView(h))y=Promise.resolve(new Uint8Array(h.buffer,h.byteOffset,h.byteLength));else if(typeof Blob<"u"&&h instanceof Blob)y=h.arrayBuffer().then(C=>new Uint8Array(C));else return;a=a.then(async()=>{let C=await y;b&&await b(C),v+=C.byteLength;let A=g?Math.min(100,v/g*100):0;P?.({received:v,total:g,percent:A});let M=Date.now();if(v===g||M-I>=V){I=M;try{w.send({t:"progress",received:v,total:g})}catch{}}}).catch(C=>{try{w.send({t:"error",message:C?.message||"Receiver write failed."})}catch{}E?.(C),j()})}catch(y){E?.(y),j()}}),w.on("close",()=>{v>0&&g>0&&v<g&&_?.()})}),{peer:S,stop:j}}return Ye(Ze);})();
2
+ if(typeof window!=="undefined"){window.DropgateCore=DropgateCore;}
3
+ //# sourceMappingURL=index.browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/errors.ts","../src/adapters/defaults.ts","../src/utils/base64.ts","../src/utils/lifetime.ts","../src/utils/semver.ts","../src/utils/filename.ts","../src/utils/network.ts","../src/crypto/decrypt.ts","../src/crypto/index.ts","../src/crypto/encrypt.ts","../src/client/DropgateClient.ts","../src/p2p/utils.ts","../src/p2p/helpers.ts","../src/p2p/send.ts","../src/p2p/receive.ts"],"sourcesContent":["// Constants\nexport {\n DEFAULT_CHUNK_SIZE,\n AES_GCM_IV_BYTES,\n AES_GCM_TAG_BYTES,\n ENCRYPTION_OVERHEAD_PER_CHUNK,\n} from './constants.js';\n\n// Errors\nexport {\n DropgateError,\n DropgateValidationError,\n DropgateNetworkError,\n DropgateProtocolError,\n DropgateAbortError,\n DropgateTimeoutError,\n} from './errors.js';\nexport type { DropgateErrorOptions } from './errors.js';\n\n// Types\nexport type {\n UploadCapabilities,\n P2PCapabilities,\n WebUICapabilities,\n ServerCapabilities,\n ServerInfo,\n ProgressEvent,\n UploadResult,\n CompatibilityResult,\n ShareTargetResult,\n CryptoAdapter,\n FetchFn,\n Base64Adapter,\n FileSource,\n LoggerFn,\n DropgateClientOptions,\n ServerTarget,\n UploadOptions,\n GetServerInfoOptions,\n ValidateUploadOptions,\n FileMetadata,\n DownloadProgressEvent,\n DownloadOptions,\n DownloadResult,\n} from './types.js';\n\n// Utils - Base64\nexport { bytesToBase64, arrayBufferToBase64, base64ToBytes } from './utils/base64.js';\n\n// Utils - Lifetime\nexport { lifetimeToMs } from './utils/lifetime.js';\n\n// Utils - Semver\nexport { parseSemverMajorMinor } from './utils/semver.js';\nexport type { SemverParts } from './utils/semver.js';\n\n// Utils - Filename\nexport { validatePlainFilename } from './utils/filename.js';\n\n// Utils - Network (internal helpers, but exported for advanced use)\nexport { sleep, makeAbortSignal, fetchJson, buildBaseUrl, parseServerUrl } from './utils/network.js';\nexport type { AbortSignalWithCleanup, FetchJsonResult, FetchJsonOptions } from './utils/network.js';\n\n// Crypto\nexport {\n sha256Hex,\n generateAesGcmKey,\n exportKeyBase64,\n importKeyFromBase64,\n decryptChunk,\n decryptFilenameFromBase64,\n} from './crypto/index.js';\nexport { encryptToBlob, encryptFilenameToBase64 } from './crypto/encrypt.js';\n\n// Client\nexport { DropgateClient, estimateTotalUploadSizeBytes } from './client/DropgateClient.js';\n\n// Adapters\nexport { getDefaultBase64, getDefaultCrypto, getDefaultFetch } from './adapters/defaults.js';\n\n// P2P - Re-export from p2p module for convenience\nexport {\n startP2PSend,\n startP2PReceive,\n generateP2PCode,\n isP2PCodeLike,\n isLocalhostHostname,\n isSecureContextForP2P,\n buildPeerOptions,\n createPeerWithRetries,\n} from './p2p/index.js';\n\nexport type {\n PeerConstructor,\n PeerInstance,\n PeerOptions,\n DataConnection,\n P2PSendOptions,\n P2PSendSession,\n P2PReceiveOptions,\n P2PReceiveSession,\n} from './p2p/index.js';\n","/**\n * Default chunk size for file uploads (5MB)\n */\nexport const DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024;\n\n/**\n * AES-GCM initialization vector size in bytes\n */\nexport const AES_GCM_IV_BYTES = 12;\n\n/**\n * AES-GCM authentication tag size in bytes\n */\nexport const AES_GCM_TAG_BYTES = 16;\n\n/**\n * Total encryption overhead per chunk (IV + tag)\n */\nexport const ENCRYPTION_OVERHEAD_PER_CHUNK = AES_GCM_IV_BYTES + AES_GCM_TAG_BYTES;\n\n/**\n * Maximum file size (in bytes) that can be downloaded without an onData callback.\n * Files larger than this require streaming via onData to avoid memory exhaustion.\n * Default: 100MB\n */\nexport const MAX_IN_MEMORY_DOWNLOAD_BYTES = 100 * 1024 * 1024;\n","export interface DropgateErrorOptions {\n code?: string;\n details?: unknown;\n cause?: unknown;\n}\n\n/**\n * Base error class for all Dropgate errors\n */\nexport class DropgateError extends Error {\n readonly code: string;\n readonly details?: unknown;\n\n constructor(message: string, opts: DropgateErrorOptions = {}) {\n super(message);\n this.name = this.constructor.name;\n this.code = opts.code || 'DROPGATE_ERROR';\n this.details = opts.details;\n if (opts.cause !== undefined) {\n // Use Object.defineProperty for cause to maintain compatibility\n Object.defineProperty(this, 'cause', {\n value: opts.cause,\n writable: false,\n enumerable: false,\n configurable: true,\n });\n }\n }\n}\n\n/**\n * Validation error for invalid inputs\n */\nexport class DropgateValidationError extends DropgateError {\n constructor(message: string, opts: DropgateErrorOptions = {}) {\n super(message, { ...opts, code: opts.code || 'VALIDATION_ERROR' });\n }\n}\n\n/**\n * Network error for connection issues\n */\nexport class DropgateNetworkError extends DropgateError {\n constructor(message: string, opts: DropgateErrorOptions = {}) {\n super(message, { ...opts, code: opts.code || 'NETWORK_ERROR' });\n }\n}\n\n/**\n * Protocol error for server communication issues\n */\nexport class DropgateProtocolError extends DropgateError {\n constructor(message: string, opts: DropgateErrorOptions = {}) {\n super(message, { ...opts, code: opts.code || 'PROTOCOL_ERROR' });\n }\n}\n\n/**\n * Abort error - replacement for DOMException with AbortError name\n * Used when operations are cancelled\n */\nexport class DropgateAbortError extends DropgateError {\n constructor(message = 'Operation aborted') {\n super(message, { code: 'ABORT_ERROR' });\n this.name = 'AbortError';\n }\n}\n\n/**\n * Timeout error - replacement for DOMException with TimeoutError name\n * Used when operations exceed their time limit\n */\nexport class DropgateTimeoutError extends DropgateError {\n constructor(message = 'Request timed out') {\n super(message, { code: 'TIMEOUT_ERROR' });\n this.name = 'TimeoutError';\n }\n}\n","import type { Base64Adapter, CryptoAdapter, FetchFn } from '../types.js';\n\n/**\n * Get the default Base64 adapter for the current environment.\n * Automatically detects Node.js Buffer vs browser btoa/atob.\n */\nexport function getDefaultBase64(): Base64Adapter {\n // Check for Node.js Buffer (works in Node.js and some bundlers)\n if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {\n return {\n encode(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString('base64');\n },\n decode(b64: string): Uint8Array {\n return new Uint8Array(Buffer.from(b64, 'base64'));\n },\n };\n }\n\n // Browser fallback using btoa/atob\n if (typeof btoa === 'function' && typeof atob === 'function') {\n return {\n encode(bytes: Uint8Array): string {\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary);\n },\n decode(b64: string): Uint8Array {\n const binary = atob(b64);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n out[i] = binary.charCodeAt(i);\n }\n return out;\n },\n };\n }\n\n throw new Error(\n 'No Base64 implementation available. Provide a Base64Adapter via options.'\n );\n}\n\n/**\n * Get the default crypto object for the current environment.\n * Returns globalThis.crypto if available.\n */\nexport function getDefaultCrypto(): CryptoAdapter | undefined {\n return globalThis.crypto as CryptoAdapter | undefined;\n}\n\n/**\n * Get the default fetch function for the current environment.\n * Returns globalThis.fetch if available.\n */\nexport function getDefaultFetch(): FetchFn | undefined {\n return globalThis.fetch?.bind(globalThis) as FetchFn | undefined;\n}\n","import type { Base64Adapter } from '../types.js';\nimport { getDefaultBase64 } from '../adapters/defaults.js';\n\nlet defaultAdapter: Base64Adapter | null = null;\n\nfunction getAdapter(adapter?: Base64Adapter): Base64Adapter {\n if (adapter) return adapter;\n if (!defaultAdapter) {\n defaultAdapter = getDefaultBase64();\n }\n return defaultAdapter;\n}\n\n/**\n * Convert a Uint8Array to a base64 string\n */\nexport function bytesToBase64(bytes: Uint8Array, adapter?: Base64Adapter): string {\n return getAdapter(adapter).encode(bytes);\n}\n\n/**\n * Convert an ArrayBuffer to a base64 string\n */\nexport function arrayBufferToBase64(buf: ArrayBuffer, adapter?: Base64Adapter): string {\n return bytesToBase64(new Uint8Array(buf), adapter);\n}\n\n/**\n * Convert a base64 string to a Uint8Array\n */\nexport function base64ToBytes(b64: string, adapter?: Base64Adapter): Uint8Array {\n return getAdapter(adapter).decode(b64);\n}\n","type LifetimeUnit = 'minutes' | 'hours' | 'days' | 'unlimited';\n\nconst MULTIPLIERS: Record<string, number> = {\n minutes: 60 * 1000,\n hours: 60 * 60 * 1000,\n days: 24 * 60 * 60 * 1000,\n};\n\n/**\n * Convert a lifetime value and unit to milliseconds.\n * Returns 0 for 'unlimited' or invalid inputs.\n */\nexport function lifetimeToMs(value: number, unit: LifetimeUnit | string): number {\n const u = String(unit || '').toLowerCase();\n const v = Number(value);\n\n if (u === 'unlimited') return 0;\n if (!Number.isFinite(v) || v <= 0) return 0;\n\n const m = MULTIPLIERS[u];\n if (!m) return 0;\n\n return Math.round(v * m);\n}\n","export interface SemverParts {\n major: number;\n minor: number;\n}\n\n/**\n * Parse a semver string and extract major.minor parts.\n * Returns { major: 0, minor: 0 } for invalid inputs.\n */\nexport function parseSemverMajorMinor(version: string | undefined | null): SemverParts {\n const parts = String(version || '')\n .split('.')\n .map((p) => Number(p));\n\n const major = Number.isFinite(parts[0]) ? parts[0] : 0;\n const minor = Number.isFinite(parts[1]) ? parts[1] : 0;\n\n return { major, minor };\n}\n","import { DropgateValidationError } from '../errors.js';\n\n/**\n * Validate a plain (non-encrypted) filename.\n * Throws DropgateValidationError if invalid.\n */\nexport function validatePlainFilename(filename: string): void {\n if (typeof filename !== 'string' || filename.trim().length === 0) {\n throw new DropgateValidationError(\n 'Invalid filename. Must be a non-empty string.'\n );\n }\n\n if (filename.length > 255 || /[\\/\\\\]/.test(filename)) {\n throw new DropgateValidationError(\n 'Invalid filename. Contains illegal characters or is too long.'\n );\n }\n}\n","import { DropgateAbortError, DropgateTimeoutError, DropgateValidationError } from '../errors.js';\nimport type { FetchFn, ServerTarget } from '../types.js';\n\n/**\n * Parse a server URL string into host, port, and secure components.\n * If no protocol is specified, defaults to HTTPS.\n */\nexport function parseServerUrl(urlStr: string): ServerTarget {\n let normalized = urlStr.trim();\n if (!normalized.startsWith('http://') && !normalized.startsWith('https://')) {\n normalized = 'https://' + normalized;\n }\n const url = new URL(normalized);\n return {\n host: url.hostname,\n port: url.port ? Number(url.port) : undefined,\n secure: url.protocol === 'https:',\n };\n}\n\n/**\n * Build a base URL from host, port, and secure options.\n */\nexport function buildBaseUrl(opts: ServerTarget): string {\n const { host, port, secure } = opts;\n\n if (!host || typeof host !== 'string') {\n throw new DropgateValidationError('Server host is required.');\n }\n\n const protocol = secure === false ? 'http' : 'https';\n const portSuffix = port ? `:${port}` : '';\n\n return `${protocol}://${host}${portSuffix}`;\n}\n\n/**\n * Sleep for a specified duration, with optional abort signal support.\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n return reject(signal.reason || new DropgateAbortError());\n }\n\n const t = setTimeout(resolve, ms);\n\n if (signal) {\n signal.addEventListener(\n 'abort',\n () => {\n clearTimeout(t);\n reject(signal.reason || new DropgateAbortError());\n },\n { once: true }\n );\n }\n });\n}\n\nexport interface AbortSignalWithCleanup {\n signal: AbortSignal;\n cleanup: () => void;\n}\n\n/**\n * Create an AbortSignal that combines a parent signal with a timeout.\n */\nexport function makeAbortSignal(\n parentSignal?: AbortSignal | null,\n timeoutMs?: number\n): AbortSignalWithCleanup {\n const controller = new AbortController();\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n const abort = (reason?: unknown): void => {\n if (!controller.signal.aborted) {\n controller.abort(reason);\n }\n };\n\n if (parentSignal) {\n if (parentSignal.aborted) {\n abort(parentSignal.reason);\n } else {\n parentSignal.addEventListener('abort', () => abort(parentSignal.reason), {\n once: true,\n });\n }\n }\n\n if (Number.isFinite(timeoutMs) && timeoutMs! > 0) {\n timeoutId = setTimeout(() => {\n abort(new DropgateTimeoutError());\n }, timeoutMs);\n }\n\n return {\n signal: controller.signal,\n cleanup: () => {\n if (timeoutId) clearTimeout(timeoutId);\n },\n };\n}\n\nexport interface FetchJsonResult {\n res: Response;\n json: unknown;\n text: string;\n}\n\nexport interface FetchJsonOptions extends Omit<RequestInit, 'signal'> {\n timeoutMs?: number;\n signal?: AbortSignal;\n}\n\n/**\n * Fetch JSON from a URL with timeout and error handling.\n */\nexport async function fetchJson(\n fetchFn: FetchFn,\n url: string,\n opts: FetchJsonOptions = {}\n): Promise<FetchJsonResult> {\n const { timeoutMs, signal, ...rest } = opts;\n const { signal: s, cleanup } = makeAbortSignal(signal, timeoutMs);\n\n try {\n const res = await fetchFn(url, { ...rest, signal: s });\n const text = await res.text();\n\n let json: unknown = null;\n try {\n json = text ? JSON.parse(text) : null;\n } catch {\n // Ignore parse errors - json will remain null\n }\n\n return { res, json, text };\n } finally {\n cleanup();\n }\n}\n","import { AES_GCM_IV_BYTES } from '../constants.js';\r\nimport type { CryptoAdapter, Base64Adapter } from '../types.js';\r\nimport { getDefaultBase64 } from '../adapters/defaults.js';\r\n\r\n/**\r\n * Import a base64-encoded AES-GCM key.\r\n * @param cryptoObj - Crypto adapter for key import.\r\n * @param keyB64 - Base64-encoded key bytes.\r\n * @param base64 - Optional base64 adapter.\r\n * @returns The imported CryptoKey.\r\n */\r\nexport async function importKeyFromBase64(\r\n cryptoObj: CryptoAdapter,\r\n keyB64: string,\r\n base64?: Base64Adapter\r\n): Promise<CryptoKey> {\r\n const adapter = base64 || getDefaultBase64();\r\n const keyBytes = adapter.decode(keyB64);\r\n // Create a new ArrayBuffer copy to satisfy TypeScript's BufferSource type\r\n const keyBuffer = new Uint8Array(keyBytes).buffer;\r\n return cryptoObj.subtle.importKey(\r\n 'raw',\r\n keyBuffer,\r\n { name: 'AES-GCM' },\r\n true,\r\n ['decrypt']\r\n );\r\n}\r\n\r\n/**\r\n * Decrypt an AES-GCM encrypted chunk.\r\n * Expected layout: [IV (12 bytes)] + [ciphertext + tag]\r\n * @param cryptoObj - Crypto adapter for decryption.\r\n * @param encryptedData - The encrypted data with IV prepended.\r\n * @param key - The AES-GCM decryption key.\r\n * @returns The decrypted data as ArrayBuffer.\r\n */\r\nexport async function decryptChunk(\r\n cryptoObj: CryptoAdapter,\r\n encryptedData: Uint8Array,\r\n key: CryptoKey\r\n): Promise<ArrayBuffer> {\r\n const iv = encryptedData.slice(0, AES_GCM_IV_BYTES);\r\n const ciphertext = encryptedData.slice(AES_GCM_IV_BYTES);\r\n return cryptoObj.subtle.decrypt(\r\n { name: 'AES-GCM', iv },\r\n key,\r\n ciphertext\r\n );\r\n}\r\n\r\n/**\r\n * Decrypt a base64-encoded encrypted filename.\r\n * @param cryptoObj - Crypto adapter for decryption.\r\n * @param encryptedFilenameB64 - Base64-encoded encrypted filename.\r\n * @param key - The AES-GCM decryption key.\r\n * @param base64 - Optional base64 adapter.\r\n * @returns The decrypted filename string.\r\n */\r\nexport async function decryptFilenameFromBase64(\r\n cryptoObj: CryptoAdapter,\r\n encryptedFilenameB64: string,\r\n key: CryptoKey,\r\n base64?: Base64Adapter\r\n): Promise<string> {\r\n const adapter = base64 || getDefaultBase64();\r\n const encryptedBytes = adapter.decode(encryptedFilenameB64);\r\n const decryptedBuffer = await decryptChunk(cryptoObj, encryptedBytes, key);\r\n return new TextDecoder().decode(decryptedBuffer);\r\n}\r\n","import type { CryptoAdapter } from '../types.js';\nimport { arrayBufferToBase64 } from '../utils/base64.js';\n\n/**\n * Compute SHA-256 hash of data and return as hex string.\n */\nexport async function sha256Hex(\n cryptoObj: CryptoAdapter,\n data: ArrayBuffer\n): Promise<string> {\n const hashBuffer = await cryptoObj.subtle.digest('SHA-256', data);\n const arr = new Uint8Array(hashBuffer);\n let hex = '';\n for (let i = 0; i < arr.length; i++) {\n hex += arr[i].toString(16).padStart(2, '0');\n }\n return hex;\n}\n\n/**\n * Generate a new AES-GCM 256-bit encryption key.\n */\nexport async function generateAesGcmKey(\n cryptoObj: CryptoAdapter\n): Promise<CryptoKey> {\n return cryptoObj.subtle.generateKey(\n { name: 'AES-GCM', length: 256 },\n true,\n ['encrypt', 'decrypt']\n );\n}\n\n/**\n * Export a CryptoKey to a base64-encoded raw key.\n */\nexport async function exportKeyBase64(\n cryptoObj: CryptoAdapter,\n key: CryptoKey\n): Promise<string> {\n const raw = await cryptoObj.subtle.exportKey('raw', key);\n return arrayBufferToBase64(raw);\n}\n\n// Re-export decryption functions\nexport { importKeyFromBase64, decryptChunk, decryptFilenameFromBase64 } from './decrypt.js';\n","import { AES_GCM_IV_BYTES } from '../constants.js';\nimport type { CryptoAdapter } from '../types.js';\nimport { arrayBufferToBase64 } from '../utils/base64.js';\n\n/**\n * Encrypt data using AES-GCM and return as a Blob with IV prepended.\n * Layout: [IV (12 bytes)] + [ciphertext + tag]\n */\nexport async function encryptToBlob(\n cryptoObj: CryptoAdapter,\n dataBuffer: ArrayBuffer,\n key: CryptoKey\n): Promise<Blob> {\n const iv = cryptoObj.getRandomValues(new Uint8Array(AES_GCM_IV_BYTES));\n const encrypted = await cryptoObj.subtle.encrypt(\n { name: 'AES-GCM', iv },\n key,\n dataBuffer\n );\n return new Blob([iv, new Uint8Array(encrypted)]);\n}\n\n/**\n * Encrypt a filename using AES-GCM and return as base64.\n */\nexport async function encryptFilenameToBase64(\n cryptoObj: CryptoAdapter,\n filename: string,\n key: CryptoKey\n): Promise<string> {\n const bytes = new TextEncoder().encode(String(filename));\n const blob = await encryptToBlob(cryptoObj, bytes.buffer, key);\n const buf = await blob.arrayBuffer();\n return arrayBufferToBase64(buf);\n}\n","import { DEFAULT_CHUNK_SIZE, ENCRYPTION_OVERHEAD_PER_CHUNK, MAX_IN_MEMORY_DOWNLOAD_BYTES } from '../constants.js';\nimport {\n DropgateError,\n DropgateValidationError,\n DropgateNetworkError,\n DropgateProtocolError,\n DropgateAbortError,\n} from '../errors.js';\nimport type {\n CryptoAdapter,\n FetchFn,\n LoggerFn,\n ServerInfo,\n CompatibilityResult,\n ShareTargetResult,\n UploadResult,\n ProgressEvent,\n DropgateClientOptions,\n UploadOptions,\n GetServerInfoOptions,\n ValidateUploadOptions,\n FileSource,\n Base64Adapter,\n DownloadOptions,\n DownloadResult,\n DownloadProgressEvent,\n FileMetadata,\n} from '../types.js';\nimport { getDefaultCrypto, getDefaultFetch, getDefaultBase64 } from '../adapters/defaults.js';\nimport { makeAbortSignal, fetchJson, sleep, buildBaseUrl } from '../utils/network.js';\nimport { parseSemverMajorMinor } from '../utils/semver.js';\nimport { validatePlainFilename } from '../utils/filename.js';\nimport { sha256Hex, generateAesGcmKey, exportKeyBase64, importKeyFromBase64, decryptChunk, decryptFilenameFromBase64 } from '../crypto/index.js';\nimport { encryptToBlob, encryptFilenameToBase64 } from '../crypto/encrypt.js';\n\n/**\n * Estimate total upload size including encryption overhead.\n */\nexport function estimateTotalUploadSizeBytes(\n fileSizeBytes: number,\n totalChunks: number,\n isEncrypted: boolean\n): number {\n const base = Number(fileSizeBytes) || 0;\n if (!isEncrypted) return base;\n return base + (Number(totalChunks) || 0) * ENCRYPTION_OVERHEAD_PER_CHUNK;\n}\n\n/**\n * Headless, environment-agnostic client for Dropgate file uploads.\n * Handles server communication, encryption, and chunked uploads.\n */\nexport class DropgateClient {\n /** Client version string for compatibility checking. */\n readonly clientVersion: string;\n /** Chunk size in bytes for upload splitting. */\n readonly chunkSize: number;\n /** Fetch implementation used for HTTP requests. */\n readonly fetchFn: FetchFn;\n /** Crypto implementation for encryption operations. */\n readonly cryptoObj: CryptoAdapter;\n /** Base64 encoder/decoder for binary data. */\n readonly base64: Base64Adapter;\n /** Optional logger for debug output. */\n readonly logger: LoggerFn | null;\n\n /**\n * Create a new DropgateClient instance.\n * @param opts - Client configuration options.\n * @throws {DropgateValidationError} If clientVersion is missing or invalid.\n */\n constructor(opts: DropgateClientOptions) {\n if (!opts || typeof opts.clientVersion !== 'string') {\n throw new DropgateValidationError(\n 'DropgateClient requires clientVersion (string).'\n );\n }\n\n this.clientVersion = opts.clientVersion;\n this.chunkSize = Number.isFinite(opts.chunkSize)\n ? opts.chunkSize!\n : DEFAULT_CHUNK_SIZE;\n\n const fetchFn = opts.fetchFn || getDefaultFetch();\n if (!fetchFn) {\n throw new DropgateValidationError('No fetch() implementation found.');\n }\n this.fetchFn = fetchFn;\n\n const cryptoObj = opts.cryptoObj || getDefaultCrypto();\n if (!cryptoObj) {\n throw new DropgateValidationError('No crypto implementation found.');\n }\n this.cryptoObj = cryptoObj;\n\n this.base64 = opts.base64 || getDefaultBase64();\n this.logger = opts.logger || null;\n }\n\n /**\n * Fetch server information from the /api/info endpoint.\n * @param opts - Server target and request options.\n * @returns The server base URL and server info object.\n * @throws {DropgateNetworkError} If the server cannot be reached.\n * @throws {DropgateProtocolError} If the server returns an invalid response.\n */\n async getServerInfo(\n opts: GetServerInfoOptions\n ): Promise<{ baseUrl: string; serverInfo: ServerInfo }> {\n const { host, port, secure, timeoutMs = 5000, signal } = opts;\n\n const baseUrl = buildBaseUrl({ host, port, secure });\n\n try {\n const { res, json } = await fetchJson(\n this.fetchFn,\n `${baseUrl}/api/info`,\n {\n method: 'GET',\n timeoutMs,\n signal,\n headers: { Accept: 'application/json' },\n }\n );\n\n if (res.ok && json && typeof json === 'object' && 'version' in json) {\n return { baseUrl, serverInfo: json as ServerInfo };\n }\n\n throw new DropgateProtocolError(\n `Server info request failed (status ${res.status}).`\n );\n } catch (err) {\n if (err instanceof DropgateError) throw err;\n throw new DropgateNetworkError('Could not reach server /api/info.', {\n cause: err,\n });\n }\n }\n\n /**\n * Resolve a user-entered sharing code or URL via the server.\n * @param value - The sharing code or URL to resolve.\n * @param opts - Server target and request options.\n * @returns The resolved share target information.\n * @throws {DropgateProtocolError} If the share lookup fails.\n */\n async resolveShareTarget(\n value: string,\n opts: GetServerInfoOptions\n ): Promise<ShareTargetResult> {\n const { host, port, secure, timeoutMs = 5000, signal } = opts;\n\n const baseUrl = buildBaseUrl({ host, port, secure });\n\n const { res, json } = await fetchJson(\n this.fetchFn,\n `${baseUrl}/api/resolve`,\n {\n method: 'POST',\n timeoutMs,\n signal,\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify({ value }),\n }\n );\n\n if (!res.ok) {\n const msg =\n (json && typeof json === 'object' && 'error' in json\n ? (json as { error: string }).error\n : null) || `Share lookup failed (status ${res.status}).`;\n throw new DropgateProtocolError(msg, { details: json });\n }\n\n return (json as ShareTargetResult) || { valid: false, reason: 'Unknown response.' };\n }\n\n /**\n * Check version compatibility between this client and a server.\n * @param serverInfo - Server info containing the version to check against.\n * @returns Compatibility result with status and message.\n */\n checkCompatibility(serverInfo: ServerInfo): CompatibilityResult {\n const serverVersion = String(serverInfo?.version || '0.0.0');\n const clientVersion = String(this.clientVersion || '0.0.0');\n\n const c = parseSemverMajorMinor(clientVersion);\n const s = parseSemverMajorMinor(serverVersion);\n\n if (c.major !== s.major) {\n return {\n compatible: false,\n clientVersion,\n serverVersion,\n message: `Incompatible versions. Client v${clientVersion}, Server v${serverVersion}${serverInfo?.name ? ` (${serverInfo.name})` : ''}.`,\n };\n }\n\n if (c.minor > s.minor) {\n return {\n compatible: true,\n clientVersion,\n serverVersion,\n message: `Client (v${clientVersion}) is newer than Server (v${serverVersion})${serverInfo?.name ? ` (${serverInfo.name})` : ''}. Some features may not work.`,\n };\n }\n\n return {\n compatible: true,\n clientVersion,\n serverVersion,\n message: `Server: v${serverVersion}, Client: v${clientVersion}${serverInfo?.name ? ` (${serverInfo.name})` : ''}.`,\n };\n }\n\n /**\n * Validate file and upload settings against server capabilities.\n * @param opts - Validation options containing file, settings, and server info.\n * @returns True if validation passes.\n * @throws {DropgateValidationError} If any validation check fails.\n */\n validateUploadInputs(opts: ValidateUploadOptions): boolean {\n const { file, lifetimeMs, encrypt, serverInfo } = opts;\n const caps = serverInfo?.capabilities?.upload;\n\n if (!caps || !caps.enabled) {\n throw new DropgateValidationError('Server does not support file uploads.');\n }\n\n // Check file validity\n const fileSize = Number(file?.size || 0);\n if (!file || !Number.isFinite(fileSize) || fileSize <= 0) {\n throw new DropgateValidationError('File is missing or invalid.');\n }\n\n // maxSizeMB: 0 means unlimited\n const maxMB = Number(caps.maxSizeMB);\n if (Number.isFinite(maxMB) && maxMB > 0) {\n const limitBytes = maxMB * 1000 * 1000;\n const totalChunks = Math.ceil(fileSize / this.chunkSize);\n const estimatedBytes = estimateTotalUploadSizeBytes(\n fileSize,\n totalChunks,\n Boolean(encrypt)\n );\n if (estimatedBytes > limitBytes) {\n const msg = encrypt\n ? `File too large once encryption overhead is included. Server limit: ${maxMB} MB.`\n : `File too large. Server limit: ${maxMB} MB.`;\n throw new DropgateValidationError(msg);\n }\n }\n\n // maxLifetimeHours: 0 means unlimited is allowed\n const maxHours = Number(caps.maxLifetimeHours);\n const lt = Number(lifetimeMs);\n if (!Number.isFinite(lt) || lt < 0 || !Number.isInteger(lt)) {\n throw new DropgateValidationError(\n 'Invalid lifetime. Must be a non-negative integer (milliseconds).'\n );\n }\n\n if (Number.isFinite(maxHours) && maxHours > 0) {\n const limitMs = Math.round(maxHours * 60 * 60 * 1000);\n if (lt === 0) {\n throw new DropgateValidationError(\n `Server does not allow unlimited file lifetime. Max: ${maxHours} hours.`\n );\n }\n if (lt > limitMs) {\n throw new DropgateValidationError(\n `File lifetime too long. Server limit: ${maxHours} hours.`\n );\n }\n }\n\n // Encryption support\n if (encrypt && !caps.e2ee) {\n throw new DropgateValidationError(\n 'Server does not support end-to-end encryption.'\n );\n }\n\n return true;\n }\n\n /**\n * Upload a file to the server with optional encryption.\n * @param opts - Upload options including file, server target, and settings.\n * @returns Upload result containing the download URL and file identifiers.\n * @throws {DropgateValidationError} If input validation fails.\n * @throws {DropgateNetworkError} If the server cannot be reached.\n * @throws {DropgateProtocolError} If the server returns an error.\n * @throws {DropgateAbortError} If the upload is cancelled.\n */\n async uploadFile(opts: UploadOptions): Promise<UploadResult> {\n const {\n host,\n port,\n secure,\n file,\n lifetimeMs,\n encrypt,\n filenameOverride,\n onProgress,\n signal,\n timeouts = {},\n retry = {},\n } = opts;\n\n const progress = (evt: ProgressEvent): void => {\n try {\n if (onProgress) onProgress(evt);\n } catch {\n // Ignore UI callback failures\n }\n };\n\n if (!this.cryptoObj?.subtle) {\n throw new DropgateValidationError(\n 'Web Crypto API not available (crypto.subtle).'\n );\n }\n\n // 0) Get server info + compat\n progress({ phase: 'server-info', text: 'Checking server...' });\n\n let baseUrl: string;\n let serverInfo: ServerInfo;\n try {\n const res = await this.getServerInfo({\n host,\n port,\n secure,\n timeoutMs: timeouts.serverInfoMs ?? 5000,\n signal,\n });\n baseUrl = res.baseUrl;\n serverInfo = res.serverInfo;\n } catch (err) {\n if (err instanceof DropgateError) throw err;\n throw new DropgateNetworkError('Could not connect to the server.', {\n cause: err,\n });\n }\n\n const compat = this.checkCompatibility(serverInfo);\n progress({ phase: 'server-compat', text: compat.message });\n if (!compat.compatible) {\n throw new DropgateValidationError(compat.message);\n }\n\n // 1) Validate inputs\n const filename = filenameOverride ?? file.name ?? 'file';\n\n if (!encrypt) {\n validatePlainFilename(filename);\n }\n\n this.validateUploadInputs({ file, lifetimeMs, encrypt, serverInfo });\n\n // 2) Encryption prep\n let cryptoKey: CryptoKey | null = null;\n let keyB64: string | null = null;\n let transmittedFilename = filename;\n\n if (encrypt) {\n progress({ phase: 'crypto', text: 'Generating encryption key...' });\n try {\n cryptoKey = await generateAesGcmKey(this.cryptoObj);\n keyB64 = await exportKeyBase64(this.cryptoObj, cryptoKey);\n transmittedFilename = await encryptFilenameToBase64(\n this.cryptoObj,\n filename,\n cryptoKey\n );\n } catch (err) {\n throw new DropgateError('Failed to prepare encryption.', {\n code: 'CRYPTO_PREP_FAILED',\n cause: err,\n });\n }\n }\n\n // 3) Compute reservation sizes\n const totalChunks = Math.ceil(file.size / this.chunkSize);\n const totalUploadSize = estimateTotalUploadSizeBytes(\n file.size,\n totalChunks,\n encrypt\n );\n\n // 4) Init\n progress({ phase: 'init', text: 'Reserving server storage...' });\n\n const initPayload = {\n filename: transmittedFilename,\n lifetime: lifetimeMs,\n isEncrypted: Boolean(encrypt),\n totalSize: totalUploadSize,\n totalChunks,\n };\n\n const initRes = await fetchJson(this.fetchFn, `${baseUrl}/upload/init`, {\n method: 'POST',\n timeoutMs: timeouts.initMs ?? 15000,\n signal,\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(initPayload),\n });\n\n if (!initRes.res.ok) {\n const errorJson = initRes.json as { error?: string } | null;\n const msg =\n errorJson?.error ||\n `Server initialisation failed: ${initRes.res.status}`;\n throw new DropgateProtocolError(msg, {\n details: initRes.json || initRes.text,\n });\n }\n\n const initJson = initRes.json as { uploadId?: string } | null;\n const uploadId = initJson?.uploadId;\n if (!uploadId || typeof uploadId !== 'string') {\n throw new DropgateProtocolError(\n 'Server did not return a valid uploadId.'\n );\n }\n\n // 5) Chunks\n const retries = Number.isFinite(retry.retries) ? retry.retries! : 5;\n const baseBackoffMs = Number.isFinite(retry.backoffMs)\n ? retry.backoffMs!\n : 1000;\n const maxBackoffMs = Number.isFinite(retry.maxBackoffMs)\n ? retry.maxBackoffMs!\n : 30000;\n\n for (let i = 0; i < totalChunks; i++) {\n if (signal?.aborted) {\n throw signal.reason || new DropgateAbortError();\n }\n\n const start = i * this.chunkSize;\n const end = Math.min(start + this.chunkSize, file.size);\n let chunkBlob: Blob | FileSource = file.slice(start, end);\n\n const percentComplete = (i / totalChunks) * 100;\n progress({\n phase: 'chunk',\n text: `Uploading chunk ${i + 1} of ${totalChunks}...`,\n percent: percentComplete,\n chunkIndex: i,\n totalChunks,\n });\n\n // Get ArrayBuffer from the slice\n const chunkBuffer = await chunkBlob.arrayBuffer();\n\n // Encrypt if needed\n let uploadBlob: Blob;\n if (encrypt && cryptoKey) {\n uploadBlob = await encryptToBlob(this.cryptoObj, chunkBuffer, cryptoKey);\n } else {\n uploadBlob = new Blob([chunkBuffer]);\n }\n\n // Server validates: chunk <= 5MB + 1024\n if (uploadBlob.size > DEFAULT_CHUNK_SIZE + 1024) {\n throw new DropgateValidationError(\n 'Chunk too large (client-side). Check chunk size settings.'\n );\n }\n\n // Hash encrypted/plain payload\n const toHash = await uploadBlob.arrayBuffer();\n const hashHex = await sha256Hex(this.cryptoObj, toHash);\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/octet-stream',\n 'X-Upload-ID': uploadId,\n 'X-Chunk-Index': String(i),\n 'X-Chunk-Hash': hashHex,\n };\n\n const chunkUrl = `${baseUrl}/upload/chunk`;\n await this.attemptChunkUpload(\n chunkUrl,\n {\n method: 'POST',\n headers,\n body: uploadBlob,\n },\n {\n retries,\n backoffMs: baseBackoffMs,\n maxBackoffMs,\n timeoutMs: timeouts.chunkMs ?? 60000,\n signal,\n progress,\n chunkIndex: i,\n totalChunks,\n }\n );\n }\n\n // 6) Complete\n progress({ phase: 'complete', text: 'Finalising upload...', percent: 100 });\n\n const completeRes = await fetchJson(\n this.fetchFn,\n `${baseUrl}/upload/complete`,\n {\n method: 'POST',\n timeoutMs: timeouts.completeMs ?? 30000,\n signal,\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify({ uploadId }),\n }\n );\n\n if (!completeRes.res.ok) {\n const errorJson = completeRes.json as { error?: string } | null;\n const msg = errorJson?.error || 'Finalisation failed.';\n throw new DropgateProtocolError(msg, {\n details: completeRes.json || completeRes.text,\n });\n }\n\n const completeJson = completeRes.json as { id?: string } | null;\n const fileId = completeJson?.id;\n if (!fileId || typeof fileId !== 'string') {\n throw new DropgateProtocolError(\n 'Server did not return a valid file id.'\n );\n }\n\n let downloadUrl = `${baseUrl}/${fileId}`;\n if (encrypt && keyB64) {\n downloadUrl += `#${keyB64}`;\n }\n\n progress({ phase: 'done', text: 'Upload successful!', percent: 100 });\n\n return {\n downloadUrl,\n fileId,\n uploadId,\n baseUrl,\n ...(encrypt && keyB64 ? { keyB64 } : {}),\n };\n }\n\n /**\n * Download a file from the server with optional decryption.\n *\n * **Important:** For large files, you must provide an `onData` callback to stream\n * data incrementally. Without it, the entire file is buffered in memory, which will\n * cause memory exhaustion for large files. Files exceeding 100MB without an `onData`\n * callback will throw a validation error.\n *\n * @param opts - Download options including file ID, server target, and optional key.\n * @param opts.onData - Streaming callback that receives data chunks. Required for files > 100MB.\n * @returns Download result containing filename and received bytes.\n * @throws {DropgateValidationError} If input validation fails or file is too large without onData.\n * @throws {DropgateNetworkError} If the server cannot be reached.\n * @throws {DropgateProtocolError} If the server returns an error.\n * @throws {DropgateAbortError} If the download is cancelled.\n */\n async downloadFile(opts: DownloadOptions): Promise<DownloadResult> {\n const {\n host,\n port,\n secure,\n fileId,\n keyB64,\n onProgress,\n onData,\n signal,\n timeoutMs = 60000,\n } = opts;\n\n const progress = (evt: DownloadProgressEvent): void => {\n try {\n if (onProgress) onProgress(evt);\n } catch {\n // Ignore UI callback failures\n }\n };\n\n if (!fileId || typeof fileId !== 'string') {\n throw new DropgateValidationError('File ID is required.');\n }\n\n const baseUrl = buildBaseUrl({ host, port, secure });\n\n // 1) Fetch metadata\n progress({ phase: 'metadata', text: 'Fetching file info...', receivedBytes: 0, totalBytes: 0, percent: 0 });\n\n const { signal: metaSignal, cleanup: metaCleanup } = makeAbortSignal(signal, timeoutMs);\n let metadata: FileMetadata;\n\n try {\n const metaRes = await this.fetchFn(`${baseUrl}/api/file/${fileId}/meta`, {\n method: 'GET',\n headers: { Accept: 'application/json' },\n signal: metaSignal,\n });\n\n if (!metaRes.ok) {\n if (metaRes.status === 404) {\n throw new DropgateProtocolError('File not found or has expired.');\n }\n throw new DropgateProtocolError(`Failed to fetch file metadata (status ${metaRes.status}).`);\n }\n\n metadata = await metaRes.json() as FileMetadata;\n } catch (err) {\n if (err instanceof DropgateError) throw err;\n if (err instanceof Error && err.name === 'AbortError') {\n throw new DropgateAbortError('Download cancelled.');\n }\n throw new DropgateNetworkError('Could not fetch file metadata.', { cause: err });\n } finally {\n metaCleanup();\n }\n\n const isEncrypted = Boolean(metadata.isEncrypted);\n const totalBytes = metadata.sizeBytes || 0;\n\n // Check if file is too large to buffer in memory without streaming\n if (!onData && totalBytes > MAX_IN_MEMORY_DOWNLOAD_BYTES) {\n const sizeMB = Math.round(totalBytes / (1024 * 1024));\n const limitMB = Math.round(MAX_IN_MEMORY_DOWNLOAD_BYTES / (1024 * 1024));\n throw new DropgateValidationError(\n `File is too large (${sizeMB}MB) to download without streaming. ` +\n `Provide an onData callback to stream files larger than ${limitMB}MB.`\n );\n }\n\n // 2) Decrypt filename if encrypted\n let filename: string;\n let cryptoKey: CryptoKey | undefined;\n\n if (isEncrypted) {\n if (!keyB64) {\n throw new DropgateValidationError('Decryption key is required for encrypted files.');\n }\n\n if (!this.cryptoObj?.subtle) {\n throw new DropgateValidationError('Web Crypto API not available for decryption.');\n }\n\n progress({ phase: 'decrypting', text: 'Preparing decryption...', receivedBytes: 0, totalBytes: 0, percent: 0 });\n\n try {\n cryptoKey = await importKeyFromBase64(this.cryptoObj, keyB64, this.base64);\n filename = await decryptFilenameFromBase64(\n this.cryptoObj,\n metadata.encryptedFilename!,\n cryptoKey,\n this.base64\n );\n } catch (err) {\n throw new DropgateError('Failed to decrypt filename. Invalid key or corrupted data.', {\n code: 'DECRYPT_FILENAME_FAILED',\n cause: err,\n });\n }\n } else {\n filename = metadata.filename || 'file';\n }\n\n // 3) Download file content\n progress({ phase: 'downloading', text: 'Starting download...', percent: 0, receivedBytes: 0, totalBytes });\n\n const { signal: downloadSignal, cleanup: downloadCleanup } = makeAbortSignal(signal, timeoutMs);\n let receivedBytes = 0;\n const dataChunks: Uint8Array[] = [];\n const collectData = !onData;\n\n try {\n const downloadRes = await this.fetchFn(`${baseUrl}/api/file/${fileId}`, {\n method: 'GET',\n signal: downloadSignal,\n });\n\n if (!downloadRes.ok) {\n throw new DropgateProtocolError(`Download failed (status ${downloadRes.status}).`);\n }\n\n if (!downloadRes.body) {\n throw new DropgateProtocolError('Streaming response not available.');\n }\n\n const reader = downloadRes.body.getReader();\n\n if (isEncrypted && cryptoKey) {\n // Encrypted: buffer and decrypt chunks\n // Use a chunk array to avoid repeated array copying on each read\n const ENCRYPTED_CHUNK_SIZE = this.chunkSize + ENCRYPTION_OVERHEAD_PER_CHUNK;\n const pendingChunks: Uint8Array[] = [];\n let pendingLength = 0;\n\n // Helper to concatenate pending chunks into a single buffer\n const flushPending = (): Uint8Array => {\n if (pendingChunks.length === 0) return new Uint8Array(0);\n if (pendingChunks.length === 1) {\n const result = pendingChunks[0];\n pendingChunks.length = 0;\n pendingLength = 0;\n return result;\n }\n const result = new Uint8Array(pendingLength);\n let offset = 0;\n for (const chunk of pendingChunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n pendingChunks.length = 0;\n pendingLength = 0;\n return result;\n };\n\n while (true) {\n if (signal?.aborted) {\n throw new DropgateAbortError('Download cancelled.');\n }\n\n const { done, value } = await reader.read();\n if (done) break;\n\n // Append to pending chunks (no copying yet)\n pendingChunks.push(value);\n pendingLength += value.length;\n\n // Process complete encrypted chunks when we have enough data\n while (pendingLength >= ENCRYPTED_CHUNK_SIZE) {\n const buffer = flushPending();\n const encryptedChunk = buffer.subarray(0, ENCRYPTED_CHUNK_SIZE);\n\n // Keep the remainder for next iteration\n if (buffer.length > ENCRYPTED_CHUNK_SIZE) {\n const remainder = buffer.subarray(ENCRYPTED_CHUNK_SIZE);\n pendingChunks.push(remainder);\n pendingLength = remainder.length;\n }\n\n const decryptedBuffer = await decryptChunk(this.cryptoObj, encryptedChunk, cryptoKey);\n const decryptedData = new Uint8Array(decryptedBuffer);\n\n if (collectData) {\n dataChunks.push(decryptedData);\n } else {\n await onData!(decryptedData);\n }\n }\n\n receivedBytes += value.length;\n const percent = totalBytes > 0 ? Math.round((receivedBytes / totalBytes) * 100) : 0;\n progress({\n phase: 'decrypting',\n text: `Downloading & decrypting... (${percent}%)`,\n percent,\n receivedBytes,\n totalBytes,\n });\n }\n\n // Process remaining buffer (final chunk)\n if (pendingLength > 0) {\n const buffer = flushPending();\n const decryptedBuffer = await decryptChunk(this.cryptoObj, buffer, cryptoKey);\n const decryptedData = new Uint8Array(decryptedBuffer);\n\n if (collectData) {\n dataChunks.push(decryptedData);\n } else {\n await onData!(decryptedData);\n }\n }\n } else {\n // Plain: stream through directly\n while (true) {\n if (signal?.aborted) {\n throw new DropgateAbortError('Download cancelled.');\n }\n\n const { done, value } = await reader.read();\n if (done) break;\n\n if (collectData) {\n dataChunks.push(value);\n } else {\n await onData!(value);\n }\n\n receivedBytes += value.length;\n const percent = totalBytes > 0 ? Math.round((receivedBytes / totalBytes) * 100) : 0;\n progress({\n phase: 'downloading',\n text: `Downloading... (${percent}%)`,\n percent,\n receivedBytes,\n totalBytes,\n });\n }\n }\n } catch (err) {\n if (err instanceof DropgateError) throw err;\n if (err instanceof Error && err.name === 'AbortError') {\n throw new DropgateAbortError('Download cancelled.');\n }\n throw new DropgateNetworkError('Download failed.', { cause: err });\n } finally {\n downloadCleanup();\n }\n\n progress({ phase: 'complete', text: 'Download complete!', percent: 100, receivedBytes, totalBytes });\n\n // Combine collected data if not using callback\n let data: Uint8Array | undefined;\n if (collectData && dataChunks.length > 0) {\n const totalLength = dataChunks.reduce((sum, chunk) => sum + chunk.length, 0);\n data = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of dataChunks) {\n data.set(chunk, offset);\n offset += chunk.length;\n }\n }\n\n return {\n filename,\n receivedBytes,\n wasEncrypted: isEncrypted,\n ...(data ? { data } : {}),\n };\n }\n\n private async attemptChunkUpload(\n url: string,\n fetchOptions: RequestInit,\n opts: {\n retries: number;\n backoffMs: number;\n maxBackoffMs: number;\n timeoutMs: number;\n signal?: AbortSignal;\n progress: (evt: ProgressEvent) => void;\n chunkIndex: number;\n totalChunks: number;\n }\n ): Promise<void> {\n const {\n retries,\n backoffMs,\n maxBackoffMs,\n timeoutMs,\n signal,\n progress,\n chunkIndex,\n totalChunks,\n } = opts;\n\n let attemptsLeft = retries;\n let currentBackoff = backoffMs;\n const maxRetries = retries;\n\n while (true) {\n if (signal?.aborted) {\n throw signal.reason || new DropgateAbortError();\n }\n\n const { signal: s, cleanup } = makeAbortSignal(signal, timeoutMs);\n try {\n const res = await this.fetchFn(url, { ...fetchOptions, signal: s });\n if (res.ok) return;\n\n const text = await res.text().catch(() => '');\n const err = new DropgateProtocolError(\n `Chunk ${chunkIndex + 1} failed (HTTP ${res.status}).`,\n {\n details: { status: res.status, bodySnippet: text.slice(0, 120) },\n }\n );\n throw err;\n } catch (err) {\n cleanup();\n\n // AbortError should not retry\n if (\n err instanceof Error &&\n (err.name === 'AbortError' || (err as { code?: string }).code === 'ABORT_ERR')\n ) {\n throw err;\n }\n if (signal?.aborted) {\n throw signal.reason || new DropgateAbortError();\n }\n\n if (attemptsLeft <= 0) {\n throw err instanceof DropgateError\n ? err\n : new DropgateNetworkError('Chunk upload failed.', { cause: err });\n }\n\n const attemptNumber = maxRetries - attemptsLeft + 1;\n let remaining = currentBackoff;\n const tick = 100;\n while (remaining > 0) {\n const secondsLeft = (remaining / 1000).toFixed(1);\n progress({\n phase: 'retry-wait',\n text: `Chunk upload failed. Retrying in ${secondsLeft}s... (${attemptNumber}/${maxRetries})`,\n chunkIndex,\n totalChunks,\n });\n await sleep(Math.min(tick, remaining), signal);\n remaining -= tick;\n }\n\n progress({\n phase: 'retry',\n text: `Chunk upload failed. Retrying now... (${attemptNumber}/${maxRetries})`,\n chunkIndex,\n totalChunks,\n });\n\n attemptsLeft -= 1;\n currentBackoff = Math.min(currentBackoff * 2, maxBackoffMs);\n continue;\n } finally {\n cleanup();\n }\n }\n }\n}\n","import type { CryptoAdapter } from '../types.js';\nimport { getDefaultCrypto } from '../adapters/defaults.js';\n\n/**\n * Check if a hostname is localhost\n */\nexport function isLocalhostHostname(hostname: string): boolean {\n const host = String(hostname || '').toLowerCase();\n return host === 'localhost' || host === '127.0.0.1' || host === '::1';\n}\n\n/**\n * Check if the current context allows P2P (HTTPS or localhost)\n */\nexport function isSecureContextForP2P(\n hostname?: string,\n isSecureContext?: boolean\n): boolean {\n return Boolean(isSecureContext) || isLocalhostHostname(hostname || '');\n}\n\n/**\n * Generate a P2P sharing code using cryptographically secure random.\n * Format: XXXX-0000 (4 letters + 4 digits)\n */\nexport function generateP2PCode(cryptoObj?: CryptoAdapter): string {\n const crypto = cryptoObj || getDefaultCrypto();\n const letters = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; // Excluded I and O to avoid confusion\n\n if (crypto) {\n const randomBytes = new Uint8Array(8);\n crypto.getRandomValues(randomBytes);\n\n let letterPart = '';\n for (let i = 0; i < 4; i++) {\n letterPart += letters[randomBytes[i] % letters.length];\n }\n\n let numberPart = '';\n for (let i = 4; i < 8; i++) {\n numberPart += (randomBytes[i] % 10).toString();\n }\n\n return `${letterPart}-${numberPart}`;\n }\n\n // Fallback to Math.random (less secure, but works everywhere)\n let a = '';\n for (let i = 0; i < 4; i++) {\n a += letters[Math.floor(Math.random() * letters.length)];\n }\n let b = '';\n for (let i = 0; i < 4; i++) {\n b += Math.floor(Math.random() * 10);\n }\n return `${a}-${b}`;\n}\n\n/**\n * Check if a string looks like a P2P sharing code\n */\nexport function isP2PCodeLike(code: string): boolean {\n return /^[A-Z]{4}-\\d{4}$/.test(String(code || '').trim());\n}\n","import { DropgateNetworkError } from '../errors.js';\nimport type { PeerInstance, PeerOptions } from './types.js';\n\nexport interface BuildPeerOptionsInput {\n host?: string;\n port?: number;\n peerjsPath?: string;\n secure?: boolean;\n iceServers?: RTCIceServer[];\n}\n\n/**\n * Build PeerJS connection options\n */\nexport function buildPeerOptions(opts: BuildPeerOptionsInput = {}): PeerOptions {\n const { host, port, peerjsPath = '/peerjs', secure = false, iceServers = [] } = opts;\n\n const peerOpts: PeerOptions = {\n host,\n path: peerjsPath,\n secure,\n config: { iceServers },\n debug: 0,\n };\n\n if (port) {\n peerOpts.port = port;\n }\n\n return peerOpts;\n}\n\nexport interface CreatePeerWithRetriesOptions {\n code?: string | null;\n codeGenerator: () => string;\n maxAttempts: number;\n buildPeer: (id: string) => PeerInstance;\n onCode?: (code: string, attempt: number) => void;\n}\n\n/**\n * Create a peer with retries if the code is already taken\n */\nexport async function createPeerWithRetries(\n opts: CreatePeerWithRetriesOptions\n): Promise<{ peer: PeerInstance; code: string }> {\n const { code, codeGenerator, maxAttempts, buildPeer, onCode } = opts;\n\n let nextCode = code || codeGenerator();\n let peer: PeerInstance | null = null;\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n onCode?.(nextCode, attempt);\n\n try {\n peer = await new Promise<PeerInstance>((resolve, reject) => {\n const instance = buildPeer(nextCode);\n instance.on('open', () => resolve(instance));\n instance.on('error', (err: Error) => {\n try {\n instance.destroy();\n } catch {\n // Ignore destroy errors\n }\n reject(err);\n });\n });\n\n return { peer, code: nextCode };\n } catch (err) {\n lastError = err as Error;\n nextCode = codeGenerator();\n }\n }\n\n throw lastError || new DropgateNetworkError('Could not establish PeerJS connection.');\n}\n","import { DropgateValidationError, DropgateNetworkError } from '../errors.js';\nimport { sleep } from '../utils/network.js';\nimport type { P2PSendOptions, P2PSendSession, DataConnection } from './types.js';\nimport { generateP2PCode } from './utils.js';\nimport { buildPeerOptions, createPeerWithRetries } from './helpers.js';\n\n/**\n * Start a direct transfer (P2P) sender session.\n *\n * IMPORTANT: Consumer must provide the PeerJS Peer constructor.\n * This removes DOM coupling (no script injection).\n *\n * Example:\n * ```js\n * import Peer from 'peerjs';\n * import { startP2PSend } from '@dropgate/core/p2p';\n *\n * const session = await startP2PSend({\n * file: myFile,\n * Peer,\n * host: 'dropgate.link',\n * secure: true,\n * onCode: (code) => console.log('Share this code:', code),\n * onProgress: (evt) => console.log(`${evt.percent}% sent`),\n * onComplete: () => console.log('Done!'),\n * });\n * ```\n */\nexport async function startP2PSend(opts: P2PSendOptions): Promise<P2PSendSession> {\n const {\n file,\n Peer,\n serverInfo,\n host,\n port,\n peerjsPath,\n secure = false,\n iceServers,\n codeGenerator,\n cryptoObj,\n maxAttempts = 4,\n chunkSize = 256 * 1024,\n readyTimeoutMs = 8000,\n endAckTimeoutMs = 15000,\n bufferHighWaterMark = 8 * 1024 * 1024,\n bufferLowWaterMark = 2 * 1024 * 1024,\n onCode,\n onStatus,\n onProgress,\n onComplete,\n onError,\n } = opts;\n\n // Validate required options\n if (!file) {\n throw new DropgateValidationError('File is missing.');\n }\n\n if (!Peer) {\n throw new DropgateValidationError(\n 'PeerJS Peer constructor is required. Install peerjs and pass it as the Peer option.'\n );\n }\n\n // Check P2P capabilities if serverInfo is provided\n const p2pCaps = serverInfo?.capabilities?.p2p;\n if (serverInfo && !p2pCaps?.enabled) {\n throw new DropgateValidationError('Direct transfer is disabled on this server.');\n }\n\n // Determine options (use serverInfo if available)\n const finalPath = peerjsPath ?? p2pCaps?.peerjsPath ?? '/peerjs';\n const finalIceServers = iceServers ?? p2pCaps?.iceServers ?? [];\n\n // Build peer options\n const peerOpts = buildPeerOptions({\n host,\n port,\n peerjsPath: finalPath,\n secure,\n iceServers: finalIceServers,\n });\n\n // Create the code generator\n const finalCodeGenerator = codeGenerator || (() => generateP2PCode(cryptoObj));\n\n // Create peer with retries\n const buildPeer = (id: string) => new Peer(id, peerOpts);\n const { peer, code } = await createPeerWithRetries({\n code: null,\n codeGenerator: finalCodeGenerator,\n maxAttempts,\n buildPeer,\n onCode,\n });\n\n let stopped = false;\n let activeConn: DataConnection | null = null;\n let transferActive = false;\n let transferCompleted = false;\n\n const reportProgress = (data: { received: number; total: number }): void => {\n const safeTotal =\n Number.isFinite(data.total) && data.total > 0 ? data.total : file.size;\n const safeReceived = Math.min(Number(data.received) || 0, safeTotal || 0);\n const percent = safeTotal ? (safeReceived / safeTotal) * 100 : 0;\n onProgress?.({ sent: safeReceived, total: safeTotal, percent });\n };\n\n const stop = (): void => {\n stopped = true;\n try {\n activeConn?.close();\n } catch {\n // Ignore close errors\n }\n try {\n peer.destroy();\n } catch {\n // Ignore destroy errors\n }\n };\n\n peer.on('connection', (conn: DataConnection) => {\n if (stopped) return;\n\n if (activeConn) {\n try {\n conn.send({ t: 'error', message: 'Another receiver is already connected.' });\n } catch {\n // Ignore send errors\n }\n try {\n conn.close();\n } catch {\n // Ignore close errors\n }\n return;\n }\n\n activeConn = conn;\n onStatus?.({ phase: 'connected', message: 'Connected. Starting transfer...' });\n\n let readyResolve: (() => void) | null = null;\n let ackResolve: ((data: unknown) => void) | null = null;\n\n const readyPromise = new Promise<void>((resolve) => {\n readyResolve = resolve;\n });\n\n const ackPromise = new Promise<unknown>((resolve) => {\n ackResolve = resolve;\n });\n\n conn.on('data', (data: unknown) => {\n if (\n !data ||\n typeof data !== 'object' ||\n data instanceof ArrayBuffer ||\n ArrayBuffer.isView(data)\n ) {\n return;\n }\n\n const msg = data as { t?: string; received?: number; total?: number; phase?: string; message?: string };\n if (!msg.t) return;\n\n if (msg.t === 'ready') {\n readyResolve?.();\n return;\n }\n\n if (msg.t === 'progress') {\n reportProgress({ received: msg.received || 0, total: msg.total || 0 });\n return;\n }\n\n if (msg.t === 'ack' && msg.phase === 'end') {\n ackResolve?.(msg);\n return;\n }\n\n if (msg.t === 'error') {\n onError?.(new DropgateNetworkError(msg.message || 'Receiver reported an error.'));\n stop();\n }\n });\n\n conn.on('open', async () => {\n try {\n transferActive = true;\n if (stopped) return;\n\n conn.send({\n t: 'meta',\n name: file.name,\n size: file.size,\n mime: file.type || 'application/octet-stream',\n });\n\n let sent = 0;\n const total = file.size;\n const dc = conn._dc;\n\n if (dc && Number.isFinite(bufferLowWaterMark)) {\n try {\n dc.bufferedAmountLowThreshold = bufferLowWaterMark;\n } catch {\n // Ignore threshold setting errors\n }\n }\n\n // Wait for ready signal\n await Promise.race([readyPromise, sleep(readyTimeoutMs).catch(() => null)]);\n\n // Send file in chunks\n for (let offset = 0; offset < total; offset += chunkSize) {\n if (stopped) return;\n\n const slice = file.slice(offset, offset + chunkSize);\n const buf = await slice.arrayBuffer();\n conn.send(buf);\n sent += buf.byteLength;\n\n // Flow control\n if (dc) {\n while (dc.bufferedAmount > bufferHighWaterMark) {\n await new Promise<void>((resolve) => {\n const fallback = setTimeout(resolve, 60);\n try {\n dc.addEventListener(\n 'bufferedamountlow',\n () => {\n clearTimeout(fallback);\n resolve();\n },\n { once: true }\n );\n } catch {\n // Fallback only\n }\n });\n }\n }\n }\n\n if (stopped) return;\n conn.send({ t: 'end' });\n\n // Wait for acknowledgment\n const ackTimeoutMs = Number.isFinite(endAckTimeoutMs)\n ? Math.max(endAckTimeoutMs, Math.ceil(file.size / (1024 * 1024)) * 1000)\n : null;\n\n const ackResult = await Promise.race([\n ackPromise,\n sleep(ackTimeoutMs || 15000).catch(() => null),\n ]);\n\n if (!ackResult || typeof ackResult !== 'object') {\n throw new DropgateNetworkError('Receiver did not confirm completion.');\n }\n\n const ackData = ackResult as { total?: number; received?: number };\n const ackTotal = Number(ackData.total) || file.size;\n const ackReceived = Number(ackData.received) || 0;\n\n if (ackTotal && ackReceived < ackTotal) {\n throw new DropgateNetworkError('Receiver reported an incomplete transfer.');\n }\n\n reportProgress({ received: ackReceived || ackTotal, total: ackTotal });\n transferCompleted = true;\n transferActive = false;\n onComplete?.();\n stop();\n } catch (err) {\n onError?.(err as Error);\n stop();\n }\n });\n\n conn.on('error', (err: Error) => {\n onError?.(err);\n stop();\n });\n\n conn.on('close', () => {\n if (!transferCompleted && transferActive && !stopped) {\n onError?.(\n new DropgateNetworkError('Receiver disconnected before transfer completed.')\n );\n }\n stop();\n });\n });\n\n return { peer, code, stop };\n}\n","import { DropgateValidationError, DropgateNetworkError } from '../errors.js';\nimport type { P2PReceiveOptions, P2PReceiveSession } from './types.js';\nimport { isP2PCodeLike } from './utils.js';\nimport { buildPeerOptions } from './helpers.js';\n\n/**\n * Start a direct transfer (P2P) receiver session.\n *\n * IMPORTANT: Consumer must provide the PeerJS Peer constructor and handle file writing.\n * This removes DOM coupling (no streamSaver).\n *\n * Example:\n * ```js\n * import Peer from 'peerjs';\n * import { startP2PReceive } from '@dropgate/core/p2p';\n *\n * let writer;\n * const session = await startP2PReceive({\n * code: 'ABCD-1234',\n * Peer,\n * host: 'dropgate.link',\n * secure: true,\n * onMeta: ({ name, total }) => {\n * // Consumer creates file writer\n * writer = createWriteStream(name);\n * },\n * onData: async (chunk) => {\n * // Consumer writes data\n * await writer.write(chunk);\n * },\n * onComplete: () => {\n * writer.close();\n * console.log('Done!');\n * },\n * });\n * ```\n */\nexport async function startP2PReceive(opts: P2PReceiveOptions): Promise<P2PReceiveSession> {\n const {\n code,\n Peer,\n serverInfo,\n host,\n port,\n peerjsPath,\n secure = false,\n iceServers,\n onStatus,\n onMeta,\n onData,\n onProgress,\n onComplete,\n onError,\n onDisconnect,\n } = opts;\n\n // Validate required options\n if (!code) {\n throw new DropgateValidationError('No sharing code was provided.');\n }\n\n if (!Peer) {\n throw new DropgateValidationError(\n 'PeerJS Peer constructor is required. Install peerjs and pass it as the Peer option.'\n );\n }\n\n // Check P2P capabilities if serverInfo is provided\n const p2pCaps = serverInfo?.capabilities?.p2p;\n if (serverInfo && !p2pCaps?.enabled) {\n throw new DropgateValidationError('Direct transfer is disabled on this server.');\n }\n\n // Validate and normalize code\n const normalizedCode = String(code).trim().replace(/\\s+/g, '').toUpperCase();\n if (!isP2PCodeLike(normalizedCode)) {\n throw new DropgateValidationError('Invalid direct transfer code.');\n }\n\n // Determine options (use serverInfo if available)\n const finalPath = peerjsPath ?? p2pCaps?.peerjsPath ?? '/peerjs';\n const finalIceServers = iceServers ?? p2pCaps?.iceServers ?? [];\n\n // Build peer options\n const peerOpts = buildPeerOptions({\n host,\n port,\n peerjsPath: finalPath,\n secure,\n iceServers: finalIceServers,\n });\n\n // Create peer (receiver doesn't need a specific ID)\n const peer = new Peer(undefined, peerOpts);\n\n let total = 0;\n let received = 0;\n let lastProgressSentAt = 0;\n const progressIntervalMs = 120;\n let writeQueue = Promise.resolve();\n\n const stop = (): void => {\n try {\n peer.destroy();\n } catch {\n // Ignore destroy errors\n }\n };\n\n peer.on('error', (err: Error) => {\n onError?.(err);\n stop();\n });\n\n peer.on('open', () => {\n const conn = peer.connect(normalizedCode, { reliable: true });\n\n conn.on('open', () => {\n onStatus?.({ phase: 'connected', message: 'Waiting for file details...' });\n });\n\n conn.on('data', async (data: unknown) => {\n try {\n // Handle control messages\n if (\n data &&\n typeof data === 'object' &&\n !(data instanceof ArrayBuffer) &&\n !ArrayBuffer.isView(data)\n ) {\n const msg = data as {\n t?: string;\n name?: string;\n size?: number;\n message?: string;\n };\n\n if (msg.t === 'meta') {\n const name = String(msg.name || 'file');\n total = Number(msg.size) || 0;\n received = 0;\n writeQueue = Promise.resolve();\n\n onMeta?.({ name, total });\n onProgress?.({ received, total, percent: 0 });\n\n try {\n conn.send({ t: 'ready' });\n } catch {\n // Ignore send errors\n }\n return;\n }\n\n if (msg.t === 'end') {\n await writeQueue;\n\n if (total && received < total) {\n const err = new DropgateNetworkError(\n 'Transfer ended before the full file was received.'\n );\n try {\n conn.send({ t: 'error', message: err.message });\n } catch {\n // Ignore send errors\n }\n throw err;\n }\n\n onComplete?.({ received, total });\n\n try {\n conn.send({ t: 'ack', phase: 'end', received, total });\n } catch {\n // Ignore send errors\n }\n return;\n }\n\n if (msg.t === 'error') {\n throw new DropgateNetworkError(msg.message || 'Sender reported an error.');\n }\n\n return;\n }\n\n // Handle binary data\n let bufPromise: Promise<Uint8Array>;\n\n if (data instanceof ArrayBuffer) {\n bufPromise = Promise.resolve(new Uint8Array(data));\n } else if (ArrayBuffer.isView(data)) {\n bufPromise = Promise.resolve(\n new Uint8Array(data.buffer, data.byteOffset, data.byteLength)\n );\n } else if (typeof Blob !== 'undefined' && data instanceof Blob) {\n bufPromise = data.arrayBuffer().then((buffer) => new Uint8Array(buffer));\n } else {\n return;\n }\n\n writeQueue = writeQueue\n .then(async () => {\n const buf = await bufPromise;\n\n // Call consumer's onData handler\n if (onData) {\n await onData(buf);\n }\n\n received += buf.byteLength;\n const percent = total ? Math.min(100, (received / total) * 100) : 0;\n onProgress?.({ received, total, percent });\n\n const now = Date.now();\n if (received === total || now - lastProgressSentAt >= progressIntervalMs) {\n lastProgressSentAt = now;\n try {\n conn.send({ t: 'progress', received, total });\n } catch {\n // Ignore send errors\n }\n }\n })\n .catch((err) => {\n try {\n conn.send({\n t: 'error',\n message: (err as Error)?.message || 'Receiver write failed.',\n });\n } catch {\n // Ignore send errors\n }\n onError?.(err as Error);\n stop();\n });\n } catch (err) {\n onError?.(err as Error);\n stop();\n }\n });\n\n conn.on('close', () => {\n if (received > 0 && total > 0 && received < total) {\n onDisconnect?.();\n }\n });\n });\n\n return { peer, stop };\n}\n"],"mappings":"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","names":["index_exports","__export","AES_GCM_IV_BYTES","AES_GCM_TAG_BYTES","DEFAULT_CHUNK_SIZE","DropgateAbortError","DropgateClient","DropgateError","DropgateNetworkError","DropgateProtocolError","DropgateTimeoutError","DropgateValidationError","ENCRYPTION_OVERHEAD_PER_CHUNK","arrayBufferToBase64","base64ToBytes","buildBaseUrl","buildPeerOptions","bytesToBase64","createPeerWithRetries","decryptChunk","decryptFilenameFromBase64","encryptFilenameToBase64","encryptToBlob","estimateTotalUploadSizeBytes","exportKeyBase64","fetchJson","generateAesGcmKey","generateP2PCode","getDefaultBase64","getDefaultCrypto","getDefaultFetch","importKeyFromBase64","isLocalhostHostname","isP2PCodeLike","isSecureContextForP2P","lifetimeToMs","makeAbortSignal","parseSemverMajorMinor","parseServerUrl","sha256Hex","sleep","startP2PReceive","startP2PSend","validatePlainFilename","DEFAULT_CHUNK_SIZE","AES_GCM_IV_BYTES","AES_GCM_TAG_BYTES","ENCRYPTION_OVERHEAD_PER_CHUNK","DropgateError","message","opts","DropgateValidationError","DropgateNetworkError","DropgateProtocolError","DropgateAbortError","DropgateTimeoutError","getDefaultBase64","bytes","b64","binary","i","out","getDefaultCrypto","getDefaultFetch","defaultAdapter","getAdapter","adapter","getDefaultBase64","bytesToBase64","bytes","arrayBufferToBase64","buf","base64ToBytes","b64","MULTIPLIERS","lifetimeToMs","value","unit","u","v","m","parseSemverMajorMinor","version","parts","p","major","minor","validatePlainFilename","filename","DropgateValidationError","parseServerUrl","urlStr","normalized","url","buildBaseUrl","opts","host","port","secure","DropgateValidationError","protocol","portSuffix","sleep","ms","signal","resolve","reject","DropgateAbortError","t","makeAbortSignal","parentSignal","timeoutMs","controller","timeoutId","abort","reason","DropgateTimeoutError","fetchJson","fetchFn","rest","s","cleanup","res","text","json","importKeyFromBase64","cryptoObj","keyB64","base64","keyBytes","getDefaultBase64","keyBuffer","decryptChunk","encryptedData","key","iv","ciphertext","decryptFilenameFromBase64","encryptedFilenameB64","encryptedBytes","decryptedBuffer","sha256Hex","cryptoObj","data","hashBuffer","arr","hex","i","generateAesGcmKey","exportKeyBase64","key","raw","arrayBufferToBase64","encryptToBlob","cryptoObj","dataBuffer","key","iv","encrypted","encryptFilenameToBase64","filename","bytes","buf","arrayBufferToBase64","estimateTotalUploadSizeBytes","fileSizeBytes","totalChunks","isEncrypted","base","DropgateClient","opts","DropgateValidationError","fetchFn","getDefaultFetch","cryptoObj","getDefaultCrypto","getDefaultBase64","host","port","secure","timeoutMs","signal","baseUrl","buildBaseUrl","res","json","fetchJson","DropgateProtocolError","err","DropgateError","DropgateNetworkError","value","msg","serverInfo","serverVersion","clientVersion","c","parseSemverMajorMinor","file","lifetimeMs","encrypt","caps","fileSize","maxMB","limitBytes","maxHours","lt","limitMs","filenameOverride","onProgress","timeouts","retry","progress","evt","compat","filename","validatePlainFilename","cryptoKey","keyB64","transmittedFilename","generateAesGcmKey","exportKeyBase64","encryptFilenameToBase64","totalUploadSize","initPayload","initRes","uploadId","retries","baseBackoffMs","maxBackoffMs","i","DropgateAbortError","start","end","chunkBlob","percentComplete","chunkBuffer","uploadBlob","encryptToBlob","toHash","hashHex","sha256Hex","headers","chunkUrl","completeRes","fileId","downloadUrl","onData","metaSignal","metaCleanup","makeAbortSignal","metadata","metaRes","totalBytes","sizeMB","limitMB","importKeyFromBase64","decryptFilenameFromBase64","downloadSignal","downloadCleanup","receivedBytes","dataChunks","collectData","downloadRes","reader","ENCRYPTED_CHUNK_SIZE","pendingChunks","pendingLength","flushPending","result","offset","chunk","done","buffer","encryptedChunk","remainder","decryptedBuffer","decryptChunk","decryptedData","percent","data","totalLength","sum","url","fetchOptions","backoffMs","chunkIndex","attemptsLeft","currentBackoff","maxRetries","s","cleanup","text","attemptNumber","remaining","tick","secondsLeft","sleep","isLocalhostHostname","hostname","host","isSecureContextForP2P","isSecureContext","generateP2PCode","cryptoObj","crypto","getDefaultCrypto","letters","randomBytes","letterPart","numberPart","a","i","b","isP2PCodeLike","code","buildPeerOptions","opts","host","port","peerjsPath","secure","iceServers","peerOpts","createPeerWithRetries","code","codeGenerator","maxAttempts","buildPeer","onCode","nextCode","peer","lastError","attempt","resolve","reject","instance","err","DropgateNetworkError","startP2PSend","opts","file","Peer","serverInfo","host","port","peerjsPath","secure","iceServers","codeGenerator","cryptoObj","maxAttempts","chunkSize","readyTimeoutMs","endAckTimeoutMs","bufferHighWaterMark","bufferLowWaterMark","onCode","onStatus","onProgress","onComplete","onError","DropgateValidationError","p2pCaps","finalPath","finalIceServers","peerOpts","buildPeerOptions","finalCodeGenerator","generateP2PCode","buildPeer","id","peer","code","createPeerWithRetries","stopped","activeConn","transferActive","transferCompleted","reportProgress","data","safeTotal","safeReceived","percent","stop","conn","readyResolve","ackResolve","readyPromise","resolve","ackPromise","msg","DropgateNetworkError","sent","total","dc","sleep","offset","buf","fallback","ackTimeoutMs","ackResult","ackData","ackTotal","ackReceived","err","startP2PReceive","opts","code","Peer","serverInfo","host","port","peerjsPath","secure","iceServers","onStatus","onMeta","onData","onProgress","onComplete","onError","onDisconnect","DropgateValidationError","p2pCaps","normalizedCode","isP2PCodeLike","finalPath","finalIceServers","peerOpts","buildPeerOptions","peer","total","received","lastProgressSentAt","progressIntervalMs","writeQueue","stop","err","conn","data","msg","name","DropgateNetworkError","bufPromise","buffer","buf","percent","now"]}