@livenetworks/ashlar 2.0.7 → 2.0.8

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.
Files changed (207) hide show
  1. package/LICENSE +641 -641
  2. package/components/COMPONENTS.md +1116 -1116
  3. package/components/index.js +81 -81
  4. package/components/ln-accordion/ln-accordion-dev.scss +8 -8
  5. package/components/ln-accordion/ln-accordion.js +1 -1
  6. package/components/ln-accordion/ln-accordion.schema.json +30 -30
  7. package/components/ln-ajax/ln-ajax-dev.scss +8 -8
  8. package/components/ln-ajax/ln-ajax.js +1 -1
  9. package/components/ln-ajax/ln-ajax.schema.json +30 -30
  10. package/components/ln-api-connector/ln-api-connector.js +1 -1
  11. package/components/ln-api-connector/ln-api-connector.schema.json +96 -96
  12. package/components/ln-api-connector/src/ln-api-connector.js +1 -0
  13. package/components/ln-api-queue/ln-api-queue.js +1 -1
  14. package/components/ln-api-queue/ln-api-queue.schema.json +34 -34
  15. package/components/ln-api-queue/src/ln-api-queue.js +3 -13
  16. package/components/ln-autoresize/ln-autoresize-dev.scss +8 -8
  17. package/components/ln-autoresize/ln-autoresize.js +1 -1
  18. package/components/ln-autoresize/ln-autoresize.schema.json +22 -22
  19. package/components/ln-autosave/ln-autosave-dev.scss +13 -13
  20. package/components/ln-autosave/ln-autosave.js +1 -1
  21. package/components/ln-autosave/ln-autosave.schema.json +48 -48
  22. package/components/ln-autosave/src/autosave-model.js +34 -34
  23. package/components/ln-chart/ln-chart.js +1 -1
  24. package/components/ln-chart/ln-chart.schema.json +146 -146
  25. package/components/ln-chart/src/chart-model.js +154 -154
  26. package/components/ln-circular-progress/ln-circular-progress.js +1 -1
  27. package/components/ln-circular-progress/ln-circular-progress.schema.json +42 -42
  28. package/components/ln-confirm/ln-confirm-dev.scss +24 -24
  29. package/components/ln-confirm/ln-confirm.js +1 -1
  30. package/components/ln-confirm/ln-confirm.schema.json +66 -66
  31. package/components/ln-confirm/src/ln-confirm.js +178 -178
  32. package/components/ln-core/crypto.js +286 -265
  33. package/components/ln-core/dom.js +115 -115
  34. package/components/ln-core/form.js +136 -136
  35. package/components/ln-core/helpers.js +33 -33
  36. package/components/ln-core/http.js +48 -48
  37. package/components/ln-core/index.js +1 -1
  38. package/components/ln-core/ln-core.schema.json +109 -109
  39. package/components/ln-core/locale.js +55 -55
  40. package/components/ln-core/matching.js +78 -78
  41. package/components/ln-core/number.js +125 -125
  42. package/components/ln-core/template.js +206 -206
  43. package/components/ln-couchdb-connector/ln-couchdb-connector.js +1 -1
  44. package/components/ln-couchdb-connector/ln-couchdb-connector.schema.json +50 -50
  45. package/components/ln-couchdb-connector/src/ln-couchdb-connector.js +1 -0
  46. package/components/ln-data-coordinator/README.md +9 -38
  47. package/components/ln-data-coordinator/ln-data-coordinator-dev.scss +8 -8
  48. package/components/ln-data-coordinator/ln-data-coordinator.js +1 -1
  49. package/components/ln-data-coordinator/ln-data-coordinator.schema.json +16 -16
  50. package/components/ln-data-coordinator/src/data-read-policy.js +0 -7
  51. package/components/ln-data-coordinator/src/ln-data-coordinator.js +428 -1308
  52. package/components/ln-data-store/ln-data-store-dev.scss +13 -13
  53. package/components/ln-data-store/ln-data-store.js +1 -1
  54. package/components/ln-data-store/ln-data-store.schema.json +84 -84
  55. package/components/ln-data-store/src/data-store-model.js +184 -184
  56. package/components/ln-data-store/src/ln-data-store.js +1 -1
  57. package/components/ln-date/ln-date.js +1 -1
  58. package/components/ln-date/ln-date.schema.json +92 -92
  59. package/components/ln-date/src/date-model.js +186 -186
  60. package/components/ln-debug/ln-debug.js +1 -1
  61. package/components/ln-debug/ln-debug.schema.json +28 -28
  62. package/components/ln-debug/src/console-sink.js +19 -19
  63. package/components/ln-debug/src/gate.js +95 -95
  64. package/components/ln-debug/src/generated-attributes.js +2 -2
  65. package/components/ln-dropdown/ln-dropdown-dev.scss +13 -13
  66. package/components/ln-dropdown/ln-dropdown.js +1 -1
  67. package/components/ln-dropdown/ln-dropdown.schema.json +90 -90
  68. package/components/ln-editor/ln-editor.js +1 -1
  69. package/components/ln-editor/ln-editor.schema.json +58 -58
  70. package/components/ln-external-links/ln-external-links.js +1 -1
  71. package/components/ln-external-links/ln-external-links.schema.json +18 -18
  72. package/components/ln-fill/ln-fill.js +1 -1
  73. package/components/ln-fill/ln-fill.schema.json +23 -23
  74. package/components/ln-filter/ln-filter.js +1 -1
  75. package/components/ln-filter/ln-filter.schema.json +153 -153
  76. package/components/ln-filter/src/filter-model.js +185 -185
  77. package/components/ln-form/ln-form-dev.scss +8 -8
  78. package/components/ln-form/ln-form.js +1 -1
  79. package/components/ln-form/ln-form.schema.json +45 -45
  80. package/components/ln-http/ln-http.js +1 -1
  81. package/components/ln-http/ln-http.schema.json +6 -6
  82. package/components/ln-icon/ln-icon.schema.json +6 -6
  83. package/components/ln-include/ln-include.js +1 -1
  84. package/components/ln-include/ln-include.schema.json +23 -23
  85. package/components/ln-key/ln-key-dev.scss +18 -18
  86. package/components/ln-key/ln-key.js +1 -1
  87. package/components/ln-key/ln-key.schema.json +56 -56
  88. package/components/ln-key/src/ln-key.js +10 -10
  89. package/components/ln-link/ln-link-dev.scss +8 -8
  90. package/components/ln-link/ln-link.js +1 -1
  91. package/components/ln-link/ln-link.schema.json +22 -22
  92. package/components/ln-list/README.md +152 -152
  93. package/components/ln-list/ln-list-dev.scss +8 -8
  94. package/components/ln-list/ln-list.js +1 -1
  95. package/components/ln-list/ln-list.schema.json +166 -166
  96. package/components/ln-modal/ln-modal.js +1 -1
  97. package/components/ln-modal/ln-modal.schema.json +65 -65
  98. package/components/ln-nav/ln-nav.js +1 -1
  99. package/components/ln-nav/ln-nav.schema.json +25 -25
  100. package/components/ln-number/ln-number.js +1 -1
  101. package/components/ln-number/ln-number.schema.json +63 -63
  102. package/components/ln-number/src/number-model.js +31 -31
  103. package/components/ln-obfuscator/README.md +60 -60
  104. package/components/ln-obfuscator/ln-obfuscator.js +1 -1
  105. package/components/ln-obfuscator/ln-obfuscator.schema.json +49 -49
  106. package/components/ln-obfuscator/src/ln-obfuscator.js +159 -159
  107. package/components/ln-obfuscator/src/obfuscator-model.js +250 -250
  108. package/components/ln-options/ln-options.js +1 -1
  109. package/components/ln-options/ln-options.schema.json +41 -41
  110. package/components/ln-persist/README.md +151 -151
  111. package/components/ln-persist/ln-persist.js +1 -1
  112. package/components/ln-persist/ln-persist.schema.json +28 -28
  113. package/components/ln-persist/src/ln-persist.js +83 -83
  114. package/components/ln-picklist/README.md +230 -230
  115. package/components/ln-picklist/ln-picklist-dev.scss +22 -22
  116. package/components/ln-picklist/ln-picklist.js +1 -1
  117. package/components/ln-picklist/ln-picklist.schema.json +49 -49
  118. package/components/ln-picklist/src/ln-picklist.js +228 -228
  119. package/components/ln-popover/ln-popover-dev.scss +13 -13
  120. package/components/ln-popover/ln-popover.js +1 -1
  121. package/components/ln-popover/ln-popover.schema.json +82 -82
  122. package/components/ln-progress/ln-progress-dev.scss +8 -8
  123. package/components/ln-progress/ln-progress.js +1 -1
  124. package/components/ln-progress/ln-progress.schema.json +40 -40
  125. package/components/ln-router/ln-router.js +1 -1
  126. package/components/ln-router/ln-router.schema.json +76 -76
  127. package/components/ln-scroll/README.md +45 -45
  128. package/components/ln-scroll/ln-scroll.js +1 -1
  129. package/components/ln-scroll/ln-scroll.schema.json +85 -85
  130. package/components/ln-scroll/src/ln-scroll.js +150 -150
  131. package/components/ln-search/README.md +188 -188
  132. package/components/ln-search/ln-search.js +1 -1
  133. package/components/ln-search/ln-search.schema.json +103 -103
  134. package/components/ln-slug/ln-slug.js +1 -1
  135. package/components/ln-slug/ln-slug.schema.json +23 -23
  136. package/components/ln-sort/ln-sort.js +1 -1
  137. package/components/ln-sort/ln-sort.schema.json +104 -104
  138. package/components/ln-sort/src/ln-sort.js +321 -321
  139. package/components/ln-sort/src/sort-model.js +117 -117
  140. package/components/ln-sortable/ln-sortable-dev.scss +13 -13
  141. package/components/ln-sortable/ln-sortable.js +1 -1
  142. package/components/ln-sortable/ln-sortable.schema.json +36 -36
  143. package/components/ln-stat/ln-stat-dev.scss +8 -8
  144. package/components/ln-stat/ln-stat.js +1 -1
  145. package/components/ln-stat/ln-stat.schema.json +32 -32
  146. package/components/ln-stat/src/stat-model.js +28 -28
  147. package/components/ln-table/ln-table.js +1 -1
  148. package/components/ln-table/ln-table.schema.json +211 -211
  149. package/components/ln-table/src/table-model.js +98 -98
  150. package/components/ln-table-coordinator/ln-table-coordinator.js +1 -1
  151. package/components/ln-table-coordinator/ln-table-coordinator.schema.json +93 -93
  152. package/components/ln-tabs/ln-tabs.js +1 -1
  153. package/components/ln-tabs/ln-tabs.schema.json +75 -75
  154. package/components/ln-tabs/src/ln-tabs.js +208 -208
  155. package/components/ln-tabs/src/tabs-model.js +82 -82
  156. package/components/ln-time/ln-time-dev.scss +13 -13
  157. package/components/ln-time/ln-time.js +1 -1
  158. package/components/ln-time/ln-time.schema.json +38 -38
  159. package/components/ln-toast/ln-toast.js +1 -1
  160. package/components/ln-toast/ln-toast.schema.json +65 -65
  161. package/components/ln-toggle/ln-toggle.js +1 -1
  162. package/components/ln-toggle/ln-toggle.schema.json +52 -52
  163. package/components/ln-tooltip/ln-tooltip.js +1 -1
  164. package/components/ln-tooltip/ln-tooltip.schema.json +86 -86
  165. package/components/ln-translations/ln-translations.js +1 -1
  166. package/components/ln-translations/ln-translations.schema.json +120 -120
  167. package/components/ln-ui-coordinator/README.md +62 -62
  168. package/components/ln-ui-coordinator/ln-ui-coordinator.js +1 -1
  169. package/components/ln-ui-coordinator/ln-ui-coordinator.schema.json +28 -28
  170. package/components/ln-ui-coordinator/src/ln-ui-coordinator.js +140 -140
  171. package/components/ln-upload/ln-upload.js +1 -1
  172. package/components/ln-upload/ln-upload.schema.json +193 -193
  173. package/components/ln-upload/src/upload-model.js +78 -78
  174. package/components/ln-validate/ln-validate.js +1 -1
  175. package/components/ln-validate/ln-validate.schema.json +50 -50
  176. package/components/ln-validate/src/validate-model.js +64 -64
  177. package/components/ln-websocket-connector/README.md +166 -166
  178. package/components/ln-websocket-connector/ln-websocket-connector.js +1 -1
  179. package/components/ln-websocket-connector/ln-websocket-connector.schema.json +29 -29
  180. package/components/ln-websocket-connector/src/ln-websocket-connector.js +283 -279
  181. package/ln-ashlar.html-data.json +7 -8
  182. package/package.json +84 -84
  183. package/theme/brand.css +73 -73
  184. package/theme/components/_confirm.scss +6 -6
  185. package/theme/components/_picklist.scss +3 -3
  186. package/theme/config/_palette.scss +274 -274
  187. package/theme/config/_theme.scss +292 -292
  188. package/theme/config/_tokens.scss +285 -285
  189. package/theme/config/mixins/_index.scss +62 -62
  190. package/theme/config/mixins/_picklist.scss +66 -66
  191. package/theme/ln-ashlar-dev.scss +66 -66
  192. package/theme/ln-ashlar-landing.scss +56 -56
  193. package/theme/ln-ashlar-theme.scss +63 -63
  194. package/theme/presentation/_bento.scss +151 -151
  195. package/theme/presentation/_cta.scss +111 -111
  196. package/theme/presentation/_effects.scss +216 -216
  197. package/theme/presentation/_features.scss +108 -108
  198. package/theme/presentation/_footer.scss +129 -129
  199. package/theme/presentation/_hero.scss +158 -158
  200. package/theme/presentation/_layout.scss +170 -170
  201. package/theme/presentation/_navbar.scss +140 -140
  202. package/theme/presentation/_pricing.scss +262 -262
  203. package/theme/presentation/_stats.scss +87 -87
  204. package/theme/presentation/_testimonials.scss +124 -124
  205. package/theme/presentation/_tokens.scss +39 -39
  206. package/theme/presentation/_typography.scss +163 -163
  207. package/web-types.json +12 -12
@@ -1,265 +1,286 @@
1
- // Web Crypto API Reusable Cryptographic Helpers
2
- let _cryptoKey = null;
3
-
4
- const DEFAULT_SALT = 'ln-ashlar:storage-salt:v1';
5
- const CHUNK_SIZE = 0x8000; // 32KB chunks for stack-safe Base64 conversion
6
-
7
- /**
8
- * Encodes a Uint8Array to a Base64 string in chunks to prevent call-stack overflow on large payloads.
9
- * @param {Uint8Array} bytes
10
- * @returns {string}
11
- */
12
- export function uint8ToBase64(bytes) {
13
- let binary = '';
14
- const len = bytes.byteLength;
15
- for (let i = 0; i < len; i += CHUNK_SIZE) {
16
- binary += String.fromCharCode.apply(
17
- null,
18
- bytes.subarray(i, Math.min(i + CHUNK_SIZE, len))
19
- );
20
- }
21
- return btoa(binary);
22
- }
23
-
24
- /**
25
- * Decodes a Base64 string to a Uint8Array.
26
- * @param {string} base64
27
- * @returns {Uint8Array}
28
- */
29
- export function base64ToUint8(base64) {
30
- const binary = atob(base64);
31
- const len = binary.length;
32
- const bytes = new Uint8Array(len);
33
- for (let i = 0; i < len; i++) {
34
- bytes[i] = binary.charCodeAt(i);
35
- }
36
- return bytes;
37
- }
38
-
39
- /**
40
- * Resolves key and options from overloaded arguments.
41
- * Ensures an options object like { silent: true } is never passed to WebCrypto as a CryptoKey.
42
- * @private
43
- */
44
- function _resolveKeyAndOptions(keyOrOptions, maybeOptions) {
45
- let key = _cryptoKey;
46
- let options = {};
47
-
48
- if (typeof CryptoKey !== 'undefined' && keyOrOptions instanceof CryptoKey) {
49
- key = keyOrOptions;
50
- if (maybeOptions && typeof maybeOptions === 'object') {
51
- options = maybeOptions;
52
- }
53
- } else if (keyOrOptions && typeof keyOrOptions === 'object') {
54
- options = keyOrOptions;
55
- if (typeof CryptoKey !== 'undefined' && options.key instanceof CryptoKey) {
56
- key = options.key;
57
- }
58
- }
59
-
60
- return { key, options };
61
- }
62
-
63
- /**
64
- * Derives a 256-bit AES-GCM CryptoKey using PBKDF2 or SHA-256.
65
- * @param {string} secretString
66
- * @param {Object} [options]
67
- * @param {'pbkdf2'|'sha256'} [options.method='pbkdf2']
68
- * @param {string|Uint8Array} [options.salt]
69
- * @param {number} [options.iterations=100000]
70
- * @returns {Promise<CryptoKey>}
71
- */
72
- export async function deriveCryptoKey(secretString, options = {}) {
73
- if (!secretString) {
74
- throw new Error('[ln-crypto] Key derivation failed: Secret string is required');
75
- }
76
-
77
- const method = options.method || 'pbkdf2';
78
- const enc = new TextEncoder();
79
-
80
- if (method === 'sha256') {
81
- const hash = await crypto.subtle.digest('SHA-256', enc.encode(secretString));
82
- return crypto.subtle.importKey(
83
- 'raw',
84
- hash,
85
- { name: 'AES-GCM' },
86
- false,
87
- ['encrypt', 'decrypt']
88
- );
89
- }
90
-
91
- // PBKDF2 mode
92
- const saltStr = options.salt || DEFAULT_SALT;
93
- const salt = typeof saltStr === 'string' ? enc.encode(saltStr) : saltStr;
94
- const iterations = options.iterations || 100000;
95
-
96
- const baseKey = await crypto.subtle.importKey(
97
- 'raw',
98
- enc.encode(secretString),
99
- 'PBKDF2',
100
- false,
101
- ['deriveKey']
102
- );
103
-
104
- return crypto.subtle.deriveKey(
105
- {
106
- name: 'PBKDF2',
107
- salt,
108
- iterations,
109
- hash: 'SHA-256'
110
- },
111
- baseKey,
112
- { name: 'AES-GCM', length: 256 },
113
- false,
114
- ['encrypt', 'decrypt']
115
- );
116
- }
117
-
118
- /**
119
- * Sets the active cryptographic key in module memory.
120
- * Default method is 'sha256' for backward-compatibility with existing at-rest storage caches.
121
- * Pass { method: 'pbkdf2' } to use PBKDF2.
122
- * Passing empty/falsy clears the key.
123
- * @param {string|null} secretString
124
- * @param {Object} [options]
125
- * @returns {Promise<void>}
126
- */
127
- export async function setCryptoKey(secretString, options = {}) {
128
- if (!secretString) {
129
- _cryptoKey = null;
130
- return;
131
- }
132
-
133
- try {
134
- // Default to sha256 when called as setCryptoKey('secret') for backward compatibility
135
- const method = options.method || 'sha256';
136
- _cryptoKey = await deriveCryptoKey(secretString, { ...options, method });
137
- } catch (err) {
138
- console.error('[ln-core/crypto] Key derivation failed:', err);
139
- _cryptoKey = null;
140
- throw err;
141
- }
142
- }
143
-
144
- /**
145
- * Returns the currently active CryptoKey in module memory.
146
- * @returns {CryptoKey|null}
147
- */
148
- export function getCryptoKey() {
149
- return _cryptoKey;
150
- }
151
-
152
- /**
153
- * Clears the active cryptographic key in module memory.
154
- */
155
- export function clearCryptoKey() {
156
- _cryptoKey = null;
157
- }
158
-
159
- /**
160
- * Checks if an active cryptographic key is set.
161
- * @returns {boolean}
162
- */
163
- export function hasCryptoKey() {
164
- return _cryptoKey !== null;
165
- }
166
-
167
- /**
168
- * Encrypts arbitrary data using AES-GCM (256-bit).
169
- * Fail-closed: Throws an Error if no active key is provided or if encryption fails.
170
- * @param {any} plainData
171
- * @param {CryptoKey|Object} [keyOrOptions]
172
- * @param {Object} [maybeOptions]
173
- * @returns {Promise<Object>} Version 1 envelope: { v: 1, alg: 'AES-GCM', encrypted: true, iv, data }
174
- */
175
- export async function encryptData(plainData, keyOrOptions, maybeOptions) {
176
- const { key } = _resolveKeyAndOptions(keyOrOptions, maybeOptions);
177
-
178
- if (plainData === undefined || plainData === null) {
179
- return plainData;
180
- }
181
-
182
- if (!key) {
183
- throw new Error('[ln-crypto] Encryption failed: No active cryptographic key provided');
184
- }
185
-
186
- try {
187
- const enc = new TextEncoder();
188
- const iv = crypto.getRandomValues(new Uint8Array(12));
189
-
190
- const serialized = typeof plainData === 'string' ? plainData : JSON.stringify(plainData);
191
- const encryptedBuffer = await crypto.subtle.encrypt(
192
- { name: 'AES-GCM', iv },
193
- key,
194
- enc.encode(serialized)
195
- );
196
-
197
- return {
198
- v: 1,
199
- alg: 'AES-GCM',
200
- encrypted: true,
201
- iv: uint8ToBase64(iv),
202
- data: uint8ToBase64(new Uint8Array(encryptedBuffer))
203
- };
204
- } catch (err) {
205
- console.error('[ln-core/crypto] Encryption failed:', err);
206
- throw new Error('[ln-crypto] Encryption failed: ' + (err && err.message ? err.message : String(err)));
207
- }
208
- }
209
-
210
- /**
211
- * Decrypts an encrypted envelope using AES-GCM (256-bit).
212
- * Fail-closed: Throws an Error if key is missing or decryption fails, unless { silent: true } is passed.
213
- * Backwards compatible: Decrypts both v1 envelopes and legacy unversioned { encrypted: true, iv, data } payloads.
214
- * @param {Object} encryptedObject
215
- * @param {CryptoKey|Object} [keyOrOptions]
216
- * @param {Object} [maybeOptions]
217
- * @returns {Promise<any>}
218
- */
219
- export async function decryptData(encryptedObject, keyOrOptions, maybeOptions) {
220
- const { key, options } = _resolveKeyAndOptions(keyOrOptions, maybeOptions);
221
- const silent = options.silent === true;
222
-
223
- if (!encryptedObject || !encryptedObject.encrypted) {
224
- return encryptedObject;
225
- }
226
-
227
- if (!key) {
228
- if (silent) {
229
- return { ...encryptedObject, decryptionError: true };
230
- }
231
- throw new Error('[ln-crypto] Decryption failed: No active cryptographic key provided');
232
- }
233
-
234
- if (!encryptedObject.iv || !encryptedObject.data) {
235
- if (silent) {
236
- return { ...encryptedObject, decryptionError: true };
237
- }
238
- throw new Error('[ln-crypto] Decryption failed: Malformed envelope (missing iv or data)');
239
- }
240
-
241
- try {
242
- const dec = new TextDecoder();
243
- const iv = base64ToUint8(encryptedObject.iv);
244
- const encryptedBuffer = base64ToUint8(encryptedObject.data);
245
-
246
- const decryptedBuffer = await crypto.subtle.decrypt(
247
- { name: 'AES-GCM', iv },
248
- key,
249
- encryptedBuffer
250
- );
251
-
252
- const decoded = dec.decode(decryptedBuffer);
253
- try {
254
- return JSON.parse(decoded);
255
- } catch (_) {
256
- return decoded;
257
- }
258
- } catch (err) {
259
- if (silent) {
260
- return { ...encryptedObject, decryptionError: true };
261
- }
262
- console.error('[ln-core/crypto] Decryption failed. Key may be incorrect or payload tampered:', err);
263
- throw new Error('[ln-crypto] Decryption failed. Key may be incorrect or payload tampered: ' + (err && err.message ? err.message : String(err)));
264
- }
265
- }
1
+ // Web Crypto API Reusable Cryptographic Helpers
2
+ let _cryptoKey = null;
3
+
4
+ const DEFAULT_SALT = 'ln-ashlar:storage-salt:v1';
5
+ const CHUNK_SIZE = 0x8000; // 32KB chunks for stack-safe Base64 conversion
6
+
7
+ /**
8
+ * Encodes a Uint8Array to a Base64 string in chunks to prevent call-stack overflow on large payloads.
9
+ * @param {Uint8Array} bytes
10
+ * @returns {string}
11
+ */
12
+ export function uint8ToBase64(bytes) {
13
+ let binary = '';
14
+ const len = bytes.byteLength;
15
+ for (let i = 0; i < len; i += CHUNK_SIZE) {
16
+ binary += String.fromCharCode.apply(
17
+ null,
18
+ bytes.subarray(i, Math.min(i + CHUNK_SIZE, len))
19
+ );
20
+ }
21
+ return btoa(binary);
22
+ }
23
+
24
+ /**
25
+ * Decodes a Base64 string to a Uint8Array.
26
+ * @param {string} base64
27
+ * @returns {Uint8Array}
28
+ */
29
+ export function base64ToUint8(base64) {
30
+ const binary = atob(base64);
31
+ const len = binary.length;
32
+ const bytes = new Uint8Array(len);
33
+ for (let i = 0; i < len; i++) {
34
+ bytes[i] = binary.charCodeAt(i);
35
+ }
36
+ return bytes;
37
+ }
38
+
39
+ /**
40
+ * Resolves key and options from overloaded arguments.
41
+ * Ensures an options object like { silent: true } is never passed to WebCrypto as a CryptoKey.
42
+ * @private
43
+ */
44
+ function _resolveKeyAndOptions(keyOrOptions, maybeOptions) {
45
+ let key = _cryptoKey;
46
+ let options = {};
47
+
48
+ if (typeof CryptoKey !== 'undefined' && keyOrOptions instanceof CryptoKey) {
49
+ key = keyOrOptions;
50
+ if (maybeOptions && typeof maybeOptions === 'object') {
51
+ options = maybeOptions;
52
+ }
53
+ } else if (keyOrOptions && typeof keyOrOptions === 'object') {
54
+ options = keyOrOptions;
55
+ if (typeof CryptoKey !== 'undefined' && options.key instanceof CryptoKey) {
56
+ key = options.key;
57
+ }
58
+ }
59
+
60
+ return { key, options };
61
+ }
62
+
63
+ /**
64
+ * Derives a 256-bit AES-GCM CryptoKey using PBKDF2 or SHA-256.
65
+ * @param {string} secretString
66
+ * @param {Object} [options]
67
+ * @param {'pbkdf2'|'sha256'} [options.method='pbkdf2']
68
+ * @param {string|Uint8Array} [options.salt]
69
+ * @param {number} [options.iterations=100000]
70
+ * @returns {Promise<CryptoKey>}
71
+ */
72
+ export async function deriveCryptoKey(secretString, options = {}) {
73
+ if (!secretString) {
74
+ throw new Error('[ln-crypto] Key derivation failed: Secret string is required');
75
+ }
76
+
77
+ const method = options.method || 'pbkdf2';
78
+ const enc = new TextEncoder();
79
+
80
+ if (method === 'sha256') {
81
+ const hash = await crypto.subtle.digest('SHA-256', enc.encode(secretString));
82
+ return crypto.subtle.importKey(
83
+ 'raw',
84
+ hash,
85
+ { name: 'AES-GCM' },
86
+ false,
87
+ ['encrypt', 'decrypt']
88
+ );
89
+ }
90
+
91
+ // PBKDF2 mode
92
+ const saltStr = options.salt || DEFAULT_SALT;
93
+ const salt = typeof saltStr === 'string' ? enc.encode(saltStr) : saltStr;
94
+ const iterations = options.iterations || 100000;
95
+
96
+ const baseKey = await crypto.subtle.importKey(
97
+ 'raw',
98
+ enc.encode(secretString),
99
+ 'PBKDF2',
100
+ false,
101
+ ['deriveKey']
102
+ );
103
+
104
+ return crypto.subtle.deriveKey(
105
+ {
106
+ name: 'PBKDF2',
107
+ salt,
108
+ iterations,
109
+ hash: 'SHA-256'
110
+ },
111
+ baseKey,
112
+ { name: 'AES-GCM', length: 256 },
113
+ false,
114
+ ['encrypt', 'decrypt']
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Sets the active cryptographic key in module memory.
120
+ * Default method is 'sha256' for backward-compatibility with existing at-rest storage caches.
121
+ * Pass { method: 'pbkdf2' } to use PBKDF2.
122
+ * Passing empty/falsy clears the key.
123
+ * @param {string|null} secretString
124
+ * @param {Object} [options]
125
+ * @returns {Promise<void>}
126
+ */
127
+ export async function setCryptoKey(secretString, options = {}) {
128
+ if (!secretString) {
129
+ _cryptoKey = null;
130
+ return;
131
+ }
132
+
133
+ try {
134
+ // Default to sha256 when called as setCryptoKey('secret') for backward compatibility
135
+ const method = options.method || 'sha256';
136
+ _cryptoKey = await deriveCryptoKey(secretString, { ...options, method });
137
+ } catch (err) {
138
+ console.error('[ln-core/crypto] Key derivation failed:', err);
139
+ _cryptoKey = null;
140
+ throw err;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Returns the currently active CryptoKey in module memory.
146
+ * @returns {CryptoKey|null}
147
+ */
148
+ export function getCryptoKey() {
149
+ return _cryptoKey;
150
+ }
151
+
152
+ /**
153
+ * Clears the active cryptographic key in module memory.
154
+ */
155
+ export function clearCryptoKey() {
156
+ _cryptoKey = null;
157
+ }
158
+
159
+ /**
160
+ * Checks if an active cryptographic key is set.
161
+ * @returns {boolean}
162
+ */
163
+ export function hasCryptoKey() {
164
+ return _cryptoKey !== null;
165
+ }
166
+
167
+ /**
168
+ * Encrypts arbitrary data using AES-GCM (256-bit).
169
+ * Fail-closed: Throws an Error if no active key is provided or if encryption fails.
170
+ * @param {any} plainData
171
+ * @param {CryptoKey|Object} [keyOrOptions]
172
+ * @param {Object} [maybeOptions]
173
+ * @returns {Promise<Object>} Version 1 envelope: { v: 1, alg: 'AES-GCM', encrypted: true, iv, data }
174
+ */
175
+ export async function encryptData(plainData, keyOrOptions, maybeOptions) {
176
+ const { key } = _resolveKeyAndOptions(keyOrOptions, maybeOptions);
177
+
178
+ if (plainData === undefined || plainData === null) {
179
+ return plainData;
180
+ }
181
+
182
+ if (!key) {
183
+ throw new Error('[ln-crypto] Encryption failed: No active cryptographic key provided');
184
+ }
185
+
186
+ try {
187
+ const enc = new TextEncoder();
188
+ const iv = crypto.getRandomValues(new Uint8Array(12));
189
+
190
+ const serialized = typeof plainData === 'string' ? plainData : JSON.stringify(plainData);
191
+ const encryptedBuffer = await crypto.subtle.encrypt(
192
+ { name: 'AES-GCM', iv },
193
+ key,
194
+ enc.encode(serialized)
195
+ );
196
+
197
+ return {
198
+ v: 1,
199
+ alg: 'AES-GCM',
200
+ encrypted: true,
201
+ iv: uint8ToBase64(iv),
202
+ data: uint8ToBase64(new Uint8Array(encryptedBuffer))
203
+ };
204
+ } catch (err) {
205
+ console.error('[ln-core/crypto] Encryption failed:', err);
206
+ throw new Error('[ln-crypto] Encryption failed: ' + (err && err.message ? err.message : String(err)));
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Decrypts an encrypted envelope using AES-GCM (256-bit).
212
+ * Fail-closed: Throws an Error if key is missing or decryption fails, unless { silent: true } is passed.
213
+ * Backwards compatible: Decrypts both v1 envelopes and legacy unversioned { encrypted: true, iv, data } payloads.
214
+ * @param {Object} encryptedObject
215
+ * @param {CryptoKey|Object} [keyOrOptions]
216
+ * @param {Object} [maybeOptions]
217
+ * @returns {Promise<any>}
218
+ */
219
+ export async function decryptData(encryptedObject, keyOrOptions, maybeOptions) {
220
+ const { key, options } = _resolveKeyAndOptions(keyOrOptions, maybeOptions);
221
+ const silent = options.silent === true;
222
+
223
+ if (!encryptedObject || !encryptedObject.encrypted) {
224
+ return encryptedObject;
225
+ }
226
+
227
+ if (!key) {
228
+ if (silent) {
229
+ return { ...encryptedObject, decryptionError: true };
230
+ }
231
+ throw new Error('[ln-crypto] Decryption failed: No active cryptographic key provided');
232
+ }
233
+
234
+ if (!encryptedObject.iv || !encryptedObject.data) {
235
+ if (silent) {
236
+ return { ...encryptedObject, decryptionError: true };
237
+ }
238
+ throw new Error('[ln-crypto] Decryption failed: Malformed envelope (missing iv or data)');
239
+ }
240
+
241
+ try {
242
+ const dec = new TextDecoder();
243
+ const iv = base64ToUint8(encryptedObject.iv);
244
+ const encryptedBuffer = base64ToUint8(encryptedObject.data);
245
+
246
+ const decryptedBuffer = await crypto.subtle.decrypt(
247
+ { name: 'AES-GCM', iv },
248
+ key,
249
+ encryptedBuffer
250
+ );
251
+
252
+ const decoded = dec.decode(decryptedBuffer);
253
+ try {
254
+ return JSON.parse(decoded);
255
+ } catch (_) {
256
+ return decoded;
257
+ }
258
+ } catch (err) {
259
+ if (silent) {
260
+ return { ...encryptedObject, decryptionError: true };
261
+ }
262
+ console.error('[ln-core/crypto] Decryption failed. Key may be incorrect or payload tampered:', err);
263
+ throw new Error('[ln-crypto] Decryption failed. Key may be incorrect or payload tampered: ' + (err && err.message ? err.message : String(err)));
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Generates a cryptographically random UUID (v4).
269
+ * Uses native crypto.randomUUID() when available, with a resilient fallback.
270
+ * @returns {string}
271
+ */
272
+ export function uuid() {
273
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
274
+ return crypto.randomUUID();
275
+ }
276
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
277
+ const r = Math.random() * 16 | 0;
278
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
279
+ return v.toString(16);
280
+ });
281
+ }
282
+
283
+ if (typeof window !== 'undefined') {
284
+ window.lnCore = window.lnCore || {};
285
+ window.lnCore.uuid = uuid;
286
+ }