@time-file/browser-file-crypto 1.1.0 → 1.1.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.ko.md CHANGED
@@ -163,6 +163,26 @@ const decrypted = await decryptFile(encrypted, {
163
163
  });
164
164
  ```
165
165
 
166
+ ### 하이브리드 암호화 (v1.1.1+)
167
+
168
+ 파일 크기에 따라 자동으로 일반/스트리밍 암호화를 전환합니다:
169
+
170
+ ```typescript
171
+ import { encryptFileAuto } from '@time-file/browser-file-crypto';
172
+
173
+ // 100MB 이상 파일은 자동으로 스트리밍 사용
174
+ const encrypted = await encryptFileAuto(file, {
175
+ password: 'secret',
176
+ autoStreaming: true,
177
+ streamingThreshold: 100 * 1024 * 1024, // 100MB (기본값)
178
+ chunkSize: 1024 * 1024, // 스트리밍 시 1MB 청크
179
+ onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)
180
+ });
181
+
182
+ // decryptFile이 자동으로 두 포맷 모두 처리합니다!
183
+ const decrypted = await decryptFile(encrypted, { password: 'secret' });
184
+ ```
185
+
166
186
  ### 스트리밍 암호화 (v1.1.0+)
167
187
 
168
188
  메모리에 맞지 않는 대용량 파일을 처리할 때:
@@ -183,7 +203,10 @@ const encryptedStream = await encryptFileStream(largeFile, {
183
203
  const response = new Response(encryptedStream);
184
204
  const encryptedBlob = await response.blob();
185
205
 
186
- // 복호화
206
+ // 복호화 - decryptFile이 스트리밍 포맷을 자동 감지합니다!
207
+ const decrypted = await decryptFile(encryptedBlob, { password: 'secret' });
208
+
209
+ // 또는 스트리밍 복호화를 직접 사용
187
210
  const decryptedStream = decryptFileStream(encryptedBlob, { password: 'secret' });
188
211
  const decryptResponse = new Response(decryptedStream);
189
212
  const decryptedBlob = await decryptResponse.blob();
@@ -225,13 +248,21 @@ generateRandomPassword(24); // 'Kx9#mP2$vL5@nQ8!...'
225
248
  ### 다운로드 & 복호화
226
249
 
227
250
  ```typescript
228
- import { downloadAndDecrypt } from '@time-file/browser-file-crypto';
251
+ import { downloadAndDecrypt, downloadAndDecryptStream } from '@time-file/browser-file-crypto';
229
252
 
253
+ // 일반 다운로드 & 복호화
230
254
  await downloadAndDecrypt('https://example.com/secret.enc', {
231
255
  password: 'secret',
232
256
  fileName: 'document.pdf',
233
257
  onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)
234
258
  });
259
+
260
+ // 스트리밍 다운로드 & 복호화 (v1.1.1+) - 대용량 파일용
261
+ await downloadAndDecryptStream('https://example.com/large-file.enc', {
262
+ password: 'secret',
263
+ fileName: 'video.mp4',
264
+ onProgress: ({ phase, processedBytes }) => console.log(`${phase}: ${processedBytes} 바이트`)
265
+ });
235
266
  ```
236
267
 
237
268
  ### 에러 처리
@@ -318,7 +349,10 @@ import type {
318
349
  // 스트리밍 타입 (v1.1.0+)
319
350
  StreamEncryptOptions,
320
351
  StreamDecryptOptions,
321
- StreamProgress
352
+ StreamProgress,
353
+ // v1.1.1+
354
+ AutoEncryptOptions,
355
+ DownloadDecryptStreamOptions
322
356
  } from '@time-file/browser-file-crypto';
323
357
  ```
324
358
 
package/README.md CHANGED
@@ -164,6 +164,26 @@ const decrypted = await decryptFile(encrypted, {
164
164
  });
165
165
  ```
166
166
 
167
+ ### Hybrid Encryption (v1.1.1+)
168
+
169
+ Auto-switch between standard and streaming encryption based on file size:
170
+
171
+ ```typescript
172
+ import { encryptFileAuto } from '@time-file/browser-file-crypto';
173
+
174
+ // Automatically uses streaming for files > 100MB
175
+ const encrypted = await encryptFileAuto(file, {
176
+ password: 'secret',
177
+ autoStreaming: true,
178
+ streamingThreshold: 100 * 1024 * 1024, // 100MB (default)
179
+ chunkSize: 1024 * 1024, // 1MB chunks for streaming
180
+ onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)
181
+ });
182
+
183
+ // decryptFile automatically handles both formats!
184
+ const decrypted = await decryptFile(encrypted, { password: 'secret' });
185
+ ```
186
+
167
187
  ### Streaming Encryption (v1.1.0+)
168
188
 
169
189
  For large files that don't fit in memory:
@@ -184,7 +204,10 @@ const encryptedStream = await encryptFileStream(largeFile, {
184
204
  const response = new Response(encryptedStream);
185
205
  const encryptedBlob = await response.blob();
186
206
 
187
- // Decrypt
207
+ // Decrypt - decryptFile auto-detects streaming format!
208
+ const decrypted = await decryptFile(encryptedBlob, { password: 'secret' });
209
+
210
+ // Or use streaming decryption directly
188
211
  const decryptedStream = decryptFileStream(encryptedBlob, { password: 'secret' });
189
212
  const decryptResponse = new Response(decryptedStream);
190
213
  const decryptedBlob = await decryptResponse.blob();
@@ -226,13 +249,21 @@ generateRandomPassword(24); // 'Kx9#mP2$vL5@nQ8!...'
226
249
  ### Download & Decrypt
227
250
 
228
251
  ```typescript
229
- import { downloadAndDecrypt } from '@time-file/browser-file-crypto';
252
+ import { downloadAndDecrypt, downloadAndDecryptStream } from '@time-file/browser-file-crypto';
230
253
 
254
+ // Standard download & decrypt
231
255
  await downloadAndDecrypt('https://example.com/secret.enc', {
232
256
  password: 'secret',
233
257
  fileName: 'document.pdf',
234
258
  onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)
235
259
  });
260
+
261
+ // Streaming download & decrypt (v1.1.1+) - for large files
262
+ await downloadAndDecryptStream('https://example.com/large-file.enc', {
263
+ password: 'secret',
264
+ fileName: 'video.mp4',
265
+ onProgress: ({ phase, processedBytes }) => console.log(`${phase}: ${processedBytes} bytes`)
266
+ });
236
267
  ```
237
268
 
238
269
  ### Error Handling
@@ -319,7 +350,10 @@ import type {
319
350
  // Streaming types (v1.1.0+)
320
351
  StreamEncryptOptions,
321
352
  StreamDecryptOptions,
322
- StreamProgress
353
+ StreamProgress,
354
+ // v1.1.1+
355
+ AutoEncryptOptions,
356
+ DownloadDecryptStreamOptions
323
357
  } from '@time-file/browser-file-crypto';
324
358
  ```
325
359
 
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=16,f=12,U=256,H=1e5,B=32,D=1,m=2,S=17,T=18,V=64*1024,N=1,F=16,C=1+h+f+F,K=1+f+F,A="AES-GCM",W="SHA-256",$={INVALID_INPUT:"Input must be a File, Blob, or ArrayBuffer.",PASSWORD_REQUIRED:"Password or keyfile is required for encryption.",KEYFILE_REQUIRED:"Keyfile is required to decrypt this file.",INVALID_PASSWORD:"Decryption failed: incorrect password.",INVALID_KEYFILE:"Decryption failed: incorrect keyfile.",INVALID_ENCRYPTED_DATA:"Invalid encrypted data: file may be corrupted.",ENCRYPTION_FAILED:"Encryption failed.",DECRYPTION_FAILED:"Decryption failed.",DOWNLOAD_FAILED:"File download failed.",UNSUPPORTED_FORMAT:"Unsupported encryption format."};class o extends Error{constructor(a,e){super(e??$[a]),this.name="CryptoError",this.code=a,Error.captureStackTrace&&Error.captureStackTrace(this,o)}}function J(n){return n instanceof o}async function L(n){if(n instanceof ArrayBuffer)return n;if(n instanceof Blob)return n.arrayBuffer();throw new o("INVALID_INPUT")}function E(n){return n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength)}function X(n){const a=new Uint8Array(n);let e="";for(let t=0;t<a.byteLength;t++)e+=String.fromCharCode(a[t]);return btoa(e)}function G(n){const a=atob(n),e=new Uint8Array(a.length);for(let t=0;t<a.length;t++)e[t]=a.charCodeAt(t);return e.buffer}async function b(n,a){const e=new TextEncoder,t=await crypto.subtle.importKey("raw",e.encode(n),"PBKDF2",!1,["deriveKey"]);return crypto.subtle.deriveKey({name:"PBKDF2",salt:E(a),iterations:H,hash:W},t,{name:A,length:U},!1,["encrypt","decrypt"])}async function k(n){try{const a=G(n);return await crypto.subtle.importKey("raw",a,{name:A,length:U},!1,["encrypt","decrypt"])}catch{throw new o("INVALID_KEYFILE")}}function O(n){return crypto.getRandomValues(new Uint8Array(n))}function x(){return O(h)}function M(){return O(f)}const Y="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*",P=16;function ee(n=P){const a=Math.max(8,Math.min(128,n)),e=O(a);let t="";for(let r=0;r<a;r++){const c=e[r]%Y.length;t+=Y[c]}return t}async function te(n,a){const{password:e,keyData:t,onProgress:r}=a;if(!e&&!t)throw new o("PASSWORD_REQUIRED");try{r==null||r({phase:"deriving_key",progress:0});const c=await L(n);return r==null||r({phase:"deriving_key",progress:10}),t?await re(c,t,r):await ne(c,e,r)}catch(c){throw c instanceof o?c:new o("ENCRYPTION_FAILED")}}async function ne(n,a,e){const t=x(),r=M();e==null||e({phase:"deriving_key",progress:20});const c=await b(a,t);e==null||e({phase:"encrypting",progress:30});const i=await crypto.subtle.encrypt({name:A,iv:E(r)},c,n);e==null||e({phase:"encrypting",progress:90});const s=new Uint8Array(1+h+f+i.byteLength);return s[0]=D,s.set(t,1),s.set(r,1+h),s.set(new Uint8Array(i),1+h+f),e==null||e({phase:"complete",progress:100}),new Blob([s],{type:"application/octet-stream"})}async function re(n,a,e){const t=M();e==null||e({phase:"deriving_key",progress:20});const r=await k(a);e==null||e({phase:"encrypting",progress:30});const c=await crypto.subtle.encrypt({name:A,iv:E(t)},r,n);e==null||e({phase:"encrypting",progress:90});const i=new Uint8Array(1+f+c.byteLength);return i[0]=m,i.set(t,1),i.set(new Uint8Array(c),1+f),e==null||e({phase:"complete",progress:100}),new Blob([i],{type:"application/octet-stream"})}async function z(n,a){const{password:e,keyData:t,onProgress:r}=a;try{r==null||r({phase:"decrypting",progress:0});const c=new Uint8Array(await L(n));if(r==null||r({phase:"decrypting",progress:5}),c.length<1)throw new o("INVALID_ENCRYPTED_DATA");const i=c[0];if(i===D){if(!e)throw new o("PASSWORD_REQUIRED");return await ae(c,e,r)}else if(i===m){if(!t)throw new o("KEYFILE_REQUIRED");return await ce(c,t,r)}else throw new o("UNSUPPORTED_FORMAT")}catch(c){throw c instanceof o?c:new o("DECRYPTION_FAILED")}}async function ae(n,a,e){if(n.length<C)throw new o("INVALID_ENCRYPTED_DATA");e==null||e({phase:"deriving_key",progress:10});const t=n.slice(1,1+h),r=n.slice(1+h,1+h+f),c=n.slice(1+h+f),i=await b(a,t);e==null||e({phase:"decrypting",progress:30});try{const s=await crypto.subtle.decrypt({name:A,iv:E(r)},i,c);return e==null||e({phase:"complete",progress:100}),new Blob([s])}catch{throw new o("INVALID_PASSWORD")}}async function ce(n,a,e){if(n.length<K)throw new o("INVALID_ENCRYPTED_DATA");e==null||e({phase:"decrypting",progress:10});const t=n.slice(1,1+f),r=n.slice(1+f),c=await k(a);e==null||e({phase:"decrypting",progress:30});try{const i=await crypto.subtle.decrypt({name:A,iv:E(t)},c,r);return e==null||e({phase:"complete",progress:100}),new Blob([i])}catch{throw new o("INVALID_KEYFILE")}}function Q(n,a){const e=new Uint8Array(12);e.set(n);const t=new DataView(e.buffer),r=t.getUint32(8,!0);return t.setUint32(8,r^a,!0),e}function j(n,a){const e=new Uint8Array(n.length+a.length);return e.set(n,0),e.set(a,n.length),e}async function g(n,a,e,t){const r=Q(e,t),c=await crypto.subtle.encrypt({name:A,iv:E(r)},a,E(n)),i=new Uint8Array(4+c.byteLength);return new DataView(i.buffer).setUint32(0,c.byteLength,!0),i.set(new Uint8Array(c),4),i}async function ie(n,a,e,t){const r=Q(e,t);try{const c=await crypto.subtle.decrypt({name:A,iv:E(r)},a,E(n));return new Uint8Array(c)}catch{throw new o("DECRYPTION_FAILED")}}function se(n,a,e,t){if(n&&e){const r=new Uint8Array(34);return r[0]=S,r[1]=N,new DataView(r.buffer).setUint32(2,a,!0),r.set(e,6),r.set(t,22),r}else{const r=new Uint8Array(18);return r[0]=T,r[1]=N,new DataView(r.buffer).setUint32(2,a,!0),r.set(t,6),r}}function oe(n){const a=n[0],e=a===S;if(a!==S&&a!==T)throw new o("UNSUPPORTED_FORMAT");const t=n[1];if(t!==N)throw new o("UNSUPPORTED_FORMAT");const r=new DataView(n.buffer,n.byteOffset).getUint32(2,!0);if(e){const c=n.slice(6,22),i=n.slice(22,34);return{isPassword:e,version:t,chunkSize:r,salt:c,baseIV:i,headerSize:34}}else{const c=n.slice(6,18);return{isPassword:e,version:t,chunkSize:r,salt:null,baseIV:c,headerSize:18}}}async function q(n){const{password:a,keyData:e,chunkSize:t=V,onProgress:r}=n;if(!a&&!e)throw new o("PASSWORD_REQUIRED");const c=!!a,i=c?x():null,s=M();r==null||r({phase:"deriving_key",processedBytes:0,processedChunks:0});const p=c?await b(a,i):await k(e),y=se(c,t,i,s);let l=new Uint8Array(0),u=0,R=0;return{stream:new TransformStream({async transform(I,d){for(l=j(l,I);l.length>=t;){const _=l.slice(0,t);l=l.slice(t);const w=await g(_,p,s,u);d.enqueue(w),u++,R+=_.length,r==null||r({phase:"processing",processedBytes:R,processedChunks:u})}},async flush(I){if(l.length>0){const d=await g(l,p,s,u);I.enqueue(d),R+=l.length,u++}r==null||r({phase:"complete",processedBytes:R,processedChunks:u,progress:100})}}),header:y}}function Z(n){const{password:a,keyData:e,onProgress:t}=n;let r=new Uint8Array(0),c=!1,i=null,s=null,p=0,y=0,l=!1,u=0;return new TransformStream({async transform(R,v){if(r=j(r,R),!c){if(r.length<2)return;const d=r[0]===S?34:18;if(r.length<d)return;const _=r.slice(0,d),w=oe(_);if(l=w.isPassword,s=w.baseIV,u=w.headerSize,l&&!a)throw new o("PASSWORD_REQUIRED");if(!l&&!e)throw new o("KEYFILE_REQUIRED");t==null||t({phase:"deriving_key",processedBytes:0,processedChunks:0}),i=l?await b(a,w.salt):await k(e),r=r.slice(u),c=!0}for(;r.length>=4;){const d=4+new DataView(r.buffer,r.byteOffset).getUint32(0,!0);if(r.length<d)break;const _=r.slice(4,d);r=r.slice(d);const w=await ie(_,i,s,p);v.enqueue(w),y+=w.length,p++,t==null||t({phase:"processing",processedBytes:y,processedChunks:p})}},async flush(){if(r.length>0)throw new o("INVALID_ENCRYPTED_DATA");t==null||t({phase:"complete",processedBytes:y,processedChunks:p,progress:100})}})}async function le(n,a){const e=n.size,t=a.onProgress?l=>{a.onProgress({...l,totalBytes:e,progress:l.phase==="complete"?100:Math.round(l.processedBytes/e*100)})}:void 0,{stream:r,header:c}=await q({...a,onProgress:t}),s=n.stream().pipeThrough(r);let p=!1;const y=s.getReader();return new ReadableStream({async pull(l){if(!p){l.enqueue(c),p=!0;return}try{const{done:u,value:R}=await y.read();u?l.close():l.enqueue(R)}catch(u){l.error(u)}},cancel(){y.releaseLock()}})}function ye(n,a){const e=Z(a);return n instanceof ReadableStream?n.pipeThrough(e):n.stream().pipeThrough(e)}async function pe(n){const a=await L(n),e=new Uint8Array(a);if(e.length<1)return"unknown";const t=e[0];return t===D?"password":t===m?"keyfile":t===S?"password-stream":t===T?"keyfile-stream":"unknown"}const ue=34,fe=18;async function de(n){const a=await L(n),e=new Uint8Array(a);if(e.length<1)return!1;const t=e[0];return t===D&&e.length>=C||t===m&&e.length>=K||t===S&&e.length>=ue||t===T&&e.length>=fe}function he(n){return n==="password-stream"||n==="keyfile-stream"}function we(){const n=O(B);return{version:1,algorithm:"AES-256-GCM",key:X(n.buffer),createdAt:new Date().toISOString()}}function Ee(n){try{const a=JSON.parse(n);return Ae(a)?a:null}catch{return null}}function Ae(n){if(typeof n!="object"||n===null)return!1;const a=n;return a.version===1&&a.algorithm==="AES-256-GCM"&&typeof a.key=="string"&&a.key.length>0&&typeof a.createdAt=="string"}function Re(n,a,e="key"){const t={version:1,algorithm:"AES-256-GCM",key:n,createdAt:new Date().toISOString()},r=JSON.stringify(t,null,2),c=new Blob([r],{type:"application/json"}),i=URL.createObjectURL(c),s=document.createElement("a");s.href=i,s.download=`${a}.${e}`,s.style.display="none",document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(i)}async function Se(n){const a=G(n),e=await crypto.subtle.digest("SHA-256",a),t=new Uint8Array(e);return Array.from(t).map(r=>r.toString(16).padStart(2,"0")).join("")}async function _e(n,a){const{fileName:e,onProgress:t,...r}=a;try{t==null||t({phase:"downloading",progress:0});const c=await fetch(n);if(!c.ok)throw new o("DOWNLOAD_FAILED",`Download failed: ${c.status} ${c.statusText}`);const i=c.headers.get("content-length");let s;i&&c.body?s=await Ie(c.body,parseInt(i,10),y=>{t==null||t({phase:"downloading",progress:Math.round(y*50)})}):(t==null||t({phase:"downloading",progress:25}),s=await c.arrayBuffer()),t==null||t({phase:"downloading",progress:50});const p=await z(s,{...r,onProgress:y=>{const l=50+Math.round(y.progress*.45);t==null||t({phase:y.phase==="complete"?"complete":y.phase,progress:y.phase==="complete"?100:l})}});De(p,e),t==null||t({phase:"complete",progress:100})}catch(c){throw c instanceof o?c:new o("DOWNLOAD_FAILED")}}async function Ie(n,a,e){const t=n.getReader(),r=[];let c=0,i=!1;for(;!i;){const y=await t.read();if(i=y.done,y.value){r.push(y.value),c+=y.value.length;const l=c/a;e(Math.min(l,1))}}const s=new Uint8Array(c);let p=0;for(const y of r)s.set(y,p),p+=y.length;return s.buffer}function De(n,a){const e=URL.createObjectURL(n),t=document.createElement("a");t.href=e,t.download=a,t.style.display="none",document.body.appendChild(t),t.click(),document.body.removeChild(t),URL.revokeObjectURL(e)}exports.ALGORITHM=A;exports.AUTH_TAG_LENGTH=F;exports.CryptoError=o;exports.DEFAULT_CHUNK_SIZE=V;exports.ENCRYPTION_MARKER_KEYFILE=m;exports.ENCRYPTION_MARKER_KEYFILE_STREAM=T;exports.ENCRYPTION_MARKER_PASSWORD=D;exports.ENCRYPTION_MARKER_PASSWORD_STREAM=S;exports.HASH_ALGORITHM=W;exports.IV_LENGTH=f;exports.KEYFILE_KEY_LENGTH=B;exports.KEY_LENGTH=U;exports.MIN_ENCRYPTED_SIZE_KEYFILE=K;exports.MIN_ENCRYPTED_SIZE_PASSWORD=C;exports.PBKDF2_ITERATIONS=H;exports.SALT_LENGTH=h;exports.STREAM_FORMAT_VERSION=N;exports.computeKeyFileHash=Se;exports.createDecryptStream=Z;exports.createEncryptStream=q;exports.decryptFile=z;exports.decryptFileStream=ye;exports.downloadAndDecrypt=_e;exports.downloadKeyFile=Re;exports.encryptFile=te;exports.encryptFileStream=le;exports.generateKeyFile=we;exports.generateRandomPassword=ee;exports.getEncryptionType=pe;exports.isCryptoError=J;exports.isEncryptedFile=de;exports.isStreamingEncryption=he;exports.parseKeyFile=Ee;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const E=16,f=12,U=256,V=1e5,W=32,_=1,T=2,D=17,I=18,C=64*1024,b=1,K=16,M=1+E+f+K,g=1+f+K,S="AES-GCM",G="SHA-256",ee={INVALID_INPUT:"Input must be a File, Blob, or ArrayBuffer.",PASSWORD_REQUIRED:"Password or keyfile is required for encryption.",KEYFILE_REQUIRED:"Keyfile is required to decrypt this file.",INVALID_PASSWORD:"Decryption failed: incorrect password.",INVALID_KEYFILE:"Decryption failed: incorrect keyfile.",INVALID_ENCRYPTED_DATA:"Invalid encrypted data: file may be corrupted.",ENCRYPTION_FAILED:"Encryption failed.",DECRYPTION_FAILED:"Decryption failed.",DOWNLOAD_FAILED:"File download failed.",UNSUPPORTED_FORMAT:"Unsupported encryption format."};class o extends Error{constructor(a,e){super(e??ee[a]),this.name="CryptoError",this.code=a,Error.captureStackTrace&&Error.captureStackTrace(this,o)}}function te(n){return n instanceof o}async function N(n){if(n instanceof ArrayBuffer)return n;if(n instanceof Blob)return n.arrayBuffer();throw new o("INVALID_INPUT")}function R(n){return n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength)}function ne(n){const a=new Uint8Array(n);let e="";for(let t=0;t<a.byteLength;t++)e+=String.fromCharCode(a[t]);return btoa(e)}function x(n){const a=atob(n),e=new Uint8Array(a.length);for(let t=0;t<a.length;t++)e[t]=a.charCodeAt(t);return e.buffer}async function O(n,a){const e=new TextEncoder,t=await crypto.subtle.importKey("raw",e.encode(n),"PBKDF2",!1,["deriveKey"]);return crypto.subtle.deriveKey({name:"PBKDF2",salt:R(a),iterations:V,hash:G},t,{name:S,length:U},!1,["encrypt","decrypt"])}async function k(n){try{const a=x(n);return await crypto.subtle.importKey("raw",a,{name:S,length:U},!1,["encrypt","decrypt"])}catch{throw new o("INVALID_KEYFILE")}}function F(n){return crypto.getRandomValues(new Uint8Array(n))}function z(){return F(E)}function v(){return F(f)}const Y="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*",re=16;function ae(n=re){const a=Math.max(8,Math.min(128,n)),e=F(a);let t="";for(let r=0;r<a;r++){const c=e[r]%Y.length;t+=Y[c]}return t}function Q(n,a){const e=new Uint8Array(12);e.set(n);const t=new DataView(e.buffer),r=t.getUint32(8,!0);return t.setUint32(8,r^a,!0),e}function j(n,a){const e=new Uint8Array(n.length+a.length);return e.set(n,0),e.set(a,n.length),e}async function H(n,a,e,t){const r=Q(e,t),c=await crypto.subtle.encrypt({name:S,iv:R(r)},a,R(n)),s=new Uint8Array(4+c.byteLength);return new DataView(s.buffer).setUint32(0,c.byteLength,!0),s.set(new Uint8Array(c),4),s}async function ce(n,a,e,t,r){const c=Q(e,t);try{const s=await crypto.subtle.decrypt({name:S,iv:R(c)},a,R(n));return new Uint8Array(s)}catch{throw t===0?new o(r?"INVALID_PASSWORD":"INVALID_KEYFILE"):new o("DECRYPTION_FAILED")}}function se(n,a,e,t){if(n&&e){const r=new Uint8Array(34);return r[0]=D,r[1]=b,new DataView(r.buffer).setUint32(2,a,!0),r.set(e,6),r.set(t,22),r}else{const r=new Uint8Array(18);return r[0]=I,r[1]=b,new DataView(r.buffer).setUint32(2,a,!0),r.set(t,6),r}}function ie(n){const a=n[0],e=a===D;if(a!==D&&a!==I)throw new o("UNSUPPORTED_FORMAT");const t=n[1];if(t!==b)throw new o("UNSUPPORTED_FORMAT");const r=new DataView(n.buffer,n.byteOffset).getUint32(2,!0);if(e){const c=n.slice(6,22),s=n.slice(22,34);return{isPassword:e,version:t,chunkSize:r,salt:c,baseIV:s,headerSize:34}}else{const c=n.slice(6,18);return{isPassword:e,version:t,chunkSize:r,salt:null,baseIV:c,headerSize:18}}}async function q(n){const{password:a,keyData:e,chunkSize:t=C,onProgress:r}=n;if(!a&&!e)throw new o("PASSWORD_REQUIRED");const c=!!a,s=c?z():null,i=v();r==null||r({phase:"deriving_key",processedBytes:0,processedChunks:0});const y=c?await O(a,s):await k(e),p=se(c,t,s,i);let l=new Uint8Array(0),u=0,d=0;return{stream:new TransformStream({async transform(h,w){for(l=j(l,h);l.length>=t;){const m=l.slice(0,t);l=l.slice(t);const A=await H(m,y,i,u);w.enqueue(A),u++,d+=m.length,r==null||r({phase:"encrypting",processedBytes:d,processedChunks:u})}},async flush(h){if(l.length>0){const w=await H(l,y,i,u);h.enqueue(w),d+=l.length,u++}r==null||r({phase:"complete",processedBytes:d,processedChunks:u,progress:100})}}),header:p}}function Z(n){const{password:a,keyData:e,onProgress:t}=n;let r=new Uint8Array(0),c=!1,s=null,i=null,y=0,p=0,l=!1,u=0;return new TransformStream({async transform(d,L){if(r=j(r,d),!c){if(r.length<2)return;const w=r[0]===D?34:18;if(r.length<w)return;const m=r.slice(0,w),A=ie(m);if(l=A.isPassword,i=A.baseIV,u=A.headerSize,l&&!a)throw new o("PASSWORD_REQUIRED");if(!l&&!e)throw new o("KEYFILE_REQUIRED");t==null||t({phase:"deriving_key",processedBytes:0,processedChunks:0}),s=l?await O(a,A.salt):await k(e),r=r.slice(u),c=!0}for(;r.length>=4;){const w=4+new DataView(r.buffer,r.byteOffset).getUint32(0,!0);if(r.length<w)break;const m=r.slice(4,w);r=r.slice(w);const A=await ce(m,s,i,y,l);L.enqueue(A),p+=A.length,y++,t==null||t({phase:"decrypting",processedBytes:p,processedChunks:y})}},async flush(){if(r.length>0)throw new o("INVALID_ENCRYPTED_DATA");t==null||t({phase:"complete",processedBytes:p,processedChunks:y,progress:100})}})}async function $(n,a){const e=n.size,t=a.onProgress?l=>{a.onProgress({...l,totalBytes:e,progress:l.phase==="complete"?100:Math.round(l.processedBytes/e*100)})}:void 0,{stream:r,header:c}=await q({...a,onProgress:t}),i=n.stream().pipeThrough(r);let y=!1;const p=i.getReader();return new ReadableStream({async pull(l){if(!y){l.enqueue(c),y=!0;return}try{const{done:u,value:d}=await p.read();u?l.close():l.enqueue(d)}catch(u){l.error(u)}},cancel(){p.releaseLock()}})}function B(n,a){const e=Z(a);return n instanceof ReadableStream?n.pipeThrough(e):n.stream().pipeThrough(e)}const oe=100*1024*1024;async function J(n,a){const{password:e,keyData:t,onProgress:r}=a;if(!e&&!t)throw new o("PASSWORD_REQUIRED");try{r==null||r({phase:"deriving_key",progress:0});const c=await N(n);return r==null||r({phase:"deriving_key",progress:10}),t?await pe(c,t,r):await le(c,e,r)}catch(c){throw c instanceof o?c:new o("ENCRYPTION_FAILED")}}async function le(n,a,e){const t=z(),r=v();e==null||e({phase:"deriving_key",progress:20});const c=await O(a,t);e==null||e({phase:"encrypting",progress:30});const s=await crypto.subtle.encrypt({name:S,iv:R(r)},c,n);e==null||e({phase:"encrypting",progress:90});const i=new Uint8Array(1+E+f+s.byteLength);return i[0]=_,i.set(t,1),i.set(r,1+E),i.set(new Uint8Array(s),1+E+f),e==null||e({phase:"complete",progress:100}),new Blob([i],{type:"application/octet-stream"})}async function pe(n,a,e){const t=v();e==null||e({phase:"deriving_key",progress:20});const r=await k(a);e==null||e({phase:"encrypting",progress:30});const c=await crypto.subtle.encrypt({name:S,iv:R(t)},r,n);e==null||e({phase:"encrypting",progress:90});const s=new Uint8Array(1+f+c.byteLength);return s[0]=T,s.set(t,1),s.set(new Uint8Array(c),1+f),e==null||e({phase:"complete",progress:100}),new Blob([s],{type:"application/octet-stream"})}async function ye(n,a){const{password:e,keyData:t,onProgress:r,autoStreaming:c=!1,streamingThreshold:s=oe,chunkSize:i=C}=a;if(!e&&!t)throw new o("PASSWORD_REQUIRED");if(c&&n.size>s){const l=await $(n,{password:e,keyData:t,chunkSize:i,onProgress:r?d=>{r({phase:d.phase,progress:d.progress??Math.round(d.processedBytes/n.size*100)})}:void 0});return await new Response(l).blob()}else return await J(n,{password:e,keyData:t,onProgress:r})}async function X(n,a){const{password:e,keyData:t,onProgress:r}=a;try{r==null||r({phase:"decrypting",progress:0});const c=new Uint8Array(await N(n));if(r==null||r({phase:"decrypting",progress:5}),c.length<1)throw new o("INVALID_ENCRYPTED_DATA");const s=c[0];if(s===_){if(!e)throw new o("PASSWORD_REQUIRED");return await ue(c,e,r)}else if(s===T){if(!t)throw new o("KEYFILE_REQUIRED");return await de(c,t,r)}else{if(s===D||s===I)return await fe(n,a);throw new o("UNSUPPORTED_FORMAT")}}catch(c){throw c instanceof o?c:new o("DECRYPTION_FAILED")}}async function ue(n,a,e){if(n.length<M)throw new o("INVALID_ENCRYPTED_DATA");e==null||e({phase:"deriving_key",progress:10});const t=n.slice(1,1+E),r=n.slice(1+E,1+E+f),c=n.slice(1+E+f),s=await O(a,t);e==null||e({phase:"decrypting",progress:30});try{const i=await crypto.subtle.decrypt({name:S,iv:R(r)},s,c);return e==null||e({phase:"complete",progress:100}),new Blob([i])}catch{throw new o("INVALID_PASSWORD")}}async function de(n,a,e){if(n.length<g)throw new o("INVALID_ENCRYPTED_DATA");e==null||e({phase:"decrypting",progress:10});const t=n.slice(1,1+f),r=n.slice(1+f),c=await k(a);e==null||e({phase:"decrypting",progress:30});try{const s=await crypto.subtle.decrypt({name:S,iv:R(t)},c,r);return e==null||e({phase:"complete",progress:100}),new Blob([s])}catch{throw new o("INVALID_KEYFILE")}}async function fe(n,a){const{password:e,keyData:t,onProgress:r}=a,c=n instanceof Blob?n:new Blob([n]),i=B(c,{password:e,keyData:t,onProgress:r?p=>{r({phase:p.phase,progress:p.progress??Math.round(p.processedBytes/(p.totalBytes||1)*100)})}:void 0});return await new Response(i).blob()}async function he(n){const a=await N(n),e=new Uint8Array(a);if(e.length<1)return"unknown";const t=e[0];return t===_?"password":t===T?"keyfile":t===D?"password-stream":t===I?"keyfile-stream":"unknown"}const we=34,Ee=18;async function Ae(n){const a=await N(n),e=new Uint8Array(a);if(e.length<1)return!1;const t=e[0];return t===_&&e.length>=M||t===T&&e.length>=g||t===D&&e.length>=we||t===I&&e.length>=Ee}function Re(n){return n==="password-stream"||n==="keyfile-stream"}function Se(){const n=F(W);return{version:1,algorithm:"AES-256-GCM",key:ne(n.buffer),createdAt:new Date().toISOString()}}function De(n){try{const a=JSON.parse(n);return me(a)?a:null}catch{return null}}function me(n){if(typeof n!="object"||n===null)return!1;const a=n;return a.version===1&&a.algorithm==="AES-256-GCM"&&typeof a.key=="string"&&a.key.length>0&&typeof a.createdAt=="string"}function Ie(n,a,e="key"){const t={version:1,algorithm:"AES-256-GCM",key:n,createdAt:new Date().toISOString()},r=JSON.stringify(t,null,2),c=new Blob([r],{type:"application/json"}),s=URL.createObjectURL(c),i=document.createElement("a");i.href=s,i.download=`${a}.${e}`,i.style.display="none",document.body.appendChild(i),i.click(),document.body.removeChild(i),URL.revokeObjectURL(s)}async function _e(n){const a=x(n),e=await crypto.subtle.digest("SHA-256",a),t=new Uint8Array(e);return Array.from(t).map(r=>r.toString(16).padStart(2,"0")).join("")}async function Te(n,a){const{fileName:e,onProgress:t,...r}=a;try{t==null||t({phase:"downloading",progress:0});const c=await fetch(n);if(!c.ok)throw new o("DOWNLOAD_FAILED",`Download failed: ${c.status} ${c.statusText}`);const s=c.headers.get("content-length");let i;s&&c.body?i=await Le(c.body,parseInt(s,10),p=>{t==null||t({phase:"downloading",progress:Math.round(p*50)})}):(t==null||t({phase:"downloading",progress:25}),i=await c.arrayBuffer()),t==null||t({phase:"downloading",progress:50});const y=await X(i,{...r,onProgress:p=>{const l=50+Math.round(p.progress*.45);t==null||t({phase:p.phase==="complete"?"complete":p.phase,progress:p.phase==="complete"?100:l})}});P(y,e),t==null||t({phase:"complete",progress:100})}catch(c){throw c instanceof o?c:new o("DOWNLOAD_FAILED")}}async function Le(n,a,e){const t=n.getReader(),r=[];let c=0,s=!1;for(;!s;){const p=await t.read();if(s=p.done,p.value){r.push(p.value),c+=p.value.length;const l=c/a;e(Math.min(l,1))}}const i=new Uint8Array(c);let y=0;for(const p of r)i.set(p,y),y+=p.length;return i.buffer}function P(n,a){const e=URL.createObjectURL(n),t=document.createElement("a");t.href=e,t.download=a,t.style.display="none",document.body.appendChild(t),t.click(),document.body.removeChild(t),URL.revokeObjectURL(e)}async function be(n,a){const{fileName:e,password:t,keyData:r,onProgress:c}=a;try{c==null||c({phase:"downloading",processedBytes:0,processedChunks:0,progress:0});const s=await fetch(n);if(!s.ok)throw new o("DOWNLOAD_FAILED",`Download failed: ${s.status} ${s.statusText}`);const i=s.headers.get("content-length"),y=i?parseInt(i,10):void 0;c==null||c({phase:"downloading",processedBytes:0,processedChunks:0,totalBytes:y,progress:25});const p=await s.blob();let l=0;const u=B(p,{password:t,keyData:r,onProgress:h=>{l=h.processedBytes,c==null||c({...h,progress:h.phase==="complete"?100:50+Math.round((h.progress??0)*.45)})}}),L=await new Response(u).blob();P(L,e),c==null||c({phase:"complete",processedBytes:l,processedChunks:0,progress:100})}catch(s){throw s instanceof o?s:new o("DOWNLOAD_FAILED")}}exports.ALGORITHM=S;exports.AUTH_TAG_LENGTH=K;exports.CryptoError=o;exports.DEFAULT_CHUNK_SIZE=C;exports.ENCRYPTION_MARKER_KEYFILE=T;exports.ENCRYPTION_MARKER_KEYFILE_STREAM=I;exports.ENCRYPTION_MARKER_PASSWORD=_;exports.ENCRYPTION_MARKER_PASSWORD_STREAM=D;exports.HASH_ALGORITHM=G;exports.IV_LENGTH=f;exports.KEYFILE_KEY_LENGTH=W;exports.KEY_LENGTH=U;exports.MIN_ENCRYPTED_SIZE_KEYFILE=g;exports.MIN_ENCRYPTED_SIZE_PASSWORD=M;exports.PBKDF2_ITERATIONS=V;exports.SALT_LENGTH=E;exports.STREAM_FORMAT_VERSION=b;exports.computeKeyFileHash=_e;exports.createDecryptStream=Z;exports.createEncryptStream=q;exports.decryptFile=X;exports.decryptFileStream=B;exports.downloadAndDecrypt=Te;exports.downloadAndDecryptStream=be;exports.downloadKeyFile=Ie;exports.encryptFile=J;exports.encryptFileAuto=ye;exports.encryptFileStream=$;exports.generateKeyFile=Se;exports.generateRandomPassword=ae;exports.getEncryptionType=he;exports.isCryptoError=te;exports.isEncryptedFile=Ae;exports.isStreamingEncryption=Re;exports.parseKeyFile=De;
2
2
  //# sourceMappingURL=index.cjs.map