@time-file/browser-file-crypto 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,334 @@
1
+ const H = 16, G = 12, C = 256, U = 1e5, M = 32, v = 1, V = 2, B = 16, W = 45, x = 29, l = "AES-GCM", N = "SHA-256", T = {
2
+ INVALID_INPUT: "Input must be a File, Blob, or ArrayBuffer.",
3
+ PASSWORD_REQUIRED: "Password or keyfile is required for encryption.",
4
+ KEYFILE_REQUIRED: "Keyfile is required to decrypt this file.",
5
+ INVALID_PASSWORD: "Decryption failed: incorrect password.",
6
+ INVALID_KEYFILE: "Decryption failed: incorrect keyfile.",
7
+ INVALID_ENCRYPTED_DATA: "Invalid encrypted data: file may be corrupted.",
8
+ ENCRYPTION_FAILED: "Encryption failed.",
9
+ DECRYPTION_FAILED: "Decryption failed.",
10
+ DOWNLOAD_FAILED: "File download failed.",
11
+ UNSUPPORTED_FORMAT: "Unsupported encryption format."
12
+ };
13
+ class p extends Error {
14
+ /**
15
+ * Creates a new CryptoError.
16
+ *
17
+ * @param code - Error code identifying the type of error
18
+ * @param message - Optional custom message (defaults to predefined message)
19
+ */
20
+ constructor(a, t) {
21
+ super(t ?? T[a]), this.name = "CryptoError", this.code = a, Error.captureStackTrace && Error.captureStackTrace(this, p);
22
+ }
23
+ }
24
+ function j(n) {
25
+ return n instanceof p;
26
+ }
27
+ async function s(n) {
28
+ if (n instanceof ArrayBuffer)
29
+ return n;
30
+ if (n instanceof Blob)
31
+ return n.arrayBuffer();
32
+ throw new p("INVALID_INPUT");
33
+ }
34
+ function f(n) {
35
+ return n.buffer.slice(n.byteOffset, n.byteOffset + n.byteLength);
36
+ }
37
+ function L(n) {
38
+ const a = new Uint8Array(n);
39
+ let t = "";
40
+ for (let e = 0; e < a.byteLength; e++)
41
+ t += String.fromCharCode(a[e]);
42
+ return btoa(t);
43
+ }
44
+ function _(n) {
45
+ const a = atob(n), t = new Uint8Array(a.length);
46
+ for (let e = 0; e < a.length; e++)
47
+ t[e] = a.charCodeAt(e);
48
+ return t.buffer;
49
+ }
50
+ async function A(n, a) {
51
+ const t = new TextEncoder(), e = await crypto.subtle.importKey(
52
+ "raw",
53
+ t.encode(n),
54
+ "PBKDF2",
55
+ !1,
56
+ ["deriveKey"]
57
+ );
58
+ return crypto.subtle.deriveKey(
59
+ {
60
+ name: "PBKDF2",
61
+ salt: f(a),
62
+ iterations: 1e5,
63
+ hash: N
64
+ },
65
+ e,
66
+ { name: l, length: 256 },
67
+ !1,
68
+ // non-extractable for security
69
+ ["encrypt", "decrypt"]
70
+ );
71
+ }
72
+ async function w(n) {
73
+ try {
74
+ const a = _(n);
75
+ return await crypto.subtle.importKey(
76
+ "raw",
77
+ a,
78
+ { name: l, length: 256 },
79
+ !1,
80
+ // non-extractable for security
81
+ ["encrypt", "decrypt"]
82
+ );
83
+ } catch {
84
+ throw new p("INVALID_KEYFILE");
85
+ }
86
+ }
87
+ function u(n) {
88
+ return crypto.getRandomValues(new Uint8Array(n));
89
+ }
90
+ function R() {
91
+ return u(16);
92
+ }
93
+ function I() {
94
+ return u(12);
95
+ }
96
+ const h = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*", D = 16;
97
+ function Z(n = D) {
98
+ const a = Math.max(8, Math.min(128, n)), t = u(a);
99
+ let e = "";
100
+ for (let i = 0; i < a; i++) {
101
+ const c = t[i] % h.length;
102
+ e += h[c];
103
+ }
104
+ return e;
105
+ }
106
+ async function Q(n, a) {
107
+ const { password: t, keyData: e, onProgress: i } = a;
108
+ if (!t && !e)
109
+ throw new p("PASSWORD_REQUIRED");
110
+ try {
111
+ i == null || i({ phase: "deriving_key", progress: 0 });
112
+ const c = await s(n);
113
+ return i == null || i({ phase: "deriving_key", progress: 10 }), e ? await m(c, e, i) : await S(c, t, i);
114
+ } catch (c) {
115
+ throw c instanceof p ? c : new p("ENCRYPTION_FAILED");
116
+ }
117
+ }
118
+ async function S(n, a, t) {
119
+ const e = R(), i = I();
120
+ t == null || t({ phase: "deriving_key", progress: 20 });
121
+ const c = await A(a, e);
122
+ t == null || t({ phase: "encrypting", progress: 30 });
123
+ const r = await crypto.subtle.encrypt(
124
+ { name: l, iv: f(i) },
125
+ c,
126
+ n
127
+ );
128
+ t == null || t({ phase: "encrypting", progress: 90 });
129
+ const y = new Uint8Array(
130
+ 29 + r.byteLength
131
+ );
132
+ return y[0] = 1, y.set(e, 1), y.set(i, 17), y.set(new Uint8Array(r), 29), t == null || t({ phase: "complete", progress: 100 }), new Blob([y], { type: "application/octet-stream" });
133
+ }
134
+ async function m(n, a, t) {
135
+ const e = I();
136
+ t == null || t({ phase: "deriving_key", progress: 20 });
137
+ const i = await w(a);
138
+ t == null || t({ phase: "encrypting", progress: 30 });
139
+ const c = await crypto.subtle.encrypt(
140
+ { name: l, iv: f(e) },
141
+ i,
142
+ n
143
+ );
144
+ t == null || t({ phase: "encrypting", progress: 90 });
145
+ const r = new Uint8Array(13 + c.byteLength);
146
+ return r[0] = 2, r.set(e, 1), r.set(new Uint8Array(c), 13), t == null || t({ phase: "complete", progress: 100 }), new Blob([r], { type: "application/octet-stream" });
147
+ }
148
+ async function O(n, a) {
149
+ const { password: t, keyData: e, onProgress: i } = a;
150
+ try {
151
+ i == null || i({ phase: "decrypting", progress: 0 });
152
+ const c = new Uint8Array(await s(n));
153
+ if (i == null || i({ phase: "decrypting", progress: 5 }), c.length < 1)
154
+ throw new p("INVALID_ENCRYPTED_DATA");
155
+ const r = c[0];
156
+ if (r === 1) {
157
+ if (!t)
158
+ throw new p("PASSWORD_REQUIRED");
159
+ return await b(c, t, i);
160
+ } else if (r === 2) {
161
+ if (!e)
162
+ throw new p("KEYFILE_REQUIRED");
163
+ return await K(c, e, i);
164
+ } else
165
+ throw new p("UNSUPPORTED_FORMAT");
166
+ } catch (c) {
167
+ throw c instanceof p ? c : new p("DECRYPTION_FAILED");
168
+ }
169
+ }
170
+ async function b(n, a, t) {
171
+ if (n.length < 45)
172
+ throw new p("INVALID_ENCRYPTED_DATA");
173
+ t == null || t({ phase: "deriving_key", progress: 10 });
174
+ const e = n.slice(1, 17), i = n.slice(17, 29), c = n.slice(29), r = await A(a, e);
175
+ t == null || t({ phase: "decrypting", progress: 30 });
176
+ try {
177
+ const y = await crypto.subtle.decrypt(
178
+ { name: l, iv: f(i) },
179
+ r,
180
+ c
181
+ );
182
+ return t == null || t({ phase: "complete", progress: 100 }), new Blob([y]);
183
+ } catch {
184
+ throw new p("INVALID_PASSWORD");
185
+ }
186
+ }
187
+ async function K(n, a, t) {
188
+ if (n.length < 29)
189
+ throw new p("INVALID_ENCRYPTED_DATA");
190
+ t == null || t({ phase: "decrypting", progress: 10 });
191
+ const e = n.slice(1, 13), i = n.slice(13), c = await w(a);
192
+ t == null || t({ phase: "decrypting", progress: 30 });
193
+ try {
194
+ const r = await crypto.subtle.decrypt(
195
+ { name: l, iv: f(e) },
196
+ c,
197
+ i
198
+ );
199
+ return t == null || t({ phase: "complete", progress: 100 }), new Blob([r]);
200
+ } catch {
201
+ throw new p("INVALID_KEYFILE");
202
+ }
203
+ }
204
+ async function g(n) {
205
+ const a = await s(n), t = new Uint8Array(a);
206
+ if (t.length < 1)
207
+ return "unknown";
208
+ const e = t[0];
209
+ return e === 1 ? "password" : e === 2 ? "keyfile" : "unknown";
210
+ }
211
+ async function $(n) {
212
+ const a = await s(n), t = new Uint8Array(a);
213
+ if (t.length < 1)
214
+ return !1;
215
+ const e = t[0];
216
+ return e === 1 && t.length >= 45 || e === 2 && t.length >= 29;
217
+ }
218
+ function q() {
219
+ const n = u(32);
220
+ return {
221
+ version: 1,
222
+ algorithm: "AES-256-GCM",
223
+ key: L(n.buffer),
224
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
225
+ };
226
+ }
227
+ function J(n) {
228
+ try {
229
+ const a = JSON.parse(n);
230
+ return Y(a) ? a : null;
231
+ } catch {
232
+ return null;
233
+ }
234
+ }
235
+ function Y(n) {
236
+ if (typeof n != "object" || n === null)
237
+ return !1;
238
+ const a = n;
239
+ return a.version === 1 && a.algorithm === "AES-256-GCM" && typeof a.key == "string" && a.key.length > 0 && typeof a.createdAt == "string";
240
+ }
241
+ function z(n, a, t = "key") {
242
+ const e = {
243
+ version: 1,
244
+ algorithm: "AES-256-GCM",
245
+ key: n,
246
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
247
+ }, i = JSON.stringify(e, null, 2), c = new Blob([i], { type: "application/json" }), r = URL.createObjectURL(c), y = document.createElement("a");
248
+ y.href = r, y.download = `${a}.${t}`, y.style.display = "none", document.body.appendChild(y), y.click(), document.body.removeChild(y), URL.revokeObjectURL(r);
249
+ }
250
+ async function X(n) {
251
+ const a = _(n), t = await crypto.subtle.digest("SHA-256", a), e = new Uint8Array(t);
252
+ return Array.from(e).map((i) => i.toString(16).padStart(2, "0")).join("");
253
+ }
254
+ async function P(n, a) {
255
+ const { fileName: t, onProgress: e, ...i } = a;
256
+ try {
257
+ e == null || e({ phase: "downloading", progress: 0 });
258
+ const c = await fetch(n);
259
+ if (!c.ok)
260
+ throw new p(
261
+ "DOWNLOAD_FAILED",
262
+ `Download failed: ${c.status} ${c.statusText}`
263
+ );
264
+ const r = c.headers.get("content-length");
265
+ let y;
266
+ r && c.body ? y = await F(
267
+ c.body,
268
+ parseInt(r, 10),
269
+ (E) => {
270
+ e == null || e({ phase: "downloading", progress: Math.round(E * 50) });
271
+ }
272
+ ) : (e == null || e({ phase: "downloading", progress: 25 }), y = await c.arrayBuffer()), e == null || e({ phase: "downloading", progress: 50 });
273
+ const o = await O(y, {
274
+ ...i,
275
+ onProgress: (E) => {
276
+ const d = 50 + Math.round(E.progress * 0.45);
277
+ e == null || e({
278
+ phase: E.phase === "complete" ? "complete" : E.phase,
279
+ progress: E.phase === "complete" ? 100 : d
280
+ });
281
+ }
282
+ });
283
+ k(o, t), e == null || e({ phase: "complete", progress: 100 });
284
+ } catch (c) {
285
+ throw c instanceof p ? c : new p("DOWNLOAD_FAILED");
286
+ }
287
+ }
288
+ async function F(n, a, t) {
289
+ const e = n.getReader(), i = [];
290
+ let c = 0;
291
+ for (; ; ) {
292
+ const { done: o, value: E } = await e.read();
293
+ if (o) break;
294
+ i.push(E), c += E.length;
295
+ const d = c / a;
296
+ t(Math.min(d, 1));
297
+ }
298
+ const r = new Uint8Array(c);
299
+ let y = 0;
300
+ for (const o of i)
301
+ r.set(o, y), y += o.length;
302
+ return r.buffer;
303
+ }
304
+ function k(n, a) {
305
+ const t = URL.createObjectURL(n), e = document.createElement("a");
306
+ e.href = t, e.download = a, e.style.display = "none", document.body.appendChild(e), e.click(), document.body.removeChild(e), URL.revokeObjectURL(t);
307
+ }
308
+ export {
309
+ l as ALGORITHM,
310
+ B as AUTH_TAG_LENGTH,
311
+ p as CryptoError,
312
+ V as ENCRYPTION_MARKER_KEYFILE,
313
+ v as ENCRYPTION_MARKER_PASSWORD,
314
+ N as HASH_ALGORITHM,
315
+ G as IV_LENGTH,
316
+ M as KEYFILE_KEY_LENGTH,
317
+ C as KEY_LENGTH,
318
+ x as MIN_ENCRYPTED_SIZE_KEYFILE,
319
+ W as MIN_ENCRYPTED_SIZE_PASSWORD,
320
+ U as PBKDF2_ITERATIONS,
321
+ H as SALT_LENGTH,
322
+ X as computeKeyFileHash,
323
+ O as decryptFile,
324
+ P as downloadAndDecrypt,
325
+ z as downloadKeyFile,
326
+ Q as encryptFile,
327
+ q as generateKeyFile,
328
+ Z as generateRandomPassword,
329
+ g as getEncryptionType,
330
+ j as isCryptoError,
331
+ $ as isEncryptedFile,
332
+ J as parseKeyFile
333
+ };
334
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":["../src/constants.ts","../src/errors.ts","../src/utils.ts","../src/encrypt.ts","../src/decrypt.ts","../src/detect.ts","../src/keyfile.ts","../src/download.ts"],"sourcesContent":["/**\n * Cryptographic constants used throughout the library.\n * These values follow industry best practices for AES-256-GCM.\n *\n * @module constants\n * @since 1.0.0\n */\n\n/**\n * Salt length in bytes for PBKDF2 key derivation.\n * 16 bytes (128 bits) provides sufficient randomness to prevent rainbow table attacks.\n */\nexport const SALT_LENGTH = 16;\n\n/**\n * Initialization vector length in bytes for AES-GCM.\n * 12 bytes (96 bits) is the recommended IV size for AES-GCM per NIST SP 800-38D.\n */\nexport const IV_LENGTH = 12;\n\n/**\n * AES key length in bits.\n * 256 bits provides the highest security level for AES.\n */\nexport const KEY_LENGTH = 256;\n\n/**\n * PBKDF2 iteration count for key derivation.\n * 100,000 iterations balance security and performance.\n * Higher values increase resistance to brute-force attacks.\n */\nexport const PBKDF2_ITERATIONS = 100_000;\n\n/**\n * Key file raw key length in bytes (256-bit).\n * Matches the AES-256 key size requirement.\n */\nexport const KEYFILE_KEY_LENGTH = 32;\n\n/**\n * Encryption marker byte for password-based encryption.\n * Used to identify the encryption method when decrypting.\n */\nexport const ENCRYPTION_MARKER_PASSWORD = 0x01;\n\n/**\n * Encryption marker byte for keyfile-based encryption.\n * Used to identify the encryption method when decrypting.\n */\nexport const ENCRYPTION_MARKER_KEYFILE = 0x02;\n\n/**\n * AES-GCM authentication tag length in bytes.\n * 16 bytes (128 bits) is the default and recommended tag size.\n */\nexport const AUTH_TAG_LENGTH = 16;\n\n/**\n * Minimum encrypted file size for password-based encryption.\n * Header (1 + 16 + 12 = 29 bytes) + Auth tag (16 bytes) = 45 bytes.\n */\nexport const MIN_ENCRYPTED_SIZE_PASSWORD = 1 + SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH;\n\n/**\n * Minimum encrypted file size for keyfile-based encryption.\n * Header (1 + 12 = 13 bytes) + Auth tag (16 bytes) = 29 bytes.\n */\nexport const MIN_ENCRYPTED_SIZE_KEYFILE = 1 + IV_LENGTH + AUTH_TAG_LENGTH;\n\n/**\n * Algorithm identifier string for AES-GCM.\n */\nexport const ALGORITHM = 'AES-GCM' as const;\n\n/**\n * Hash algorithm used in PBKDF2 key derivation.\n */\nexport const HASH_ALGORITHM = 'SHA-256' as const;\n","/**\n * Error handling for browser-file-crypto.\n *\n * @module errors\n * @since 1.0.0\n */\n\n/**\n * Error codes for CryptoError.\n *\n * @description\n * - `INVALID_INPUT`: Input is not a valid File, Blob, or ArrayBuffer\n * - `PASSWORD_REQUIRED`: Password is required but not provided\n * - `KEYFILE_REQUIRED`: Keyfile is required but not provided\n * - `INVALID_PASSWORD`: Decryption failed due to incorrect password\n * - `INVALID_KEYFILE`: Decryption failed due to incorrect keyfile\n * - `INVALID_ENCRYPTED_DATA`: Data is corrupted or not encrypted with this library\n * - `ENCRYPTION_FAILED`: Encryption operation failed\n * - `DECRYPTION_FAILED`: Decryption operation failed\n * - `DOWNLOAD_FAILED`: File download failed\n * - `UNSUPPORTED_FORMAT`: Encrypted file format is not supported\n */\nexport type CryptoErrorCode =\n | 'INVALID_INPUT'\n | 'PASSWORD_REQUIRED'\n | 'KEYFILE_REQUIRED'\n | 'INVALID_PASSWORD'\n | 'INVALID_KEYFILE'\n | 'INVALID_ENCRYPTED_DATA'\n | 'ENCRYPTION_FAILED'\n | 'DECRYPTION_FAILED'\n | 'DOWNLOAD_FAILED'\n | 'UNSUPPORTED_FORMAT';\n\n/**\n * Error messages for each error code.\n */\nconst ERROR_MESSAGES: Record<CryptoErrorCode, string> = {\n INVALID_INPUT: 'Input must be a File, Blob, or ArrayBuffer.',\n PASSWORD_REQUIRED: 'Password or keyfile is required for encryption.',\n KEYFILE_REQUIRED: 'Keyfile is required to decrypt this file.',\n INVALID_PASSWORD: 'Decryption failed: incorrect password.',\n INVALID_KEYFILE: 'Decryption failed: incorrect keyfile.',\n INVALID_ENCRYPTED_DATA: 'Invalid encrypted data: file may be corrupted.',\n ENCRYPTION_FAILED: 'Encryption failed.',\n DECRYPTION_FAILED: 'Decryption failed.',\n DOWNLOAD_FAILED: 'File download failed.',\n UNSUPPORTED_FORMAT: 'Unsupported encryption format.',\n};\n\n/**\n * Custom error class for crypto operations.\n *\n * @description\n * Provides structured error handling with error codes for programmatic handling.\n * Each error includes a code that can be used for i18n or specific error handling.\n *\n * @example\n * ```typescript\n * try {\n * await decryptFile(data, { password: 'wrong' });\n * } catch (error) {\n * if (error instanceof CryptoError) {\n * console.log(error.code); // 'INVALID_PASSWORD'\n * console.log(error.message); // 'Decryption failed: incorrect password.'\n *\n * switch (error.code) {\n * case 'INVALID_PASSWORD':\n * showPasswordError();\n * break;\n * case 'INVALID_ENCRYPTED_DATA':\n * showCorruptedFileError();\n * break;\n * }\n * }\n * }\n * ```\n *\n * @see {@link CryptoErrorCode} for all available error codes\n * @since 1.0.0\n */\nexport class CryptoError extends Error {\n /** Error code for programmatic handling */\n readonly code: CryptoErrorCode;\n\n /**\n * Creates a new CryptoError.\n *\n * @param code - Error code identifying the type of error\n * @param message - Optional custom message (defaults to predefined message)\n */\n constructor(code: CryptoErrorCode, message?: string) {\n super(message ?? ERROR_MESSAGES[code]);\n this.name = 'CryptoError';\n this.code = code;\n\n // Maintains proper stack trace in V8 environments (Chrome, Node.js)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, CryptoError);\n }\n }\n}\n\n/**\n * Type guard to check if an error is a CryptoError.\n *\n * @param error - The error to check\n * @returns True if the error is a CryptoError\n *\n * @example\n * ```typescript\n * try {\n * await decryptFile(data, options);\n * } catch (error) {\n * if (isCryptoError(error)) {\n * console.log(error.code);\n * }\n * }\n * ```\n */\nexport function isCryptoError(error: unknown): error is CryptoError {\n return error instanceof CryptoError;\n}\n","/**\n * Utility functions for browser-file-crypto.\n *\n * @module utils\n * @internal\n * @since 1.0.0\n */\n\nimport {\n SALT_LENGTH,\n IV_LENGTH,\n KEY_LENGTH,\n PBKDF2_ITERATIONS,\n ALGORITHM,\n HASH_ALGORITHM,\n} from './constants';\nimport { CryptoError } from './errors';\n\n/**\n * Normalizes input to ArrayBuffer for consistent processing.\n *\n * @description\n * Accepts File, Blob, or ArrayBuffer and converts to ArrayBuffer.\n * This ensures all encryption/decryption functions work with a consistent type.\n *\n * @param input - File, Blob, or ArrayBuffer to normalize\n * @returns Promise resolving to ArrayBuffer\n *\n * @throws {CryptoError} When input is not a valid type (INVALID_INPUT)\n *\n * @example\n * ```typescript\n * const file = document.querySelector('input').files[0];\n * const buffer = await normalizeInput(file);\n * ```\n *\n * @internal\n */\nexport async function normalizeInput(\n input: File | Blob | ArrayBuffer\n): Promise<ArrayBuffer> {\n if (input instanceof ArrayBuffer) {\n return input;\n }\n\n if (input instanceof Blob) {\n return input.arrayBuffer();\n }\n\n throw new CryptoError('INVALID_INPUT');\n}\n\n/**\n * Safely slices a TypedArray's buffer to get exact range.\n *\n * @description\n * Required because TypedArray.buffer may reference a larger ArrayBuffer\n * when the TypedArray is a view into a portion of the buffer.\n * This function ensures we get only the exact bytes we need.\n *\n * @param arr - Uint8Array to slice\n * @returns ArrayBuffer containing only the bytes from the Uint8Array\n *\n * @example\n * ```typescript\n * const iv = new Uint8Array(12);\n * const ivBuffer = sliceBuffer(iv);\n * // ivBuffer is guaranteed to be exactly 12 bytes\n * ```\n *\n * @internal\n */\nexport function sliceBuffer(arr: Uint8Array): ArrayBuffer {\n return (arr.buffer as ArrayBuffer).slice(arr.byteOffset, arr.byteOffset + arr.byteLength);\n}\n\n/**\n * Converts ArrayBuffer to base64 string.\n *\n * @description\n * Uses browser's native btoa() for encoding.\n * Handles binary data by converting each byte to a character.\n *\n * @param buffer - ArrayBuffer to encode\n * @returns Base64-encoded string\n *\n * @example\n * ```typescript\n * const key = crypto.getRandomValues(new Uint8Array(32));\n * const base64Key = arrayBufferToBase64(key.buffer);\n * ```\n *\n * @internal\n */\nexport function arrayBufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.byteLength; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n return btoa(binary);\n}\n\n/**\n * Converts base64 string to ArrayBuffer.\n *\n * @description\n * Uses browser's native atob() for decoding.\n * Returns an ArrayBuffer that can be used with Web Crypto API.\n *\n * @param base64 - Base64-encoded string to decode\n * @returns ArrayBuffer containing the decoded bytes\n *\n * @throws {DOMException} When base64 string is invalid\n *\n * @example\n * ```typescript\n * const keyBuffer = base64ToArrayBuffer(keyFile.key);\n * const cryptoKey = await crypto.subtle.importKey('raw', keyBuffer, ...);\n * ```\n *\n * @internal\n */\nexport function base64ToArrayBuffer(base64: string): ArrayBuffer {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\n/**\n * Derives an AES-256 encryption key from a password using PBKDF2.\n *\n * @description\n * Uses PBKDF2 with SHA-256 and 100,000 iterations to derive a 256-bit key.\n * The derived key is non-extractable for security.\n *\n * @param password - The user-provided password string\n * @param salt - 16-byte random salt for key derivation\n * @returns Promise resolving to a non-extractable CryptoKey\n *\n * @throws {Error} When key derivation fails\n *\n * @example\n * ```typescript\n * const salt = crypto.getRandomValues(new Uint8Array(16));\n * const key = await deriveKeyFromPassword('my-password', salt);\n * ```\n *\n * @internal\n */\nexport async function deriveKeyFromPassword(\n password: string,\n salt: Uint8Array\n): Promise<CryptoKey> {\n const encoder = new TextEncoder();\n\n // Import password as raw key material\n const keyMaterial = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(password),\n 'PBKDF2',\n false,\n ['deriveKey']\n );\n\n // Derive AES-256 key using PBKDF2\n return crypto.subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: sliceBuffer(salt),\n iterations: PBKDF2_ITERATIONS,\n hash: HASH_ALGORITHM,\n },\n keyMaterial,\n { name: ALGORITHM, length: KEY_LENGTH },\n false, // non-extractable for security\n ['encrypt', 'decrypt']\n );\n}\n\n/**\n * Imports raw key bytes for keyfile-based encryption.\n *\n * @description\n * Imports a 256-bit key directly from base64-encoded key data.\n * No key derivation is needed - the key is used as-is.\n * The imported key is non-extractable for security.\n *\n * @param keyData - Base64-encoded 256-bit key string\n * @returns Promise resolving to a non-extractable CryptoKey\n *\n * @throws {CryptoError} When key import fails (INVALID_KEYFILE)\n *\n * @example\n * ```typescript\n * const keyFile = generateKeyFile();\n * const cryptoKey = await importKeyFromKeyfile(keyFile.key);\n * ```\n *\n * @internal\n */\nexport async function importKeyFromKeyfile(keyData: string): Promise<CryptoKey> {\n try {\n const keyBuffer = base64ToArrayBuffer(keyData);\n\n return await crypto.subtle.importKey(\n 'raw',\n keyBuffer,\n { name: ALGORITHM, length: KEY_LENGTH },\n false, // non-extractable for security\n ['encrypt', 'decrypt']\n );\n } catch {\n throw new CryptoError('INVALID_KEYFILE');\n }\n}\n\n/**\n * Generates cryptographically secure random bytes.\n *\n * @description\n * Uses crypto.getRandomValues() for cryptographically secure randomness.\n *\n * @param length - Number of random bytes to generate\n * @returns Uint8Array containing random bytes\n *\n * @example\n * ```typescript\n * const salt = generateRandomBytes(SALT_LENGTH);\n * const iv = generateRandomBytes(IV_LENGTH);\n * ```\n *\n * @internal\n */\nexport function generateRandomBytes(length: number): Uint8Array {\n return crypto.getRandomValues(new Uint8Array(length));\n}\n\n/**\n * Generates a random salt for PBKDF2 key derivation.\n *\n * @returns 16-byte random salt\n *\n * @internal\n */\nexport function generateSalt(): Uint8Array {\n return generateRandomBytes(SALT_LENGTH);\n}\n\n/**\n * Generates a random IV for AES-GCM encryption.\n *\n * @returns 12-byte random IV\n *\n * @internal\n */\nexport function generateIV(): Uint8Array {\n return generateRandomBytes(IV_LENGTH);\n}\n\n/**\n * Default character set for random password generation.\n * Includes uppercase, lowercase, numbers, and special characters.\n */\nconst PASSWORD_CHARSET =\n 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';\n\n/**\n * Default password length.\n */\nconst DEFAULT_PASSWORD_LENGTH = 16;\n\n/**\n * Generates a cryptographically secure random password.\n *\n * @description\n * Creates a random password using crypto.getRandomValues() for\n * cryptographic randomness. The password includes:\n * - Uppercase letters (A-Z)\n * - Lowercase letters (a-z)\n * - Numbers (0-9)\n * - Special characters (!@#$%^&*)\n *\n * @param length - Password length (default: 16, min: 8, max: 128)\n * @returns Random password string\n *\n * @example\n * ```typescript\n * const password = generateRandomPassword();\n * console.log(password); // e.g., 'Kx9#mP2$vL5@nQ8!'\n *\n * const longPassword = generateRandomPassword(32);\n * console.log(longPassword.length); // 32\n * ```\n *\n * @since 1.0.0\n */\nexport function generateRandomPassword(length: number = DEFAULT_PASSWORD_LENGTH): string {\n // Clamp length to reasonable bounds\n const safeLength = Math.max(8, Math.min(128, length));\n\n const randomBytes = generateRandomBytes(safeLength);\n let password = '';\n\n for (let i = 0; i < safeLength; i++) {\n const index = randomBytes[i]! % PASSWORD_CHARSET.length;\n password += PASSWORD_CHARSET[index];\n }\n\n return password;\n}\n","/**\n * File encryption functions for browser-file-crypto.\n *\n * @module encrypt\n * @since 1.0.0\n */\n\nimport {\n SALT_LENGTH,\n IV_LENGTH,\n ENCRYPTION_MARKER_PASSWORD,\n ENCRYPTION_MARKER_KEYFILE,\n ALGORITHM,\n} from './constants';\nimport { CryptoError } from './errors';\nimport type { EncryptOptions } from './types';\nimport {\n normalizeInput,\n sliceBuffer,\n deriveKeyFromPassword,\n importKeyFromKeyfile,\n generateSalt,\n generateIV,\n} from './utils';\n\n/**\n * Encrypts a file using AES-256-GCM with password-based key derivation.\n *\n * @description\n * Uses PBKDF2 with 100,000 iterations to derive a 256-bit key from the password.\n * Each encryption generates a unique random salt (16 bytes) and IV (12 bytes).\n * The output format is: [marker(1)] + [salt(16)] + [iv(12)] + [ciphertext + auth tag].\n *\n * For keyfile-based encryption, the key is used directly without derivation.\n * The output format is: [marker(1)] + [iv(12)] + [ciphertext + auth tag].\n *\n * @param file - The file to encrypt (File, Blob, or ArrayBuffer)\n * @param options - Encryption options including password or keyData\n * @returns Promise resolving to encrypted Blob with 'application/octet-stream' MIME type\n *\n * @throws {CryptoError} When neither password nor keyData is provided (PASSWORD_REQUIRED)\n * @throws {CryptoError} When input is invalid (INVALID_INPUT)\n * @throws {CryptoError} When encryption fails (ENCRYPTION_FAILED)\n *\n * @example\n * ```typescript\n * // Password-based encryption\n * const encrypted = await encryptFile(file, {\n * password: 'my-secret',\n * onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)\n * });\n *\n * // Keyfile-based encryption\n * const keyFile = generateKeyFile();\n * const encrypted = await encryptFile(file, {\n * keyData: keyFile.key,\n * onProgress: ({ progress }) => updateProgressBar(progress)\n * });\n * ```\n *\n * @see {@link decryptFile} for decryption\n * @see {@link generateKeyFile} for creating keyfiles\n * @since 1.0.0\n */\nexport async function encryptFile(\n file: File | Blob | ArrayBuffer,\n options: EncryptOptions\n): Promise<Blob> {\n const { password, keyData, onProgress } = options;\n\n // Validate: either password or keyData must be provided\n if (!password && !keyData) {\n throw new CryptoError('PASSWORD_REQUIRED');\n }\n\n try {\n // Step 1: Normalize input to ArrayBuffer\n onProgress?.({ phase: 'deriving_key', progress: 0 });\n const data = await normalizeInput(file);\n onProgress?.({ phase: 'deriving_key', progress: 10 });\n\n // Branch based on encryption method\n if (keyData) {\n return await encryptWithKeyfile(data, keyData, onProgress);\n } else {\n return await encryptWithPassword(data, password!, onProgress);\n }\n } catch (error) {\n if (error instanceof CryptoError) {\n throw error;\n }\n throw new CryptoError('ENCRYPTION_FAILED');\n }\n}\n\n/**\n * Encrypts data using password-based key derivation.\n *\n * @internal\n */\nasync function encryptWithPassword(\n data: ArrayBuffer,\n password: string,\n onProgress?: EncryptOptions['onProgress']\n): Promise<Blob> {\n // Step 2: Generate random salt and IV\n const salt = generateSalt();\n const iv = generateIV();\n onProgress?.({ phase: 'deriving_key', progress: 20 });\n\n // Step 3: Derive key from password\n const key = await deriveKeyFromPassword(password, salt);\n onProgress?.({ phase: 'encrypting', progress: 30 });\n\n // Step 4: Encrypt with AES-GCM\n const ciphertext = await crypto.subtle.encrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n data\n );\n onProgress?.({ phase: 'encrypting', progress: 90 });\n\n // Step 5: Assemble output: marker + salt + iv + ciphertext\n const result = new Uint8Array(\n 1 + SALT_LENGTH + IV_LENGTH + ciphertext.byteLength\n );\n result[0] = ENCRYPTION_MARKER_PASSWORD;\n result.set(salt, 1);\n result.set(iv, 1 + SALT_LENGTH);\n result.set(new Uint8Array(ciphertext), 1 + SALT_LENGTH + IV_LENGTH);\n\n onProgress?.({ phase: 'complete', progress: 100 });\n\n return new Blob([result], { type: 'application/octet-stream' });\n}\n\n/**\n * Encrypts data using keyfile-based encryption.\n *\n * @internal\n */\nasync function encryptWithKeyfile(\n data: ArrayBuffer,\n keyData: string,\n onProgress?: EncryptOptions['onProgress']\n): Promise<Blob> {\n // Step 2: Generate random IV (no salt needed for keyfile)\n const iv = generateIV();\n onProgress?.({ phase: 'deriving_key', progress: 20 });\n\n // Step 3: Import key from keyfile\n const key = await importKeyFromKeyfile(keyData);\n onProgress?.({ phase: 'encrypting', progress: 30 });\n\n // Step 4: Encrypt with AES-GCM\n const ciphertext = await crypto.subtle.encrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n data\n );\n onProgress?.({ phase: 'encrypting', progress: 90 });\n\n // Step 5: Assemble output: marker + iv + ciphertext\n const result = new Uint8Array(1 + IV_LENGTH + ciphertext.byteLength);\n result[0] = ENCRYPTION_MARKER_KEYFILE;\n result.set(iv, 1);\n result.set(new Uint8Array(ciphertext), 1 + IV_LENGTH);\n\n onProgress?.({ phase: 'complete', progress: 100 });\n\n return new Blob([result], { type: 'application/octet-stream' });\n}\n","/**\n * File decryption functions for browser-file-crypto.\n *\n * @module decrypt\n * @since 1.0.0\n */\n\nimport {\n SALT_LENGTH,\n IV_LENGTH,\n ENCRYPTION_MARKER_PASSWORD,\n ENCRYPTION_MARKER_KEYFILE,\n MIN_ENCRYPTED_SIZE_PASSWORD,\n MIN_ENCRYPTED_SIZE_KEYFILE,\n ALGORITHM,\n} from './constants';\nimport { CryptoError } from './errors';\nimport type { DecryptOptions } from './types';\nimport {\n normalizeInput,\n sliceBuffer,\n deriveKeyFromPassword,\n importKeyFromKeyfile,\n} from './utils';\n\n/**\n * Decrypts a file that was encrypted with encryptFile.\n *\n * @description\n * Automatically detects whether the file was encrypted with password or keyfile\n * based on the marker byte, then decrypts accordingly.\n *\n * For password-encrypted files:\n * - Extracts salt and IV from header\n * - Derives key using PBKDF2\n * - Decrypts with AES-GCM\n *\n * For keyfile-encrypted files:\n * - Extracts IV from header\n * - Imports key directly\n * - Decrypts with AES-GCM\n *\n * @param encrypted - The encrypted data (Blob or ArrayBuffer)\n * @param options - Decryption options including password or keyData\n * @returns Promise resolving to decrypted Blob\n *\n * @throws {CryptoError} When password is required but not provided (PASSWORD_REQUIRED)\n * @throws {CryptoError} When keyfile is required but not provided (KEYFILE_REQUIRED)\n * @throws {CryptoError} When password is incorrect (INVALID_PASSWORD)\n * @throws {CryptoError} When keyfile is incorrect (INVALID_KEYFILE)\n * @throws {CryptoError} When data is corrupted (INVALID_ENCRYPTED_DATA)\n *\n * @example\n * ```typescript\n * // Password-based decryption\n * try {\n * const decrypted = await decryptFile(encryptedBlob, {\n * password: 'my-secret',\n * onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)\n * });\n * } catch (error) {\n * if (error instanceof CryptoError && error.code === 'INVALID_PASSWORD') {\n * console.log('Wrong password!');\n * }\n * }\n *\n * // Keyfile-based decryption\n * const decrypted = await decryptFile(encryptedBlob, {\n * keyData: keyFile.key\n * });\n * ```\n *\n * @see {@link encryptFile} for encryption\n * @see {@link getEncryptionType} for detecting encryption method\n * @since 1.0.0\n */\nexport async function decryptFile(\n encrypted: Blob | ArrayBuffer,\n options: DecryptOptions\n): Promise<Blob> {\n const { password, keyData, onProgress } = options;\n\n try {\n // Step 1: Normalize input to ArrayBuffer\n onProgress?.({ phase: 'decrypting', progress: 0 });\n const data = new Uint8Array(await normalizeInput(encrypted));\n onProgress?.({ phase: 'decrypting', progress: 5 });\n\n // Step 2: Check minimum size and read marker\n if (data.length < 1) {\n throw new CryptoError('INVALID_ENCRYPTED_DATA');\n }\n\n const marker = data[0];\n\n // Step 3: Branch based on encryption method\n if (marker === ENCRYPTION_MARKER_PASSWORD) {\n if (!password) {\n throw new CryptoError('PASSWORD_REQUIRED');\n }\n return await decryptWithPassword(data, password, onProgress);\n } else if (marker === ENCRYPTION_MARKER_KEYFILE) {\n if (!keyData) {\n throw new CryptoError('KEYFILE_REQUIRED');\n }\n return await decryptWithKeyfile(data, keyData, onProgress);\n } else {\n throw new CryptoError('UNSUPPORTED_FORMAT');\n }\n } catch (error) {\n if (error instanceof CryptoError) {\n throw error;\n }\n throw new CryptoError('DECRYPTION_FAILED');\n }\n}\n\n/**\n * Decrypts data that was encrypted with password.\n *\n * @internal\n */\nasync function decryptWithPassword(\n data: Uint8Array,\n password: string,\n onProgress?: DecryptOptions['onProgress']\n): Promise<Blob> {\n // Validate minimum size\n if (data.length < MIN_ENCRYPTED_SIZE_PASSWORD) {\n throw new CryptoError('INVALID_ENCRYPTED_DATA');\n }\n\n onProgress?.({ phase: 'deriving_key', progress: 10 });\n\n // Extract components: marker(1) + salt(16) + iv(12) + ciphertext\n const salt = data.slice(1, 1 + SALT_LENGTH);\n const iv = data.slice(1 + SALT_LENGTH, 1 + SALT_LENGTH + IV_LENGTH);\n const ciphertext = data.slice(1 + SALT_LENGTH + IV_LENGTH);\n\n // Derive key from password\n const key = await deriveKeyFromPassword(password, salt);\n onProgress?.({ phase: 'decrypting', progress: 30 });\n\n // Decrypt with AES-GCM\n try {\n const plaintext = await crypto.subtle.decrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n ciphertext\n );\n onProgress?.({ phase: 'complete', progress: 100 });\n\n return new Blob([plaintext]);\n } catch {\n throw new CryptoError('INVALID_PASSWORD');\n }\n}\n\n/**\n * Decrypts data that was encrypted with keyfile.\n *\n * @internal\n */\nasync function decryptWithKeyfile(\n data: Uint8Array,\n keyData: string,\n onProgress?: DecryptOptions['onProgress']\n): Promise<Blob> {\n // Validate minimum size\n if (data.length < MIN_ENCRYPTED_SIZE_KEYFILE) {\n throw new CryptoError('INVALID_ENCRYPTED_DATA');\n }\n\n onProgress?.({ phase: 'decrypting', progress: 10 });\n\n // Extract components: marker(1) + iv(12) + ciphertext\n const iv = data.slice(1, 1 + IV_LENGTH);\n const ciphertext = data.slice(1 + IV_LENGTH);\n\n // Import key from keyfile\n const key = await importKeyFromKeyfile(keyData);\n onProgress?.({ phase: 'decrypting', progress: 30 });\n\n // Decrypt with AES-GCM\n try {\n const plaintext = await crypto.subtle.decrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n ciphertext\n );\n onProgress?.({ phase: 'complete', progress: 100 });\n\n return new Blob([plaintext]);\n } catch {\n throw new CryptoError('INVALID_KEYFILE');\n }\n}\n","/**\n * Encryption type detection functions for browser-file-crypto.\n *\n * @module detect\n * @since 1.0.0\n */\n\nimport {\n ENCRYPTION_MARKER_PASSWORD,\n ENCRYPTION_MARKER_KEYFILE,\n MIN_ENCRYPTED_SIZE_PASSWORD,\n MIN_ENCRYPTED_SIZE_KEYFILE,\n} from './constants';\nimport type { EncryptionType } from './types';\nimport { normalizeInput } from './utils';\n\n/**\n * Detects the encryption type of encrypted data.\n *\n * @description\n * Reads the first byte (marker) of the encrypted data to determine\n * whether it was encrypted with a password or keyfile.\n *\n * - Marker 0x01: Password-based encryption\n * - Marker 0x02: Keyfile-based encryption\n * - Other: Unknown format\n *\n * @param data - The encrypted data (Blob or ArrayBuffer)\n * @returns Promise resolving to encryption type\n *\n * @example\n * ```typescript\n * const type = await getEncryptionType(encryptedBlob);\n *\n * switch (type) {\n * case 'password':\n * // Show password input\n * break;\n * case 'keyfile':\n * // Show keyfile picker\n * break;\n * case 'unknown':\n * // Show error: not encrypted with this library\n * break;\n * }\n * ```\n *\n * @see {@link isEncryptedFile} for simple encryption check\n * @since 1.0.0\n */\nexport async function getEncryptionType(\n data: Blob | ArrayBuffer\n): Promise<EncryptionType> {\n const buffer = await normalizeInput(data);\n const bytes = new Uint8Array(buffer);\n\n if (bytes.length < 1) {\n return 'unknown';\n }\n\n const marker = bytes[0];\n\n if (marker === ENCRYPTION_MARKER_PASSWORD) {\n return 'password';\n }\n\n if (marker === ENCRYPTION_MARKER_KEYFILE) {\n return 'keyfile';\n }\n\n return 'unknown';\n}\n\n/**\n * Checks if data appears to be encrypted with this library.\n *\n * @description\n * Performs a quick check to determine if the data was likely encrypted\n * with browser-file-crypto. This checks:\n * 1. The marker byte is valid (0x01 or 0x02)\n * 2. The data meets minimum size requirements\n *\n * Note: This is not a cryptographic verification. It only checks\n * the format markers and size constraints.\n *\n * @param data - The data to check (Blob or ArrayBuffer)\n * @returns Promise resolving to true if data appears to be encrypted\n *\n * @example\n * ```typescript\n * const file = event.target.files[0];\n * const isEncrypted = await isEncryptedFile(file);\n *\n * if (isEncrypted) {\n * showDecryptionUI();\n * } else {\n * showEncryptionUI();\n * }\n * ```\n *\n * @since 1.0.0\n */\nexport async function isEncryptedFile(data: Blob | ArrayBuffer): Promise<boolean> {\n const buffer = await normalizeInput(data);\n const bytes = new Uint8Array(buffer);\n\n if (bytes.length < 1) {\n return false;\n }\n\n const marker = bytes[0];\n\n // Check password-encrypted format\n if (\n marker === ENCRYPTION_MARKER_PASSWORD &&\n bytes.length >= MIN_ENCRYPTED_SIZE_PASSWORD\n ) {\n return true;\n }\n\n // Check keyfile-encrypted format\n if (\n marker === ENCRYPTION_MARKER_KEYFILE &&\n bytes.length >= MIN_ENCRYPTED_SIZE_KEYFILE\n ) {\n return true;\n }\n\n return false;\n}\n","/**\n * Keyfile generation and management functions for browser-file-crypto.\n *\n * @module keyfile\n * @since 1.0.0\n */\n\nimport { KEYFILE_KEY_LENGTH } from './constants';\nimport type { KeyFile } from './types';\nimport { arrayBufferToBase64, base64ToArrayBuffer, generateRandomBytes } from './utils';\n\n/**\n * Generates a new keyfile with a 256-bit random key.\n *\n * @description\n * Creates a cryptographically secure 256-bit (32 bytes) random key\n * using crypto.getRandomValues(). The key is returned as a KeyFile\n * object with metadata including version, algorithm, and creation timestamp.\n *\n * The generated key can be used directly with encryptFile() and decryptFile()\n * without any key derivation (unlike password-based encryption).\n *\n * @returns KeyFile object containing the generated key\n *\n * @example\n * ```typescript\n * const keyFile = generateKeyFile();\n * console.log(keyFile);\n * // {\n * // version: 1,\n * // algorithm: 'AES-256-GCM',\n * // key: 'base64-encoded-32-bytes...',\n * // createdAt: '2025-01-01T12:00:00.000Z'\n * // }\n *\n * // Use for encryption\n * const encrypted = await encryptFile(file, { keyData: keyFile.key });\n *\n * // Save keyfile for later\n * downloadKeyFile(keyFile.key, 'my-encryption-key');\n * ```\n *\n * @see {@link downloadKeyFile} for saving the keyfile\n * @see {@link parseKeyFile} for loading a saved keyfile\n * @since 1.0.0\n */\nexport function generateKeyFile(): KeyFile {\n const keyBytes = generateRandomBytes(KEYFILE_KEY_LENGTH);\n const key = arrayBufferToBase64(keyBytes.buffer as ArrayBuffer);\n\n return {\n version: 1,\n algorithm: 'AES-256-GCM',\n key,\n createdAt: new Date().toISOString(),\n };\n}\n\n/**\n * Parses a keyfile from JSON string content.\n *\n * @description\n * Validates and parses a JSON string into a KeyFile object.\n * Performs validation to ensure:\n * - Valid JSON format\n * - Required fields present (version, algorithm, key)\n * - Correct version (1)\n * - Correct algorithm ('AES-256-GCM')\n * - Key is a non-empty string\n *\n * @param content - JSON string content of the keyfile\n * @returns KeyFile object if valid, null if invalid\n *\n * @example\n * ```typescript\n * // From file input\n * const fileInput = document.querySelector('input[type=\"file\"]');\n * fileInput.addEventListener('change', async (e) => {\n * const file = e.target.files[0];\n * const content = await file.text();\n * const keyFile = parseKeyFile(content);\n *\n * if (keyFile) {\n * const decrypted = await decryptFile(encrypted, { keyData: keyFile.key });\n * } else {\n * console.error('Invalid keyfile format');\n * }\n * });\n * ```\n *\n * @see {@link generateKeyFile} for creating keyfiles\n * @since 1.0.0\n */\nexport function parseKeyFile(content: string): KeyFile | null {\n try {\n const parsed = JSON.parse(content) as unknown;\n\n // Type guard and validation\n if (!isValidKeyFile(parsed)) {\n return null;\n }\n\n return parsed;\n } catch {\n return null;\n }\n}\n\n/**\n * Validates if an object is a valid KeyFile.\n *\n * @internal\n */\nfunction isValidKeyFile(obj: unknown): obj is KeyFile {\n if (typeof obj !== 'object' || obj === null) {\n return false;\n }\n\n const candidate = obj as Record<string, unknown>;\n\n return (\n candidate.version === 1 &&\n candidate.algorithm === 'AES-256-GCM' &&\n typeof candidate.key === 'string' &&\n candidate.key.length > 0 &&\n typeof candidate.createdAt === 'string'\n );\n}\n\n/**\n * Downloads a keyfile as a JSON file with customizable extension.\n *\n * @description\n * Creates a downloadable JSON file containing the keyfile data.\n * The file extension can be customized (default: .key).\n *\n * Note: This function uses browser APIs (URL.createObjectURL, document.createElement)\n * and will not work in Node.js or Web Worker environments.\n *\n * @param keyData - Base64-encoded key string (from KeyFile.key)\n * @param fileName - Name for the downloaded file (without extension)\n * @param extension - File extension without dot (default: 'key')\n *\n * @example\n * ```typescript\n * const keyFile = generateKeyFile();\n *\n * // Downloads as 'my-secret-key.key' (default)\n * downloadKeyFile(keyFile.key, 'my-secret-key');\n *\n * // Downloads as 'my-secret-key.tfkey' (custom extension)\n * downloadKeyFile(keyFile.key, 'my-secret-key', 'tfkey');\n *\n * // The downloaded file contains:\n * // {\n * // \"version\": 1,\n * // \"algorithm\": \"AES-256-GCM\",\n * // \"key\": \"base64...\",\n * // \"createdAt\": \"2025-01-01T00:00:00.000Z\"\n * // }\n * ```\n *\n * @since 1.0.0\n */\nexport function downloadKeyFile(keyData: string, fileName: string, extension: string = 'key'): void {\n const keyFile: KeyFile = {\n version: 1,\n algorithm: 'AES-256-GCM',\n key: keyData,\n createdAt: new Date().toISOString(),\n };\n\n const json = JSON.stringify(keyFile, null, 2);\n const blob = new Blob([json], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n\n const link = document.createElement('a');\n link.href = url;\n link.download = `${fileName}.${extension}`;\n link.style.display = 'none';\n\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n\n URL.revokeObjectURL(url);\n}\n\n/**\n * Computes SHA-256 hash of a keyfile's key data.\n *\n * @description\n * Generates a SHA-256 hash of the key data for server-side verification.\n * This allows the server to verify that the client has the correct keyfile\n * without ever seeing the actual key.\n *\n * Use case: Store the hash on the server, then verify client-provided\n * hash matches before allowing access to encrypted content.\n *\n * @param keyData - Base64-encoded key string (from KeyFile.key)\n * @returns Promise resolving to hex-encoded SHA-256 hash\n *\n * @example\n * ```typescript\n * const keyFile = generateKeyFile();\n * const hash = await computeKeyFileHash(keyFile.key);\n * console.log(hash); // '3a7bd3e2c1f8...' (64 hex characters)\n *\n * // Send hash to server for storage\n * await fetch('/api/store-key-hash', {\n * method: 'POST',\n * body: JSON.stringify({ hash })\n * });\n *\n * // Later, verify keyfile by comparing hashes\n * const uploadedHash = await computeKeyFileHash(uploadedKeyFile.key);\n * const isValid = uploadedHash === storedHash;\n * ```\n *\n * @since 1.0.0\n */\nexport async function computeKeyFileHash(keyData: string): Promise<string> {\n const keyBuffer = base64ToArrayBuffer(keyData);\n const hashBuffer = await crypto.subtle.digest('SHA-256', keyBuffer);\n const hashArray = new Uint8Array(hashBuffer);\n\n // Convert to hex string\n return Array.from(hashArray)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","/**\n * Download and decrypt functions for browser-file-crypto.\n *\n * @module download\n * @since 1.0.0\n */\n\nimport { CryptoError } from './errors';\nimport type { DownloadDecryptOptions } from './types';\nimport { decryptFile } from './decrypt';\n\n/**\n * Downloads an encrypted file from URL, decrypts it, and saves to disk.\n *\n * @description\n * Combines file download, decryption, and save into a single operation.\n * Progress callback reports both download and decryption phases.\n *\n * Phases:\n * 1. `downloading` (0-50%): Fetching file from URL\n * 2. `deriving_key` (50-60%): Key derivation (password mode only)\n * 3. `decrypting` (60-95%): AES-GCM decryption\n * 4. `complete` (100%): File saved\n *\n * Note: This function uses browser APIs (fetch, URL.createObjectURL, document.createElement)\n * and will not work in Node.js environments.\n *\n * @param url - URL of the encrypted file to download\n * @param options - Options including password/keyData and fileName\n * @returns Promise that resolves when file is saved\n *\n * @throws {CryptoError} When download fails (DOWNLOAD_FAILED)\n * @throws {CryptoError} When decryption fails (see decryptFile errors)\n *\n * @example\n * ```typescript\n * await downloadAndDecrypt('https://example.com/secret.enc', {\n * password: 'my-secret',\n * fileName: 'document.pdf',\n * onProgress: ({ phase, progress }) => {\n * console.log(`${phase}: ${progress}%`);\n * // downloading: 25%\n * // downloading: 50%\n * // deriving_key: 55%\n * // decrypting: 80%\n * // complete: 100%\n * }\n * });\n * ```\n *\n * @see {@link decryptFile} for decryption only\n * @since 1.0.0\n */\nexport async function downloadAndDecrypt(\n url: string,\n options: DownloadDecryptOptions\n): Promise<void> {\n const { fileName, onProgress, ...decryptOptions } = options;\n\n try {\n // Phase 1: Download file\n onProgress?.({ phase: 'downloading', progress: 0 });\n\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new CryptoError(\n 'DOWNLOAD_FAILED',\n `Download failed: ${response.status} ${response.statusText}`\n );\n }\n\n // Track download progress if content-length is available\n const contentLength = response.headers.get('content-length');\n let encryptedData: ArrayBuffer;\n\n if (contentLength && response.body) {\n encryptedData = await downloadWithProgress(\n response.body,\n parseInt(contentLength, 10),\n (downloadProgress) => {\n // Map download progress to 0-50%\n onProgress?.({ phase: 'downloading', progress: Math.round(downloadProgress * 50) });\n }\n );\n } else {\n // Fallback: no progress tracking\n onProgress?.({ phase: 'downloading', progress: 25 });\n encryptedData = await response.arrayBuffer();\n }\n\n onProgress?.({ phase: 'downloading', progress: 50 });\n\n // Phase 2 & 3: Decrypt with progress mapping\n const decrypted = await decryptFile(encryptedData, {\n ...decryptOptions,\n onProgress: (progress) => {\n // Map decryption progress (0-100) to (50-95)\n const mappedProgress = 50 + Math.round(progress.progress * 0.45);\n onProgress?.({\n phase: progress.phase === 'complete' ? 'complete' : progress.phase,\n progress: progress.phase === 'complete' ? 100 : mappedProgress,\n });\n },\n });\n\n // Phase 4: Save file\n saveFile(decrypted, fileName);\n onProgress?.({ phase: 'complete', progress: 100 });\n } catch (error) {\n if (error instanceof CryptoError) {\n throw error;\n }\n throw new CryptoError('DOWNLOAD_FAILED');\n }\n}\n\n/**\n * Downloads with progress tracking using ReadableStream.\n *\n * @internal\n */\nasync function downloadWithProgress(\n body: ReadableStream<Uint8Array>,\n contentLength: number,\n onProgress: (progress: number) => void\n): Promise<ArrayBuffer> {\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let receivedLength = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) break;\n\n chunks.push(value);\n receivedLength += value.length;\n\n const progress = receivedLength / contentLength;\n onProgress(Math.min(progress, 1));\n }\n\n // Combine chunks into single ArrayBuffer\n const result = new Uint8Array(receivedLength);\n let position = 0;\n for (const chunk of chunks) {\n result.set(chunk, position);\n position += chunk.length;\n }\n\n return result.buffer;\n}\n\n/**\n * Saves a Blob as a file download.\n *\n * @internal\n */\nfunction saveFile(blob: Blob, fileName: string): void {\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n\n link.href = url;\n link.download = fileName;\n link.style.display = 'none';\n\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n\n URL.revokeObjectURL(url);\n}\n"],"names":["SALT_LENGTH","IV_LENGTH","KEY_LENGTH","PBKDF2_ITERATIONS","KEYFILE_KEY_LENGTH","ENCRYPTION_MARKER_PASSWORD","ENCRYPTION_MARKER_KEYFILE","AUTH_TAG_LENGTH","MIN_ENCRYPTED_SIZE_PASSWORD","MIN_ENCRYPTED_SIZE_KEYFILE","ALGORITHM","HASH_ALGORITHM","ERROR_MESSAGES","CryptoError","code","message","isCryptoError","error","normalizeInput","input","sliceBuffer","arr","arrayBufferToBase64","buffer","bytes","binary","i","base64ToArrayBuffer","base64","deriveKeyFromPassword","password","salt","encoder","keyMaterial","importKeyFromKeyfile","keyData","keyBuffer","generateRandomBytes","length","generateSalt","generateIV","PASSWORD_CHARSET","DEFAULT_PASSWORD_LENGTH","generateRandomPassword","safeLength","randomBytes","index","encryptFile","file","options","onProgress","data","encryptWithKeyfile","encryptWithPassword","iv","key","ciphertext","result","decryptFile","encrypted","marker","decryptWithPassword","decryptWithKeyfile","plaintext","getEncryptionType","isEncryptedFile","generateKeyFile","keyBytes","parseKeyFile","content","parsed","isValidKeyFile","obj","candidate","downloadKeyFile","fileName","extension","keyFile","json","blob","url","link","computeKeyFileHash","hashBuffer","hashArray","b","downloadAndDecrypt","decryptOptions","response","contentLength","encryptedData","downloadWithProgress","downloadProgress","decrypted","progress","mappedProgress","saveFile","body","reader","chunks","receivedLength","done","value","position","chunk"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ (function(r,d){typeof exports=="object"&&typeof module<"u"?d(exports):typeof define=="function"&&define.amd?define(["exports"],d):(r=typeof globalThis<"u"?globalThis:r||self,d(r.BrowserFileCrypto={}))})(this,function(r){"use strict";const p="AES-GCM",I="SHA-256",S={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 y extends Error{constructor(a,e){super(e??S[a]),this.name="CryptoError",this.code=a,Error.captureStackTrace&&Error.captureStackTrace(this,y)}}function D(n){return n instanceof y}async function u(n){if(n instanceof ArrayBuffer)return n;if(n instanceof Blob)return n.arrayBuffer();throw new y("INVALID_INPUT")}function f(n){return n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength)}function K(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 s(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 N(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:f(a),iterations:1e5,hash:I},t,{name:p,length:256},!1,["encrypt","decrypt"])}async function h(n){try{const a=s(n);return await crypto.subtle.importKey("raw",a,{name:p,length:256},!1,["encrypt","decrypt"])}catch{throw new y("INVALID_KEYFILE")}}function T(n){return crypto.getRandomValues(new Uint8Array(n))}function O(){return T(16)}function R(){return T(12)}const L="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*",Y=16;function m(n=Y){const a=Math.max(8,Math.min(128,n)),e=T(a);let t="";for(let c=0;c<a;c++){const i=e[c]%L.length;t+=L[i]}return t}async function F(n,a){const{password:e,keyData:t,onProgress:c}=a;if(!e&&!t)throw new y("PASSWORD_REQUIRED");try{c==null||c({phase:"deriving_key",progress:0});const i=await u(n);return c==null||c({phase:"deriving_key",progress:10}),t?await G(i,t,c):await H(i,e,c)}catch(i){throw i instanceof y?i:new y("ENCRYPTION_FAILED")}}async function H(n,a,e){const t=O(),c=R();e==null||e({phase:"deriving_key",progress:20});const i=await N(a,t);e==null||e({phase:"encrypting",progress:30});const E=await crypto.subtle.encrypt({name:p,iv:f(c)},i,n);e==null||e({phase:"encrypting",progress:90});const l=new Uint8Array(29+E.byteLength);return l[0]=1,l.set(t,1),l.set(c,17),l.set(new Uint8Array(E),29),e==null||e({phase:"complete",progress:100}),new Blob([l],{type:"application/octet-stream"})}async function G(n,a,e){const t=R();e==null||e({phase:"deriving_key",progress:20});const c=await h(a);e==null||e({phase:"encrypting",progress:30});const i=await crypto.subtle.encrypt({name:p,iv:f(t)},c,n);e==null||e({phase:"encrypting",progress:90});const E=new Uint8Array(13+i.byteLength);return E[0]=2,E.set(t,1),E.set(new Uint8Array(i),13),e==null||e({phase:"complete",progress:100}),new Blob([E],{type:"application/octet-stream"})}async function w(n,a){const{password:e,keyData:t,onProgress:c}=a;try{c==null||c({phase:"decrypting",progress:0});const i=new Uint8Array(await u(n));if(c==null||c({phase:"decrypting",progress:5}),i.length<1)throw new y("INVALID_ENCRYPTED_DATA");const E=i[0];if(E===1){if(!e)throw new y("PASSWORD_REQUIRED");return await b(i,e,c)}else if(E===2){if(!t)throw new y("KEYFILE_REQUIRED");return await C(i,t,c)}else throw new y("UNSUPPORTED_FORMAT")}catch(i){throw i instanceof y?i:new y("DECRYPTION_FAILED")}}async function b(n,a,e){if(n.length<45)throw new y("INVALID_ENCRYPTED_DATA");e==null||e({phase:"deriving_key",progress:10});const t=n.slice(1,17),c=n.slice(17,29),i=n.slice(29),E=await N(a,t);e==null||e({phase:"decrypting",progress:30});try{const l=await crypto.subtle.decrypt({name:p,iv:f(c)},E,i);return e==null||e({phase:"complete",progress:100}),new Blob([l])}catch{throw new y("INVALID_PASSWORD")}}async function C(n,a,e){if(n.length<29)throw new y("INVALID_ENCRYPTED_DATA");e==null||e({phase:"decrypting",progress:10});const t=n.slice(1,13),c=n.slice(13),i=await h(a);e==null||e({phase:"decrypting",progress:30});try{const E=await crypto.subtle.decrypt({name:p,iv:f(t)},i,c);return e==null||e({phase:"complete",progress:100}),new Blob([E])}catch{throw new y("INVALID_KEYFILE")}}async function k(n){const a=await u(n),e=new Uint8Array(a);if(e.length<1)return"unknown";const t=e[0];return t===1?"password":t===2?"keyfile":"unknown"}async function M(n){const a=await u(n),e=new Uint8Array(a);if(e.length<1)return!1;const t=e[0];return t===1&&e.length>=45||t===2&&e.length>=29}function U(){const n=T(32);return{version:1,algorithm:"AES-256-GCM",key:K(n.buffer),createdAt:new Date().toISOString()}}function v(n){try{const a=JSON.parse(n);return B(a)?a:null}catch{return null}}function B(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 V(n,a,e="key"){const t={version:1,algorithm:"AES-256-GCM",key:n,createdAt:new Date().toISOString()},c=JSON.stringify(t,null,2),i=new Blob([c],{type:"application/json"}),E=URL.createObjectURL(i),l=document.createElement("a");l.href=E,l.download=`${a}.${e}`,l.style.display="none",document.body.appendChild(l),l.click(),document.body.removeChild(l),URL.revokeObjectURL(E)}async function W(n){const a=s(n),e=await crypto.subtle.digest("SHA-256",a),t=new Uint8Array(e);return Array.from(t).map(c=>c.toString(16).padStart(2,"0")).join("")}async function Z(n,a){const{fileName:e,onProgress:t,...c}=a;try{t==null||t({phase:"downloading",progress:0});const i=await fetch(n);if(!i.ok)throw new y("DOWNLOAD_FAILED",`Download failed: ${i.status} ${i.statusText}`);const E=i.headers.get("content-length");let l;E&&i.body?l=await g(i.body,parseInt(E,10),o=>{t==null||t({phase:"downloading",progress:Math.round(o*50)})}):(t==null||t({phase:"downloading",progress:25}),l=await i.arrayBuffer()),t==null||t({phase:"downloading",progress:50});const _=await w(l,{...c,onProgress:o=>{const A=50+Math.round(o.progress*.45);t==null||t({phase:o.phase==="complete"?"complete":o.phase,progress:o.phase==="complete"?100:A})}});j(_,e),t==null||t({phase:"complete",progress:100})}catch(i){throw i instanceof y?i:new y("DOWNLOAD_FAILED")}}async function g(n,a,e){const t=n.getReader(),c=[];let i=0;for(;;){const{done:_,value:o}=await t.read();if(_)break;c.push(o),i+=o.length;const A=i/a;e(Math.min(A,1))}const E=new Uint8Array(i);let l=0;for(const _ of c)E.set(_,l),l+=_.length;return E.buffer}function j(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)}r.ALGORITHM=p,r.AUTH_TAG_LENGTH=16,r.CryptoError=y,r.ENCRYPTION_MARKER_KEYFILE=2,r.ENCRYPTION_MARKER_PASSWORD=1,r.HASH_ALGORITHM=I,r.IV_LENGTH=12,r.KEYFILE_KEY_LENGTH=32,r.KEY_LENGTH=256,r.MIN_ENCRYPTED_SIZE_KEYFILE=29,r.MIN_ENCRYPTED_SIZE_PASSWORD=45,r.PBKDF2_ITERATIONS=1e5,r.SALT_LENGTH=16,r.computeKeyFileHash=W,r.decryptFile=w,r.downloadAndDecrypt=Z,r.downloadKeyFile=V,r.encryptFile=F,r.generateKeyFile=U,r.generateRandomPassword=m,r.getEncryptionType=k,r.isCryptoError=D,r.isEncryptedFile=M,r.parseKeyFile=v,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})});
2
+ //# sourceMappingURL=index.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.js","sources":["../src/constants.ts","../src/errors.ts","../src/utils.ts","../src/encrypt.ts","../src/decrypt.ts","../src/detect.ts","../src/keyfile.ts","../src/download.ts"],"sourcesContent":["/**\n * Cryptographic constants used throughout the library.\n * These values follow industry best practices for AES-256-GCM.\n *\n * @module constants\n * @since 1.0.0\n */\n\n/**\n * Salt length in bytes for PBKDF2 key derivation.\n * 16 bytes (128 bits) provides sufficient randomness to prevent rainbow table attacks.\n */\nexport const SALT_LENGTH = 16;\n\n/**\n * Initialization vector length in bytes for AES-GCM.\n * 12 bytes (96 bits) is the recommended IV size for AES-GCM per NIST SP 800-38D.\n */\nexport const IV_LENGTH = 12;\n\n/**\n * AES key length in bits.\n * 256 bits provides the highest security level for AES.\n */\nexport const KEY_LENGTH = 256;\n\n/**\n * PBKDF2 iteration count for key derivation.\n * 100,000 iterations balance security and performance.\n * Higher values increase resistance to brute-force attacks.\n */\nexport const PBKDF2_ITERATIONS = 100_000;\n\n/**\n * Key file raw key length in bytes (256-bit).\n * Matches the AES-256 key size requirement.\n */\nexport const KEYFILE_KEY_LENGTH = 32;\n\n/**\n * Encryption marker byte for password-based encryption.\n * Used to identify the encryption method when decrypting.\n */\nexport const ENCRYPTION_MARKER_PASSWORD = 0x01;\n\n/**\n * Encryption marker byte for keyfile-based encryption.\n * Used to identify the encryption method when decrypting.\n */\nexport const ENCRYPTION_MARKER_KEYFILE = 0x02;\n\n/**\n * AES-GCM authentication tag length in bytes.\n * 16 bytes (128 bits) is the default and recommended tag size.\n */\nexport const AUTH_TAG_LENGTH = 16;\n\n/**\n * Minimum encrypted file size for password-based encryption.\n * Header (1 + 16 + 12 = 29 bytes) + Auth tag (16 bytes) = 45 bytes.\n */\nexport const MIN_ENCRYPTED_SIZE_PASSWORD = 1 + SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH;\n\n/**\n * Minimum encrypted file size for keyfile-based encryption.\n * Header (1 + 12 = 13 bytes) + Auth tag (16 bytes) = 29 bytes.\n */\nexport const MIN_ENCRYPTED_SIZE_KEYFILE = 1 + IV_LENGTH + AUTH_TAG_LENGTH;\n\n/**\n * Algorithm identifier string for AES-GCM.\n */\nexport const ALGORITHM = 'AES-GCM' as const;\n\n/**\n * Hash algorithm used in PBKDF2 key derivation.\n */\nexport const HASH_ALGORITHM = 'SHA-256' as const;\n","/**\n * Error handling for browser-file-crypto.\n *\n * @module errors\n * @since 1.0.0\n */\n\n/**\n * Error codes for CryptoError.\n *\n * @description\n * - `INVALID_INPUT`: Input is not a valid File, Blob, or ArrayBuffer\n * - `PASSWORD_REQUIRED`: Password is required but not provided\n * - `KEYFILE_REQUIRED`: Keyfile is required but not provided\n * - `INVALID_PASSWORD`: Decryption failed due to incorrect password\n * - `INVALID_KEYFILE`: Decryption failed due to incorrect keyfile\n * - `INVALID_ENCRYPTED_DATA`: Data is corrupted or not encrypted with this library\n * - `ENCRYPTION_FAILED`: Encryption operation failed\n * - `DECRYPTION_FAILED`: Decryption operation failed\n * - `DOWNLOAD_FAILED`: File download failed\n * - `UNSUPPORTED_FORMAT`: Encrypted file format is not supported\n */\nexport type CryptoErrorCode =\n | 'INVALID_INPUT'\n | 'PASSWORD_REQUIRED'\n | 'KEYFILE_REQUIRED'\n | 'INVALID_PASSWORD'\n | 'INVALID_KEYFILE'\n | 'INVALID_ENCRYPTED_DATA'\n | 'ENCRYPTION_FAILED'\n | 'DECRYPTION_FAILED'\n | 'DOWNLOAD_FAILED'\n | 'UNSUPPORTED_FORMAT';\n\n/**\n * Error messages for each error code.\n */\nconst ERROR_MESSAGES: Record<CryptoErrorCode, string> = {\n INVALID_INPUT: 'Input must be a File, Blob, or ArrayBuffer.',\n PASSWORD_REQUIRED: 'Password or keyfile is required for encryption.',\n KEYFILE_REQUIRED: 'Keyfile is required to decrypt this file.',\n INVALID_PASSWORD: 'Decryption failed: incorrect password.',\n INVALID_KEYFILE: 'Decryption failed: incorrect keyfile.',\n INVALID_ENCRYPTED_DATA: 'Invalid encrypted data: file may be corrupted.',\n ENCRYPTION_FAILED: 'Encryption failed.',\n DECRYPTION_FAILED: 'Decryption failed.',\n DOWNLOAD_FAILED: 'File download failed.',\n UNSUPPORTED_FORMAT: 'Unsupported encryption format.',\n};\n\n/**\n * Custom error class for crypto operations.\n *\n * @description\n * Provides structured error handling with error codes for programmatic handling.\n * Each error includes a code that can be used for i18n or specific error handling.\n *\n * @example\n * ```typescript\n * try {\n * await decryptFile(data, { password: 'wrong' });\n * } catch (error) {\n * if (error instanceof CryptoError) {\n * console.log(error.code); // 'INVALID_PASSWORD'\n * console.log(error.message); // 'Decryption failed: incorrect password.'\n *\n * switch (error.code) {\n * case 'INVALID_PASSWORD':\n * showPasswordError();\n * break;\n * case 'INVALID_ENCRYPTED_DATA':\n * showCorruptedFileError();\n * break;\n * }\n * }\n * }\n * ```\n *\n * @see {@link CryptoErrorCode} for all available error codes\n * @since 1.0.0\n */\nexport class CryptoError extends Error {\n /** Error code for programmatic handling */\n readonly code: CryptoErrorCode;\n\n /**\n * Creates a new CryptoError.\n *\n * @param code - Error code identifying the type of error\n * @param message - Optional custom message (defaults to predefined message)\n */\n constructor(code: CryptoErrorCode, message?: string) {\n super(message ?? ERROR_MESSAGES[code]);\n this.name = 'CryptoError';\n this.code = code;\n\n // Maintains proper stack trace in V8 environments (Chrome, Node.js)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, CryptoError);\n }\n }\n}\n\n/**\n * Type guard to check if an error is a CryptoError.\n *\n * @param error - The error to check\n * @returns True if the error is a CryptoError\n *\n * @example\n * ```typescript\n * try {\n * await decryptFile(data, options);\n * } catch (error) {\n * if (isCryptoError(error)) {\n * console.log(error.code);\n * }\n * }\n * ```\n */\nexport function isCryptoError(error: unknown): error is CryptoError {\n return error instanceof CryptoError;\n}\n","/**\n * Utility functions for browser-file-crypto.\n *\n * @module utils\n * @internal\n * @since 1.0.0\n */\n\nimport {\n SALT_LENGTH,\n IV_LENGTH,\n KEY_LENGTH,\n PBKDF2_ITERATIONS,\n ALGORITHM,\n HASH_ALGORITHM,\n} from './constants';\nimport { CryptoError } from './errors';\n\n/**\n * Normalizes input to ArrayBuffer for consistent processing.\n *\n * @description\n * Accepts File, Blob, or ArrayBuffer and converts to ArrayBuffer.\n * This ensures all encryption/decryption functions work with a consistent type.\n *\n * @param input - File, Blob, or ArrayBuffer to normalize\n * @returns Promise resolving to ArrayBuffer\n *\n * @throws {CryptoError} When input is not a valid type (INVALID_INPUT)\n *\n * @example\n * ```typescript\n * const file = document.querySelector('input').files[0];\n * const buffer = await normalizeInput(file);\n * ```\n *\n * @internal\n */\nexport async function normalizeInput(\n input: File | Blob | ArrayBuffer\n): Promise<ArrayBuffer> {\n if (input instanceof ArrayBuffer) {\n return input;\n }\n\n if (input instanceof Blob) {\n return input.arrayBuffer();\n }\n\n throw new CryptoError('INVALID_INPUT');\n}\n\n/**\n * Safely slices a TypedArray's buffer to get exact range.\n *\n * @description\n * Required because TypedArray.buffer may reference a larger ArrayBuffer\n * when the TypedArray is a view into a portion of the buffer.\n * This function ensures we get only the exact bytes we need.\n *\n * @param arr - Uint8Array to slice\n * @returns ArrayBuffer containing only the bytes from the Uint8Array\n *\n * @example\n * ```typescript\n * const iv = new Uint8Array(12);\n * const ivBuffer = sliceBuffer(iv);\n * // ivBuffer is guaranteed to be exactly 12 bytes\n * ```\n *\n * @internal\n */\nexport function sliceBuffer(arr: Uint8Array): ArrayBuffer {\n return (arr.buffer as ArrayBuffer).slice(arr.byteOffset, arr.byteOffset + arr.byteLength);\n}\n\n/**\n * Converts ArrayBuffer to base64 string.\n *\n * @description\n * Uses browser's native btoa() for encoding.\n * Handles binary data by converting each byte to a character.\n *\n * @param buffer - ArrayBuffer to encode\n * @returns Base64-encoded string\n *\n * @example\n * ```typescript\n * const key = crypto.getRandomValues(new Uint8Array(32));\n * const base64Key = arrayBufferToBase64(key.buffer);\n * ```\n *\n * @internal\n */\nexport function arrayBufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.byteLength; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n return btoa(binary);\n}\n\n/**\n * Converts base64 string to ArrayBuffer.\n *\n * @description\n * Uses browser's native atob() for decoding.\n * Returns an ArrayBuffer that can be used with Web Crypto API.\n *\n * @param base64 - Base64-encoded string to decode\n * @returns ArrayBuffer containing the decoded bytes\n *\n * @throws {DOMException} When base64 string is invalid\n *\n * @example\n * ```typescript\n * const keyBuffer = base64ToArrayBuffer(keyFile.key);\n * const cryptoKey = await crypto.subtle.importKey('raw', keyBuffer, ...);\n * ```\n *\n * @internal\n */\nexport function base64ToArrayBuffer(base64: string): ArrayBuffer {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\n/**\n * Derives an AES-256 encryption key from a password using PBKDF2.\n *\n * @description\n * Uses PBKDF2 with SHA-256 and 100,000 iterations to derive a 256-bit key.\n * The derived key is non-extractable for security.\n *\n * @param password - The user-provided password string\n * @param salt - 16-byte random salt for key derivation\n * @returns Promise resolving to a non-extractable CryptoKey\n *\n * @throws {Error} When key derivation fails\n *\n * @example\n * ```typescript\n * const salt = crypto.getRandomValues(new Uint8Array(16));\n * const key = await deriveKeyFromPassword('my-password', salt);\n * ```\n *\n * @internal\n */\nexport async function deriveKeyFromPassword(\n password: string,\n salt: Uint8Array\n): Promise<CryptoKey> {\n const encoder = new TextEncoder();\n\n // Import password as raw key material\n const keyMaterial = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(password),\n 'PBKDF2',\n false,\n ['deriveKey']\n );\n\n // Derive AES-256 key using PBKDF2\n return crypto.subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: sliceBuffer(salt),\n iterations: PBKDF2_ITERATIONS,\n hash: HASH_ALGORITHM,\n },\n keyMaterial,\n { name: ALGORITHM, length: KEY_LENGTH },\n false, // non-extractable for security\n ['encrypt', 'decrypt']\n );\n}\n\n/**\n * Imports raw key bytes for keyfile-based encryption.\n *\n * @description\n * Imports a 256-bit key directly from base64-encoded key data.\n * No key derivation is needed - the key is used as-is.\n * The imported key is non-extractable for security.\n *\n * @param keyData - Base64-encoded 256-bit key string\n * @returns Promise resolving to a non-extractable CryptoKey\n *\n * @throws {CryptoError} When key import fails (INVALID_KEYFILE)\n *\n * @example\n * ```typescript\n * const keyFile = generateKeyFile();\n * const cryptoKey = await importKeyFromKeyfile(keyFile.key);\n * ```\n *\n * @internal\n */\nexport async function importKeyFromKeyfile(keyData: string): Promise<CryptoKey> {\n try {\n const keyBuffer = base64ToArrayBuffer(keyData);\n\n return await crypto.subtle.importKey(\n 'raw',\n keyBuffer,\n { name: ALGORITHM, length: KEY_LENGTH },\n false, // non-extractable for security\n ['encrypt', 'decrypt']\n );\n } catch {\n throw new CryptoError('INVALID_KEYFILE');\n }\n}\n\n/**\n * Generates cryptographically secure random bytes.\n *\n * @description\n * Uses crypto.getRandomValues() for cryptographically secure randomness.\n *\n * @param length - Number of random bytes to generate\n * @returns Uint8Array containing random bytes\n *\n * @example\n * ```typescript\n * const salt = generateRandomBytes(SALT_LENGTH);\n * const iv = generateRandomBytes(IV_LENGTH);\n * ```\n *\n * @internal\n */\nexport function generateRandomBytes(length: number): Uint8Array {\n return crypto.getRandomValues(new Uint8Array(length));\n}\n\n/**\n * Generates a random salt for PBKDF2 key derivation.\n *\n * @returns 16-byte random salt\n *\n * @internal\n */\nexport function generateSalt(): Uint8Array {\n return generateRandomBytes(SALT_LENGTH);\n}\n\n/**\n * Generates a random IV for AES-GCM encryption.\n *\n * @returns 12-byte random IV\n *\n * @internal\n */\nexport function generateIV(): Uint8Array {\n return generateRandomBytes(IV_LENGTH);\n}\n\n/**\n * Default character set for random password generation.\n * Includes uppercase, lowercase, numbers, and special characters.\n */\nconst PASSWORD_CHARSET =\n 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';\n\n/**\n * Default password length.\n */\nconst DEFAULT_PASSWORD_LENGTH = 16;\n\n/**\n * Generates a cryptographically secure random password.\n *\n * @description\n * Creates a random password using crypto.getRandomValues() for\n * cryptographic randomness. The password includes:\n * - Uppercase letters (A-Z)\n * - Lowercase letters (a-z)\n * - Numbers (0-9)\n * - Special characters (!@#$%^&*)\n *\n * @param length - Password length (default: 16, min: 8, max: 128)\n * @returns Random password string\n *\n * @example\n * ```typescript\n * const password = generateRandomPassword();\n * console.log(password); // e.g., 'Kx9#mP2$vL5@nQ8!'\n *\n * const longPassword = generateRandomPassword(32);\n * console.log(longPassword.length); // 32\n * ```\n *\n * @since 1.0.0\n */\nexport function generateRandomPassword(length: number = DEFAULT_PASSWORD_LENGTH): string {\n // Clamp length to reasonable bounds\n const safeLength = Math.max(8, Math.min(128, length));\n\n const randomBytes = generateRandomBytes(safeLength);\n let password = '';\n\n for (let i = 0; i < safeLength; i++) {\n const index = randomBytes[i]! % PASSWORD_CHARSET.length;\n password += PASSWORD_CHARSET[index];\n }\n\n return password;\n}\n","/**\n * File encryption functions for browser-file-crypto.\n *\n * @module encrypt\n * @since 1.0.0\n */\n\nimport {\n SALT_LENGTH,\n IV_LENGTH,\n ENCRYPTION_MARKER_PASSWORD,\n ENCRYPTION_MARKER_KEYFILE,\n ALGORITHM,\n} from './constants';\nimport { CryptoError } from './errors';\nimport type { EncryptOptions } from './types';\nimport {\n normalizeInput,\n sliceBuffer,\n deriveKeyFromPassword,\n importKeyFromKeyfile,\n generateSalt,\n generateIV,\n} from './utils';\n\n/**\n * Encrypts a file using AES-256-GCM with password-based key derivation.\n *\n * @description\n * Uses PBKDF2 with 100,000 iterations to derive a 256-bit key from the password.\n * Each encryption generates a unique random salt (16 bytes) and IV (12 bytes).\n * The output format is: [marker(1)] + [salt(16)] + [iv(12)] + [ciphertext + auth tag].\n *\n * For keyfile-based encryption, the key is used directly without derivation.\n * The output format is: [marker(1)] + [iv(12)] + [ciphertext + auth tag].\n *\n * @param file - The file to encrypt (File, Blob, or ArrayBuffer)\n * @param options - Encryption options including password or keyData\n * @returns Promise resolving to encrypted Blob with 'application/octet-stream' MIME type\n *\n * @throws {CryptoError} When neither password nor keyData is provided (PASSWORD_REQUIRED)\n * @throws {CryptoError} When input is invalid (INVALID_INPUT)\n * @throws {CryptoError} When encryption fails (ENCRYPTION_FAILED)\n *\n * @example\n * ```typescript\n * // Password-based encryption\n * const encrypted = await encryptFile(file, {\n * password: 'my-secret',\n * onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)\n * });\n *\n * // Keyfile-based encryption\n * const keyFile = generateKeyFile();\n * const encrypted = await encryptFile(file, {\n * keyData: keyFile.key,\n * onProgress: ({ progress }) => updateProgressBar(progress)\n * });\n * ```\n *\n * @see {@link decryptFile} for decryption\n * @see {@link generateKeyFile} for creating keyfiles\n * @since 1.0.0\n */\nexport async function encryptFile(\n file: File | Blob | ArrayBuffer,\n options: EncryptOptions\n): Promise<Blob> {\n const { password, keyData, onProgress } = options;\n\n // Validate: either password or keyData must be provided\n if (!password && !keyData) {\n throw new CryptoError('PASSWORD_REQUIRED');\n }\n\n try {\n // Step 1: Normalize input to ArrayBuffer\n onProgress?.({ phase: 'deriving_key', progress: 0 });\n const data = await normalizeInput(file);\n onProgress?.({ phase: 'deriving_key', progress: 10 });\n\n // Branch based on encryption method\n if (keyData) {\n return await encryptWithKeyfile(data, keyData, onProgress);\n } else {\n return await encryptWithPassword(data, password!, onProgress);\n }\n } catch (error) {\n if (error instanceof CryptoError) {\n throw error;\n }\n throw new CryptoError('ENCRYPTION_FAILED');\n }\n}\n\n/**\n * Encrypts data using password-based key derivation.\n *\n * @internal\n */\nasync function encryptWithPassword(\n data: ArrayBuffer,\n password: string,\n onProgress?: EncryptOptions['onProgress']\n): Promise<Blob> {\n // Step 2: Generate random salt and IV\n const salt = generateSalt();\n const iv = generateIV();\n onProgress?.({ phase: 'deriving_key', progress: 20 });\n\n // Step 3: Derive key from password\n const key = await deriveKeyFromPassword(password, salt);\n onProgress?.({ phase: 'encrypting', progress: 30 });\n\n // Step 4: Encrypt with AES-GCM\n const ciphertext = await crypto.subtle.encrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n data\n );\n onProgress?.({ phase: 'encrypting', progress: 90 });\n\n // Step 5: Assemble output: marker + salt + iv + ciphertext\n const result = new Uint8Array(\n 1 + SALT_LENGTH + IV_LENGTH + ciphertext.byteLength\n );\n result[0] = ENCRYPTION_MARKER_PASSWORD;\n result.set(salt, 1);\n result.set(iv, 1 + SALT_LENGTH);\n result.set(new Uint8Array(ciphertext), 1 + SALT_LENGTH + IV_LENGTH);\n\n onProgress?.({ phase: 'complete', progress: 100 });\n\n return new Blob([result], { type: 'application/octet-stream' });\n}\n\n/**\n * Encrypts data using keyfile-based encryption.\n *\n * @internal\n */\nasync function encryptWithKeyfile(\n data: ArrayBuffer,\n keyData: string,\n onProgress?: EncryptOptions['onProgress']\n): Promise<Blob> {\n // Step 2: Generate random IV (no salt needed for keyfile)\n const iv = generateIV();\n onProgress?.({ phase: 'deriving_key', progress: 20 });\n\n // Step 3: Import key from keyfile\n const key = await importKeyFromKeyfile(keyData);\n onProgress?.({ phase: 'encrypting', progress: 30 });\n\n // Step 4: Encrypt with AES-GCM\n const ciphertext = await crypto.subtle.encrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n data\n );\n onProgress?.({ phase: 'encrypting', progress: 90 });\n\n // Step 5: Assemble output: marker + iv + ciphertext\n const result = new Uint8Array(1 + IV_LENGTH + ciphertext.byteLength);\n result[0] = ENCRYPTION_MARKER_KEYFILE;\n result.set(iv, 1);\n result.set(new Uint8Array(ciphertext), 1 + IV_LENGTH);\n\n onProgress?.({ phase: 'complete', progress: 100 });\n\n return new Blob([result], { type: 'application/octet-stream' });\n}\n","/**\n * File decryption functions for browser-file-crypto.\n *\n * @module decrypt\n * @since 1.0.0\n */\n\nimport {\n SALT_LENGTH,\n IV_LENGTH,\n ENCRYPTION_MARKER_PASSWORD,\n ENCRYPTION_MARKER_KEYFILE,\n MIN_ENCRYPTED_SIZE_PASSWORD,\n MIN_ENCRYPTED_SIZE_KEYFILE,\n ALGORITHM,\n} from './constants';\nimport { CryptoError } from './errors';\nimport type { DecryptOptions } from './types';\nimport {\n normalizeInput,\n sliceBuffer,\n deriveKeyFromPassword,\n importKeyFromKeyfile,\n} from './utils';\n\n/**\n * Decrypts a file that was encrypted with encryptFile.\n *\n * @description\n * Automatically detects whether the file was encrypted with password or keyfile\n * based on the marker byte, then decrypts accordingly.\n *\n * For password-encrypted files:\n * - Extracts salt and IV from header\n * - Derives key using PBKDF2\n * - Decrypts with AES-GCM\n *\n * For keyfile-encrypted files:\n * - Extracts IV from header\n * - Imports key directly\n * - Decrypts with AES-GCM\n *\n * @param encrypted - The encrypted data (Blob or ArrayBuffer)\n * @param options - Decryption options including password or keyData\n * @returns Promise resolving to decrypted Blob\n *\n * @throws {CryptoError} When password is required but not provided (PASSWORD_REQUIRED)\n * @throws {CryptoError} When keyfile is required but not provided (KEYFILE_REQUIRED)\n * @throws {CryptoError} When password is incorrect (INVALID_PASSWORD)\n * @throws {CryptoError} When keyfile is incorrect (INVALID_KEYFILE)\n * @throws {CryptoError} When data is corrupted (INVALID_ENCRYPTED_DATA)\n *\n * @example\n * ```typescript\n * // Password-based decryption\n * try {\n * const decrypted = await decryptFile(encryptedBlob, {\n * password: 'my-secret',\n * onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)\n * });\n * } catch (error) {\n * if (error instanceof CryptoError && error.code === 'INVALID_PASSWORD') {\n * console.log('Wrong password!');\n * }\n * }\n *\n * // Keyfile-based decryption\n * const decrypted = await decryptFile(encryptedBlob, {\n * keyData: keyFile.key\n * });\n * ```\n *\n * @see {@link encryptFile} for encryption\n * @see {@link getEncryptionType} for detecting encryption method\n * @since 1.0.0\n */\nexport async function decryptFile(\n encrypted: Blob | ArrayBuffer,\n options: DecryptOptions\n): Promise<Blob> {\n const { password, keyData, onProgress } = options;\n\n try {\n // Step 1: Normalize input to ArrayBuffer\n onProgress?.({ phase: 'decrypting', progress: 0 });\n const data = new Uint8Array(await normalizeInput(encrypted));\n onProgress?.({ phase: 'decrypting', progress: 5 });\n\n // Step 2: Check minimum size and read marker\n if (data.length < 1) {\n throw new CryptoError('INVALID_ENCRYPTED_DATA');\n }\n\n const marker = data[0];\n\n // Step 3: Branch based on encryption method\n if (marker === ENCRYPTION_MARKER_PASSWORD) {\n if (!password) {\n throw new CryptoError('PASSWORD_REQUIRED');\n }\n return await decryptWithPassword(data, password, onProgress);\n } else if (marker === ENCRYPTION_MARKER_KEYFILE) {\n if (!keyData) {\n throw new CryptoError('KEYFILE_REQUIRED');\n }\n return await decryptWithKeyfile(data, keyData, onProgress);\n } else {\n throw new CryptoError('UNSUPPORTED_FORMAT');\n }\n } catch (error) {\n if (error instanceof CryptoError) {\n throw error;\n }\n throw new CryptoError('DECRYPTION_FAILED');\n }\n}\n\n/**\n * Decrypts data that was encrypted with password.\n *\n * @internal\n */\nasync function decryptWithPassword(\n data: Uint8Array,\n password: string,\n onProgress?: DecryptOptions['onProgress']\n): Promise<Blob> {\n // Validate minimum size\n if (data.length < MIN_ENCRYPTED_SIZE_PASSWORD) {\n throw new CryptoError('INVALID_ENCRYPTED_DATA');\n }\n\n onProgress?.({ phase: 'deriving_key', progress: 10 });\n\n // Extract components: marker(1) + salt(16) + iv(12) + ciphertext\n const salt = data.slice(1, 1 + SALT_LENGTH);\n const iv = data.slice(1 + SALT_LENGTH, 1 + SALT_LENGTH + IV_LENGTH);\n const ciphertext = data.slice(1 + SALT_LENGTH + IV_LENGTH);\n\n // Derive key from password\n const key = await deriveKeyFromPassword(password, salt);\n onProgress?.({ phase: 'decrypting', progress: 30 });\n\n // Decrypt with AES-GCM\n try {\n const plaintext = await crypto.subtle.decrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n ciphertext\n );\n onProgress?.({ phase: 'complete', progress: 100 });\n\n return new Blob([plaintext]);\n } catch {\n throw new CryptoError('INVALID_PASSWORD');\n }\n}\n\n/**\n * Decrypts data that was encrypted with keyfile.\n *\n * @internal\n */\nasync function decryptWithKeyfile(\n data: Uint8Array,\n keyData: string,\n onProgress?: DecryptOptions['onProgress']\n): Promise<Blob> {\n // Validate minimum size\n if (data.length < MIN_ENCRYPTED_SIZE_KEYFILE) {\n throw new CryptoError('INVALID_ENCRYPTED_DATA');\n }\n\n onProgress?.({ phase: 'decrypting', progress: 10 });\n\n // Extract components: marker(1) + iv(12) + ciphertext\n const iv = data.slice(1, 1 + IV_LENGTH);\n const ciphertext = data.slice(1 + IV_LENGTH);\n\n // Import key from keyfile\n const key = await importKeyFromKeyfile(keyData);\n onProgress?.({ phase: 'decrypting', progress: 30 });\n\n // Decrypt with AES-GCM\n try {\n const plaintext = await crypto.subtle.decrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n ciphertext\n );\n onProgress?.({ phase: 'complete', progress: 100 });\n\n return new Blob([plaintext]);\n } catch {\n throw new CryptoError('INVALID_KEYFILE');\n }\n}\n","/**\n * Encryption type detection functions for browser-file-crypto.\n *\n * @module detect\n * @since 1.0.0\n */\n\nimport {\n ENCRYPTION_MARKER_PASSWORD,\n ENCRYPTION_MARKER_KEYFILE,\n MIN_ENCRYPTED_SIZE_PASSWORD,\n MIN_ENCRYPTED_SIZE_KEYFILE,\n} from './constants';\nimport type { EncryptionType } from './types';\nimport { normalizeInput } from './utils';\n\n/**\n * Detects the encryption type of encrypted data.\n *\n * @description\n * Reads the first byte (marker) of the encrypted data to determine\n * whether it was encrypted with a password or keyfile.\n *\n * - Marker 0x01: Password-based encryption\n * - Marker 0x02: Keyfile-based encryption\n * - Other: Unknown format\n *\n * @param data - The encrypted data (Blob or ArrayBuffer)\n * @returns Promise resolving to encryption type\n *\n * @example\n * ```typescript\n * const type = await getEncryptionType(encryptedBlob);\n *\n * switch (type) {\n * case 'password':\n * // Show password input\n * break;\n * case 'keyfile':\n * // Show keyfile picker\n * break;\n * case 'unknown':\n * // Show error: not encrypted with this library\n * break;\n * }\n * ```\n *\n * @see {@link isEncryptedFile} for simple encryption check\n * @since 1.0.0\n */\nexport async function getEncryptionType(\n data: Blob | ArrayBuffer\n): Promise<EncryptionType> {\n const buffer = await normalizeInput(data);\n const bytes = new Uint8Array(buffer);\n\n if (bytes.length < 1) {\n return 'unknown';\n }\n\n const marker = bytes[0];\n\n if (marker === ENCRYPTION_MARKER_PASSWORD) {\n return 'password';\n }\n\n if (marker === ENCRYPTION_MARKER_KEYFILE) {\n return 'keyfile';\n }\n\n return 'unknown';\n}\n\n/**\n * Checks if data appears to be encrypted with this library.\n *\n * @description\n * Performs a quick check to determine if the data was likely encrypted\n * with browser-file-crypto. This checks:\n * 1. The marker byte is valid (0x01 or 0x02)\n * 2. The data meets minimum size requirements\n *\n * Note: This is not a cryptographic verification. It only checks\n * the format markers and size constraints.\n *\n * @param data - The data to check (Blob or ArrayBuffer)\n * @returns Promise resolving to true if data appears to be encrypted\n *\n * @example\n * ```typescript\n * const file = event.target.files[0];\n * const isEncrypted = await isEncryptedFile(file);\n *\n * if (isEncrypted) {\n * showDecryptionUI();\n * } else {\n * showEncryptionUI();\n * }\n * ```\n *\n * @since 1.0.0\n */\nexport async function isEncryptedFile(data: Blob | ArrayBuffer): Promise<boolean> {\n const buffer = await normalizeInput(data);\n const bytes = new Uint8Array(buffer);\n\n if (bytes.length < 1) {\n return false;\n }\n\n const marker = bytes[0];\n\n // Check password-encrypted format\n if (\n marker === ENCRYPTION_MARKER_PASSWORD &&\n bytes.length >= MIN_ENCRYPTED_SIZE_PASSWORD\n ) {\n return true;\n }\n\n // Check keyfile-encrypted format\n if (\n marker === ENCRYPTION_MARKER_KEYFILE &&\n bytes.length >= MIN_ENCRYPTED_SIZE_KEYFILE\n ) {\n return true;\n }\n\n return false;\n}\n","/**\n * Keyfile generation and management functions for browser-file-crypto.\n *\n * @module keyfile\n * @since 1.0.0\n */\n\nimport { KEYFILE_KEY_LENGTH } from './constants';\nimport type { KeyFile } from './types';\nimport { arrayBufferToBase64, base64ToArrayBuffer, generateRandomBytes } from './utils';\n\n/**\n * Generates a new keyfile with a 256-bit random key.\n *\n * @description\n * Creates a cryptographically secure 256-bit (32 bytes) random key\n * using crypto.getRandomValues(). The key is returned as a KeyFile\n * object with metadata including version, algorithm, and creation timestamp.\n *\n * The generated key can be used directly with encryptFile() and decryptFile()\n * without any key derivation (unlike password-based encryption).\n *\n * @returns KeyFile object containing the generated key\n *\n * @example\n * ```typescript\n * const keyFile = generateKeyFile();\n * console.log(keyFile);\n * // {\n * // version: 1,\n * // algorithm: 'AES-256-GCM',\n * // key: 'base64-encoded-32-bytes...',\n * // createdAt: '2025-01-01T12:00:00.000Z'\n * // }\n *\n * // Use for encryption\n * const encrypted = await encryptFile(file, { keyData: keyFile.key });\n *\n * // Save keyfile for later\n * downloadKeyFile(keyFile.key, 'my-encryption-key');\n * ```\n *\n * @see {@link downloadKeyFile} for saving the keyfile\n * @see {@link parseKeyFile} for loading a saved keyfile\n * @since 1.0.0\n */\nexport function generateKeyFile(): KeyFile {\n const keyBytes = generateRandomBytes(KEYFILE_KEY_LENGTH);\n const key = arrayBufferToBase64(keyBytes.buffer as ArrayBuffer);\n\n return {\n version: 1,\n algorithm: 'AES-256-GCM',\n key,\n createdAt: new Date().toISOString(),\n };\n}\n\n/**\n * Parses a keyfile from JSON string content.\n *\n * @description\n * Validates and parses a JSON string into a KeyFile object.\n * Performs validation to ensure:\n * - Valid JSON format\n * - Required fields present (version, algorithm, key)\n * - Correct version (1)\n * - Correct algorithm ('AES-256-GCM')\n * - Key is a non-empty string\n *\n * @param content - JSON string content of the keyfile\n * @returns KeyFile object if valid, null if invalid\n *\n * @example\n * ```typescript\n * // From file input\n * const fileInput = document.querySelector('input[type=\"file\"]');\n * fileInput.addEventListener('change', async (e) => {\n * const file = e.target.files[0];\n * const content = await file.text();\n * const keyFile = parseKeyFile(content);\n *\n * if (keyFile) {\n * const decrypted = await decryptFile(encrypted, { keyData: keyFile.key });\n * } else {\n * console.error('Invalid keyfile format');\n * }\n * });\n * ```\n *\n * @see {@link generateKeyFile} for creating keyfiles\n * @since 1.0.0\n */\nexport function parseKeyFile(content: string): KeyFile | null {\n try {\n const parsed = JSON.parse(content) as unknown;\n\n // Type guard and validation\n if (!isValidKeyFile(parsed)) {\n return null;\n }\n\n return parsed;\n } catch {\n return null;\n }\n}\n\n/**\n * Validates if an object is a valid KeyFile.\n *\n * @internal\n */\nfunction isValidKeyFile(obj: unknown): obj is KeyFile {\n if (typeof obj !== 'object' || obj === null) {\n return false;\n }\n\n const candidate = obj as Record<string, unknown>;\n\n return (\n candidate.version === 1 &&\n candidate.algorithm === 'AES-256-GCM' &&\n typeof candidate.key === 'string' &&\n candidate.key.length > 0 &&\n typeof candidate.createdAt === 'string'\n );\n}\n\n/**\n * Downloads a keyfile as a JSON file with customizable extension.\n *\n * @description\n * Creates a downloadable JSON file containing the keyfile data.\n * The file extension can be customized (default: .key).\n *\n * Note: This function uses browser APIs (URL.createObjectURL, document.createElement)\n * and will not work in Node.js or Web Worker environments.\n *\n * @param keyData - Base64-encoded key string (from KeyFile.key)\n * @param fileName - Name for the downloaded file (without extension)\n * @param extension - File extension without dot (default: 'key')\n *\n * @example\n * ```typescript\n * const keyFile = generateKeyFile();\n *\n * // Downloads as 'my-secret-key.key' (default)\n * downloadKeyFile(keyFile.key, 'my-secret-key');\n *\n * // Downloads as 'my-secret-key.tfkey' (custom extension)\n * downloadKeyFile(keyFile.key, 'my-secret-key', 'tfkey');\n *\n * // The downloaded file contains:\n * // {\n * // \"version\": 1,\n * // \"algorithm\": \"AES-256-GCM\",\n * // \"key\": \"base64...\",\n * // \"createdAt\": \"2025-01-01T00:00:00.000Z\"\n * // }\n * ```\n *\n * @since 1.0.0\n */\nexport function downloadKeyFile(keyData: string, fileName: string, extension: string = 'key'): void {\n const keyFile: KeyFile = {\n version: 1,\n algorithm: 'AES-256-GCM',\n key: keyData,\n createdAt: new Date().toISOString(),\n };\n\n const json = JSON.stringify(keyFile, null, 2);\n const blob = new Blob([json], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n\n const link = document.createElement('a');\n link.href = url;\n link.download = `${fileName}.${extension}`;\n link.style.display = 'none';\n\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n\n URL.revokeObjectURL(url);\n}\n\n/**\n * Computes SHA-256 hash of a keyfile's key data.\n *\n * @description\n * Generates a SHA-256 hash of the key data for server-side verification.\n * This allows the server to verify that the client has the correct keyfile\n * without ever seeing the actual key.\n *\n * Use case: Store the hash on the server, then verify client-provided\n * hash matches before allowing access to encrypted content.\n *\n * @param keyData - Base64-encoded key string (from KeyFile.key)\n * @returns Promise resolving to hex-encoded SHA-256 hash\n *\n * @example\n * ```typescript\n * const keyFile = generateKeyFile();\n * const hash = await computeKeyFileHash(keyFile.key);\n * console.log(hash); // '3a7bd3e2c1f8...' (64 hex characters)\n *\n * // Send hash to server for storage\n * await fetch('/api/store-key-hash', {\n * method: 'POST',\n * body: JSON.stringify({ hash })\n * });\n *\n * // Later, verify keyfile by comparing hashes\n * const uploadedHash = await computeKeyFileHash(uploadedKeyFile.key);\n * const isValid = uploadedHash === storedHash;\n * ```\n *\n * @since 1.0.0\n */\nexport async function computeKeyFileHash(keyData: string): Promise<string> {\n const keyBuffer = base64ToArrayBuffer(keyData);\n const hashBuffer = await crypto.subtle.digest('SHA-256', keyBuffer);\n const hashArray = new Uint8Array(hashBuffer);\n\n // Convert to hex string\n return Array.from(hashArray)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","/**\n * Download and decrypt functions for browser-file-crypto.\n *\n * @module download\n * @since 1.0.0\n */\n\nimport { CryptoError } from './errors';\nimport type { DownloadDecryptOptions } from './types';\nimport { decryptFile } from './decrypt';\n\n/**\n * Downloads an encrypted file from URL, decrypts it, and saves to disk.\n *\n * @description\n * Combines file download, decryption, and save into a single operation.\n * Progress callback reports both download and decryption phases.\n *\n * Phases:\n * 1. `downloading` (0-50%): Fetching file from URL\n * 2. `deriving_key` (50-60%): Key derivation (password mode only)\n * 3. `decrypting` (60-95%): AES-GCM decryption\n * 4. `complete` (100%): File saved\n *\n * Note: This function uses browser APIs (fetch, URL.createObjectURL, document.createElement)\n * and will not work in Node.js environments.\n *\n * @param url - URL of the encrypted file to download\n * @param options - Options including password/keyData and fileName\n * @returns Promise that resolves when file is saved\n *\n * @throws {CryptoError} When download fails (DOWNLOAD_FAILED)\n * @throws {CryptoError} When decryption fails (see decryptFile errors)\n *\n * @example\n * ```typescript\n * await downloadAndDecrypt('https://example.com/secret.enc', {\n * password: 'my-secret',\n * fileName: 'document.pdf',\n * onProgress: ({ phase, progress }) => {\n * console.log(`${phase}: ${progress}%`);\n * // downloading: 25%\n * // downloading: 50%\n * // deriving_key: 55%\n * // decrypting: 80%\n * // complete: 100%\n * }\n * });\n * ```\n *\n * @see {@link decryptFile} for decryption only\n * @since 1.0.0\n */\nexport async function downloadAndDecrypt(\n url: string,\n options: DownloadDecryptOptions\n): Promise<void> {\n const { fileName, onProgress, ...decryptOptions } = options;\n\n try {\n // Phase 1: Download file\n onProgress?.({ phase: 'downloading', progress: 0 });\n\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new CryptoError(\n 'DOWNLOAD_FAILED',\n `Download failed: ${response.status} ${response.statusText}`\n );\n }\n\n // Track download progress if content-length is available\n const contentLength = response.headers.get('content-length');\n let encryptedData: ArrayBuffer;\n\n if (contentLength && response.body) {\n encryptedData = await downloadWithProgress(\n response.body,\n parseInt(contentLength, 10),\n (downloadProgress) => {\n // Map download progress to 0-50%\n onProgress?.({ phase: 'downloading', progress: Math.round(downloadProgress * 50) });\n }\n );\n } else {\n // Fallback: no progress tracking\n onProgress?.({ phase: 'downloading', progress: 25 });\n encryptedData = await response.arrayBuffer();\n }\n\n onProgress?.({ phase: 'downloading', progress: 50 });\n\n // Phase 2 & 3: Decrypt with progress mapping\n const decrypted = await decryptFile(encryptedData, {\n ...decryptOptions,\n onProgress: (progress) => {\n // Map decryption progress (0-100) to (50-95)\n const mappedProgress = 50 + Math.round(progress.progress * 0.45);\n onProgress?.({\n phase: progress.phase === 'complete' ? 'complete' : progress.phase,\n progress: progress.phase === 'complete' ? 100 : mappedProgress,\n });\n },\n });\n\n // Phase 4: Save file\n saveFile(decrypted, fileName);\n onProgress?.({ phase: 'complete', progress: 100 });\n } catch (error) {\n if (error instanceof CryptoError) {\n throw error;\n }\n throw new CryptoError('DOWNLOAD_FAILED');\n }\n}\n\n/**\n * Downloads with progress tracking using ReadableStream.\n *\n * @internal\n */\nasync function downloadWithProgress(\n body: ReadableStream<Uint8Array>,\n contentLength: number,\n onProgress: (progress: number) => void\n): Promise<ArrayBuffer> {\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let receivedLength = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) break;\n\n chunks.push(value);\n receivedLength += value.length;\n\n const progress = receivedLength / contentLength;\n onProgress(Math.min(progress, 1));\n }\n\n // Combine chunks into single ArrayBuffer\n const result = new Uint8Array(receivedLength);\n let position = 0;\n for (const chunk of chunks) {\n result.set(chunk, position);\n position += chunk.length;\n }\n\n return result.buffer;\n}\n\n/**\n * Saves a Blob as a file download.\n *\n * @internal\n */\nfunction saveFile(blob: Blob, fileName: string): void {\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n\n link.href = url;\n link.download = fileName;\n link.style.display = 'none';\n\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n\n URL.revokeObjectURL(url);\n}\n"],"names":["ALGORITHM","HASH_ALGORITHM","ERROR_MESSAGES","CryptoError","code","message","isCryptoError","error","normalizeInput","input","sliceBuffer","arr","arrayBufferToBase64","buffer","bytes","binary","i","base64ToArrayBuffer","base64","deriveKeyFromPassword","password","salt","encoder","keyMaterial","importKeyFromKeyfile","keyData","keyBuffer","generateRandomBytes","length","generateSalt","generateIV","PASSWORD_CHARSET","DEFAULT_PASSWORD_LENGTH","generateRandomPassword","safeLength","randomBytes","index","encryptFile","file","options","onProgress","data","encryptWithKeyfile","encryptWithPassword","iv","key","ciphertext","result","decryptFile","encrypted","marker","decryptWithPassword","decryptWithKeyfile","plaintext","getEncryptionType","isEncryptedFile","generateKeyFile","keyBytes","parseKeyFile","content","parsed","isValidKeyFile","obj","candidate","downloadKeyFile","fileName","extension","keyFile","json","blob","url","link","computeKeyFileHash","hashBuffer","hashArray","b","downloadAndDecrypt","decryptOptions","response","contentLength","encryptedData","downloadWithProgress","downloadProgress","decrypted","progress","mappedProgress","saveFile","body","reader","chunks","receivedLength","done","value","position","chunk"],"mappings":"yOAwEO,MAAMA,EAAY,UAKZC,EAAiB,UCxCxBC,EAAkD,CACtD,cAAe,8CACf,kBAAmB,kDACnB,iBAAkB,4CAClB,iBAAkB,yCAClB,gBAAiB,wCACjB,uBAAwB,iDACxB,kBAAmB,qBACnB,kBAAmB,qBACnB,gBAAiB,wBACjB,mBAAoB,gCACtB,EAiCO,MAAMC,UAAoB,KAAM,CAUrC,YAAYC,EAAuBC,EAAkB,CACnD,MAAMA,GAAWH,EAAeE,CAAI,CAAC,EACrC,KAAK,KAAO,cACZ,KAAK,KAAOA,EAGR,MAAM,mBACR,MAAM,kBAAkB,KAAMD,CAAW,CAE7C,CACF,CAmBO,SAASG,EAAcC,EAAsC,CAClE,OAAOA,aAAiBJ,CAC1B,CCpFA,eAAsBK,EACpBC,EACsB,CACtB,GAAIA,aAAiB,YACnB,OAAOA,EAGT,GAAIA,aAAiB,KACnB,OAAOA,EAAM,YAAA,EAGf,MAAM,IAAIN,EAAY,eAAe,CACvC,CAsBO,SAASO,EAAYC,EAA8B,CACxD,OAAQA,EAAI,OAAuB,MAAMA,EAAI,WAAYA,EAAI,WAAaA,EAAI,UAAU,CAC1F,CAoBO,SAASC,EAAoBC,EAA6B,CAC/D,MAAMC,EAAQ,IAAI,WAAWD,CAAM,EACnC,IAAIE,EAAS,GACb,QAASC,EAAI,EAAGA,EAAIF,EAAM,WAAYE,IACpCD,GAAU,OAAO,aAAaD,EAAME,CAAC,CAAE,EAEzC,OAAO,KAAKD,CAAM,CACpB,CAsBO,SAASE,EAAoBC,EAA6B,CAC/D,MAAMH,EAAS,KAAKG,CAAM,EACpBJ,EAAQ,IAAI,WAAWC,EAAO,MAAM,EAC1C,QAASC,EAAI,EAAGA,EAAID,EAAO,OAAQC,IACjCF,EAAME,CAAC,EAAID,EAAO,WAAWC,CAAC,EAEhC,OAAOF,EAAM,MACf,CAuBA,eAAsBK,EACpBC,EACAC,EACoB,CACpB,MAAMC,EAAU,IAAI,YAGdC,EAAc,MAAM,OAAO,OAAO,UACtC,MACAD,EAAQ,OAAOF,CAAQ,EACvB,SACA,GACA,CAAC,WAAW,CAAA,EAId,OAAO,OAAO,OAAO,UACnB,CACE,KAAM,SACN,KAAMV,EAAYW,CAAI,EACtB,WAAY,IACZ,KAAMpB,CAAA,EAERsB,EACA,CAAE,KAAMvB,EAAW,OAAQ,GAAA,EAC3B,GACA,CAAC,UAAW,SAAS,CAAA,CAEzB,CAuBA,eAAsBwB,EAAqBC,EAAqC,CAC9E,GAAI,CACF,MAAMC,EAAYT,EAAoBQ,CAAO,EAE7C,OAAO,MAAM,OAAO,OAAO,UACzB,MACAC,EACA,CAAE,KAAM1B,EAAW,OAAQ,GAAA,EAC3B,GACA,CAAC,UAAW,SAAS,CAAA,CAEzB,MAAQ,CACN,MAAM,IAAIG,EAAY,iBAAiB,CACzC,CACF,CAmBO,SAASwB,EAAoBC,EAA4B,CAC9D,OAAO,OAAO,gBAAgB,IAAI,WAAWA,CAAM,CAAC,CACtD,CASO,SAASC,GAA2B,CACzC,OAAOF,EAAoB,EAAW,CACxC,CASO,SAASG,GAAyB,CACvC,OAAOH,EAAoB,EAAS,CACtC,CAMA,MAAMI,EACJ,yEAKIC,EAA0B,GA2BzB,SAASC,EAAuBL,EAAiBI,EAAiC,CAEvF,MAAME,EAAa,KAAK,IAAI,EAAG,KAAK,IAAI,IAAKN,CAAM,CAAC,EAE9CO,EAAcR,EAAoBO,CAAU,EAClD,IAAId,EAAW,GAEf,QAASJ,EAAI,EAAGA,EAAIkB,EAAYlB,IAAK,CACnC,MAAMoB,EAAQD,EAAYnB,CAAC,EAAKe,EAAiB,OACjDX,GAAYW,EAAiBK,CAAK,CACpC,CAEA,OAAOhB,CACT,CCzPA,eAAsBiB,EACpBC,EACAC,EACe,CACf,KAAM,CAAE,SAAAnB,EAAU,QAAAK,EAAS,WAAAe,CAAA,EAAeD,EAG1C,GAAI,CAACnB,GAAY,CAACK,EAChB,MAAM,IAAItB,EAAY,mBAAmB,EAG3C,GAAI,CAEFqC,GAAA,MAAAA,EAAa,CAAE,MAAO,eAAgB,SAAU,IAChD,MAAMC,EAAO,MAAMjC,EAAe8B,CAAI,EAItC,OAHAE,GAAA,MAAAA,EAAa,CAAE,MAAO,eAAgB,SAAU,KAG5Cf,EACK,MAAMiB,EAAmBD,EAAMhB,EAASe,CAAU,EAElD,MAAMG,EAAoBF,EAAMrB,EAAWoB,CAAU,CAEhE,OAASjC,EAAO,CACd,MAAIA,aAAiBJ,EACbI,EAEF,IAAIJ,EAAY,mBAAmB,CAC3C,CACF,CAOA,eAAewC,EACbF,EACArB,EACAoB,EACe,CAEf,MAAMnB,EAAOQ,EAAA,EACPe,EAAKd,EAAA,EACXU,GAAA,MAAAA,EAAa,CAAE,MAAO,eAAgB,SAAU,KAGhD,MAAMK,EAAM,MAAM1B,EAAsBC,EAAUC,CAAI,EACtDmB,GAAA,MAAAA,EAAa,CAAE,MAAO,aAAc,SAAU,KAG9C,MAAMM,EAAa,MAAM,OAAO,OAAO,QACrC,CAAE,KAAM9C,EAAW,GAAIU,EAAYkC,CAAE,CAAA,EACrCC,EACAJ,CAAA,EAEFD,GAAA,MAAAA,EAAa,CAAE,MAAO,aAAc,SAAU,KAG9C,MAAMO,EAAS,IAAI,WACjB,GAA8BD,EAAW,UAAA,EAE3C,OAAAC,EAAO,CAAC,EAAI,EACZA,EAAO,IAAI1B,EAAM,CAAC,EAClB0B,EAAO,IAAIH,EAAI,EAAe,EAC9BG,EAAO,IAAI,IAAI,WAAWD,CAAU,EAAG,EAA2B,EAElEN,GAAA,MAAAA,EAAa,CAAE,MAAO,WAAY,SAAU,MAErC,IAAI,KAAK,CAACO,CAAM,EAAG,CAAE,KAAM,2BAA4B,CAChE,CAOA,eAAeL,EACbD,EACAhB,EACAe,EACe,CAEf,MAAMI,EAAKd,EAAA,EACXU,GAAA,MAAAA,EAAa,CAAE,MAAO,eAAgB,SAAU,KAGhD,MAAMK,EAAM,MAAMrB,EAAqBC,CAAO,EAC9Ce,GAAA,MAAAA,EAAa,CAAE,MAAO,aAAc,SAAU,KAG9C,MAAMM,EAAa,MAAM,OAAO,OAAO,QACrC,CAAE,KAAM9C,EAAW,GAAIU,EAAYkC,CAAE,CAAA,EACrCC,EACAJ,CAAA,EAEFD,GAAA,MAAAA,EAAa,CAAE,MAAO,aAAc,SAAU,KAG9C,MAAMO,EAAS,IAAI,WAAW,GAAgBD,EAAW,UAAU,EACnE,OAAAC,EAAO,CAAC,EAAI,EACZA,EAAO,IAAIH,EAAI,CAAC,EAChBG,EAAO,IAAI,IAAI,WAAWD,CAAU,EAAG,EAAa,EAEpDN,GAAA,MAAAA,EAAa,CAAE,MAAO,WAAY,SAAU,MAErC,IAAI,KAAK,CAACO,CAAM,EAAG,CAAE,KAAM,2BAA4B,CAChE,CC/FA,eAAsBC,EACpBC,EACAV,EACe,CACf,KAAM,CAAE,SAAAnB,EAAU,QAAAK,EAAS,WAAAe,CAAA,EAAeD,EAE1C,GAAI,CAEFC,GAAA,MAAAA,EAAa,CAAE,MAAO,aAAc,SAAU,IAC9C,MAAMC,EAAO,IAAI,WAAW,MAAMjC,EAAeyC,CAAS,CAAC,EAI3D,GAHAT,GAAA,MAAAA,EAAa,CAAE,MAAO,aAAc,SAAU,IAG1CC,EAAK,OAAS,EAChB,MAAM,IAAItC,EAAY,wBAAwB,EAGhD,MAAM+C,EAAST,EAAK,CAAC,EAGrB,GAAIS,IAAW,EAA4B,CACzC,GAAI,CAAC9B,EACH,MAAM,IAAIjB,EAAY,mBAAmB,EAE3C,OAAO,MAAMgD,EAAoBV,EAAMrB,EAAUoB,CAAU,CAC7D,SAAWU,IAAW,EAA2B,CAC/C,GAAI,CAACzB,EACH,MAAM,IAAItB,EAAY,kBAAkB,EAE1C,OAAO,MAAMiD,EAAmBX,EAAMhB,EAASe,CAAU,CAC3D,KACE,OAAM,IAAIrC,EAAY,oBAAoB,CAE9C,OAASI,EAAO,CACd,MAAIA,aAAiBJ,EACbI,EAEF,IAAIJ,EAAY,mBAAmB,CAC3C,CACF,CAOA,eAAegD,EACbV,EACArB,EACAoB,EACe,CAEf,GAAIC,EAAK,OAAS,GAChB,MAAM,IAAItC,EAAY,wBAAwB,EAGhDqC,GAAA,MAAAA,EAAa,CAAE,MAAO,eAAgB,SAAU,KAGhD,MAAMnB,EAAOoB,EAAK,MAAM,EAAG,EAAe,EACpCG,EAAKH,EAAK,MAAM,GAAiB,EAA2B,EAC5DK,EAAaL,EAAK,MAAM,EAA2B,EAGnDI,EAAM,MAAM1B,EAAsBC,EAAUC,CAAI,EACtDmB,GAAA,MAAAA,EAAa,CAAE,MAAO,aAAc,SAAU,KAG9C,GAAI,CACF,MAAMa,EAAY,MAAM,OAAO,OAAO,QACpC,CAAE,KAAMrD,EAAW,GAAIU,EAAYkC,CAAE,CAAA,EACrCC,EACAC,CAAA,EAEF,OAAAN,GAAA,MAAAA,EAAa,CAAE,MAAO,WAAY,SAAU,MAErC,IAAI,KAAK,CAACa,CAAS,CAAC,CAC7B,MAAQ,CACN,MAAM,IAAIlD,EAAY,kBAAkB,CAC1C,CACF,CAOA,eAAeiD,EACbX,EACAhB,EACAe,EACe,CAEf,GAAIC,EAAK,OAAS,GAChB,MAAM,IAAItC,EAAY,wBAAwB,EAGhDqC,GAAA,MAAAA,EAAa,CAAE,MAAO,aAAc,SAAU,KAG9C,MAAMI,EAAKH,EAAK,MAAM,EAAG,EAAa,EAChCK,EAAaL,EAAK,MAAM,EAAa,EAGrCI,EAAM,MAAMrB,EAAqBC,CAAO,EAC9Ce,GAAA,MAAAA,EAAa,CAAE,MAAO,aAAc,SAAU,KAG9C,GAAI,CACF,MAAMa,EAAY,MAAM,OAAO,OAAO,QACpC,CAAE,KAAMrD,EAAW,GAAIU,EAAYkC,CAAE,CAAA,EACrCC,EACAC,CAAA,EAEF,OAAAN,GAAA,MAAAA,EAAa,CAAE,MAAO,WAAY,SAAU,MAErC,IAAI,KAAK,CAACa,CAAS,CAAC,CAC7B,MAAQ,CACN,MAAM,IAAIlD,EAAY,iBAAiB,CACzC,CACF,CClJA,eAAsBmD,EACpBb,EACyB,CACzB,MAAM5B,EAAS,MAAML,EAAeiC,CAAI,EAClC3B,EAAQ,IAAI,WAAWD,CAAM,EAEnC,GAAIC,EAAM,OAAS,EACjB,MAAO,UAGT,MAAMoC,EAASpC,EAAM,CAAC,EAEtB,OAAIoC,IAAW,EACN,WAGLA,IAAW,EACN,UAGF,SACT,CA+BA,eAAsBK,EAAgBd,EAA4C,CAChF,MAAM5B,EAAS,MAAML,EAAeiC,CAAI,EAClC3B,EAAQ,IAAI,WAAWD,CAAM,EAEnC,GAAIC,EAAM,OAAS,EACjB,MAAO,GAGT,MAAMoC,EAASpC,EAAM,CAAC,EAWtB,OAPEoC,IAAW,GACXpC,EAAM,QAAU,IAOhBoC,IAAW,GACXpC,EAAM,QAAU,EAMpB,CCnFO,SAAS0C,GAA2B,CACzC,MAAMC,EAAW9B,EAAoB,EAAkB,EAGvD,MAAO,CACL,QAAS,EACT,UAAW,cACX,IALUf,EAAoB6C,EAAS,MAAqB,EAM5D,UAAW,IAAI,KAAA,EAAO,YAAA,CAAY,CAEtC,CAqCO,SAASC,EAAaC,EAAiC,CAC5D,GAAI,CACF,MAAMC,EAAS,KAAK,MAAMD,CAAO,EAGjC,OAAKE,EAAeD,CAAM,EAInBA,EAHE,IAIX,MAAQ,CACN,OAAO,IACT,CACF,CAOA,SAASC,EAAeC,EAA8B,CACpD,GAAI,OAAOA,GAAQ,UAAYA,IAAQ,KACrC,MAAO,GAGT,MAAMC,EAAYD,EAElB,OACEC,EAAU,UAAY,GACtBA,EAAU,YAAc,eACxB,OAAOA,EAAU,KAAQ,UACzBA,EAAU,IAAI,OAAS,GACvB,OAAOA,EAAU,WAAc,QAEnC,CAqCO,SAASC,EAAgBvC,EAAiBwC,EAAkBC,EAAoB,MAAa,CAClG,MAAMC,EAAmB,CACvB,QAAS,EACT,UAAW,cACX,IAAK1C,EACL,UAAW,IAAI,KAAA,EAAO,YAAA,CAAY,EAG9B2C,EAAO,KAAK,UAAUD,EAAS,KAAM,CAAC,EACtCE,EAAO,IAAI,KAAK,CAACD,CAAI,EAAG,CAAE,KAAM,mBAAoB,EACpDE,EAAM,IAAI,gBAAgBD,CAAI,EAE9BE,EAAO,SAAS,cAAc,GAAG,EACvCA,EAAK,KAAOD,EACZC,EAAK,SAAW,GAAGN,CAAQ,IAAIC,CAAS,GACxCK,EAAK,MAAM,QAAU,OAErB,SAAS,KAAK,YAAYA,CAAI,EAC9BA,EAAK,MAAA,EACL,SAAS,KAAK,YAAYA,CAAI,EAE9B,IAAI,gBAAgBD,CAAG,CACzB,CAmCA,eAAsBE,EAAmB/C,EAAkC,CACzE,MAAMC,EAAYT,EAAoBQ,CAAO,EACvCgD,EAAa,MAAM,OAAO,OAAO,OAAO,UAAW/C,CAAS,EAC5DgD,EAAY,IAAI,WAAWD,CAAU,EAG3C,OAAO,MAAM,KAAKC,CAAS,EACxB,IAAKC,GAAMA,EAAE,SAAS,EAAE,EAAE,SAAS,EAAG,GAAG,CAAC,EAC1C,KAAK,EAAE,CACZ,CCjLA,eAAsBC,EACpBN,EACA/B,EACe,CACf,KAAM,CAAE,SAAA0B,EAAU,WAAAzB,EAAY,GAAGqC,GAAmBtC,EAEpD,GAAI,CAEFC,GAAA,MAAAA,EAAa,CAAE,MAAO,cAAe,SAAU,IAE/C,MAAMsC,EAAW,MAAM,MAAMR,CAAG,EAEhC,GAAI,CAACQ,EAAS,GACZ,MAAM,IAAI3E,EACR,kBACA,oBAAoB2E,EAAS,MAAM,IAAIA,EAAS,UAAU,EAAA,EAK9D,MAAMC,EAAgBD,EAAS,QAAQ,IAAI,gBAAgB,EAC3D,IAAIE,EAEAD,GAAiBD,EAAS,KAC5BE,EAAgB,MAAMC,EACpBH,EAAS,KACT,SAASC,EAAe,EAAE,EACzBG,GAAqB,CAEpB1C,GAAA,MAAAA,EAAa,CAAE,MAAO,cAAe,SAAU,KAAK,MAAM0C,EAAmB,EAAE,GACjF,CAAA,GAIF1C,GAAA,MAAAA,EAAa,CAAE,MAAO,cAAe,SAAU,KAC/CwC,EAAgB,MAAMF,EAAS,YAAA,GAGjCtC,GAAA,MAAAA,EAAa,CAAE,MAAO,cAAe,SAAU,KAG/C,MAAM2C,EAAY,MAAMnC,EAAYgC,EAAe,CACjD,GAAGH,EACH,WAAaO,GAAa,CAExB,MAAMC,EAAiB,GAAK,KAAK,MAAMD,EAAS,SAAW,GAAI,EAC/D5C,GAAA,MAAAA,EAAa,CACX,MAAO4C,EAAS,QAAU,WAAa,WAAaA,EAAS,MAC7D,SAAUA,EAAS,QAAU,WAAa,IAAMC,CAAA,EAEpD,CAAA,CACD,EAGDC,EAASH,EAAWlB,CAAQ,EAC5BzB,GAAA,MAAAA,EAAa,CAAE,MAAO,WAAY,SAAU,KAC9C,OAASjC,EAAO,CACd,MAAIA,aAAiBJ,EACbI,EAEF,IAAIJ,EAAY,iBAAiB,CACzC,CACF,CAOA,eAAe8E,EACbM,EACAR,EACAvC,EACsB,CACtB,MAAMgD,EAASD,EAAK,UAAA,EACdE,EAAuB,CAAA,EAC7B,IAAIC,EAAiB,EAErB,OAAa,CACX,KAAM,CAAE,KAAAC,EAAM,MAAAC,CAAA,EAAU,MAAMJ,EAAO,KAAA,EAErC,GAAIG,EAAM,MAEVF,EAAO,KAAKG,CAAK,EACjBF,GAAkBE,EAAM,OAExB,MAAMR,EAAWM,EAAiBX,EAClCvC,EAAW,KAAK,IAAI4C,EAAU,CAAC,CAAC,CAClC,CAGA,MAAMrC,EAAS,IAAI,WAAW2C,CAAc,EAC5C,IAAIG,EAAW,EACf,UAAWC,KAASL,EAClB1C,EAAO,IAAI+C,EAAOD,CAAQ,EAC1BA,GAAYC,EAAM,OAGpB,OAAO/C,EAAO,MAChB,CAOA,SAASuC,EAASjB,EAAYJ,EAAwB,CACpD,MAAMK,EAAM,IAAI,gBAAgBD,CAAI,EAC9BE,EAAO,SAAS,cAAc,GAAG,EAEvCA,EAAK,KAAOD,EACZC,EAAK,SAAWN,EAChBM,EAAK,MAAM,QAAU,OAErB,SAAS,KAAK,YAAYA,CAAI,EAC9BA,EAAK,MAAA,EACL,SAAS,KAAK,YAAYA,CAAI,EAE9B,IAAI,gBAAgBD,CAAG,CACzB"}