@livenetworks/ashlar 2.0.7 → 2.0.9

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 (213) 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 +9 -0
  12. package/components/ln-api-connector/src/ln-api-connector.js +2 -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/attrs.js +13 -0
  33. package/components/ln-core/crypto.js +286 -265
  34. package/components/ln-core/dom.js +115 -115
  35. package/components/ln-core/form.js +146 -136
  36. package/components/ln-core/helpers.js +33 -33
  37. package/components/ln-core/http.js +48 -48
  38. package/components/ln-core/index.js +1 -1
  39. package/components/ln-core/lifecycle.js +5 -0
  40. package/components/ln-core/ln-core.schema.json +3 -2
  41. package/components/ln-core/locale.js +55 -55
  42. package/components/ln-core/matching.js +78 -78
  43. package/components/ln-core/number.js +125 -125
  44. package/components/ln-core/template.js +206 -206
  45. package/components/ln-couchdb-connector/ln-couchdb-connector.js +1 -1
  46. package/components/ln-couchdb-connector/ln-couchdb-connector.schema.json +50 -50
  47. package/components/ln-couchdb-connector/src/ln-couchdb-connector.js +1 -0
  48. package/components/ln-data-coordinator/README.md +9 -38
  49. package/components/ln-data-coordinator/ln-data-coordinator-dev.scss +8 -8
  50. package/components/ln-data-coordinator/ln-data-coordinator.js +1 -1
  51. package/components/ln-data-coordinator/ln-data-coordinator.schema.json +16 -16
  52. package/components/ln-data-coordinator/src/data-read-policy.js +0 -7
  53. package/components/ln-data-coordinator/src/ln-data-coordinator.js +432 -1308
  54. package/components/ln-data-store/ln-data-store-dev.scss +13 -13
  55. package/components/ln-data-store/ln-data-store.js +1 -1
  56. package/components/ln-data-store/ln-data-store.schema.json +84 -84
  57. package/components/ln-data-store/src/data-store-model.js +184 -184
  58. package/components/ln-data-store/src/ln-data-store.js +1 -1
  59. package/components/ln-date/ln-date.js +1 -1
  60. package/components/ln-date/ln-date.schema.json +92 -92
  61. package/components/ln-date/src/date-model.js +186 -186
  62. package/components/ln-debug/ln-debug.js +1 -1
  63. package/components/ln-debug/ln-debug.schema.json +28 -28
  64. package/components/ln-debug/src/console-sink.js +19 -19
  65. package/components/ln-debug/src/gate.js +95 -95
  66. package/components/ln-debug/src/generated-attributes.js +4 -2
  67. package/components/ln-dropdown/ln-dropdown-dev.scss +13 -13
  68. package/components/ln-dropdown/ln-dropdown.js +1 -1
  69. package/components/ln-dropdown/ln-dropdown.schema.json +90 -90
  70. package/components/ln-editor/ln-editor.js +1 -1
  71. package/components/ln-editor/ln-editor.schema.json +58 -58
  72. package/components/ln-external-links/ln-external-links.js +1 -1
  73. package/components/ln-external-links/ln-external-links.schema.json +18 -18
  74. package/components/ln-fill/ln-fill.js +1 -1
  75. package/components/ln-fill/ln-fill.schema.json +23 -23
  76. package/components/ln-filter/ln-filter.js +1 -1
  77. package/components/ln-filter/ln-filter.schema.json +153 -153
  78. package/components/ln-filter/src/filter-model.js +185 -185
  79. package/components/ln-form/ln-form-dev.scss +8 -8
  80. package/components/ln-form/ln-form.js +1 -1
  81. package/components/ln-form/ln-form.schema.json +6 -0
  82. package/components/ln-form/src/ln-form.js +21 -0
  83. package/components/ln-http/ln-http.js +1 -1
  84. package/components/ln-http/ln-http.schema.json +6 -6
  85. package/components/ln-icon/ln-icon.schema.json +6 -6
  86. package/components/ln-include/ln-include.js +1 -1
  87. package/components/ln-include/ln-include.schema.json +23 -23
  88. package/components/ln-key/ln-key-dev.scss +18 -18
  89. package/components/ln-key/ln-key.js +1 -1
  90. package/components/ln-key/ln-key.schema.json +56 -56
  91. package/components/ln-key/src/ln-key.js +10 -10
  92. package/components/ln-link/ln-link-dev.scss +8 -8
  93. package/components/ln-link/ln-link.js +1 -1
  94. package/components/ln-link/ln-link.schema.json +22 -22
  95. package/components/ln-list/README.md +152 -152
  96. package/components/ln-list/ln-list-dev.scss +8 -8
  97. package/components/ln-list/ln-list.js +1 -1
  98. package/components/ln-list/ln-list.schema.json +166 -166
  99. package/components/ln-modal/ln-modal.js +1 -1
  100. package/components/ln-modal/ln-modal.schema.json +65 -65
  101. package/components/ln-nav/ln-nav.js +1 -1
  102. package/components/ln-nav/ln-nav.schema.json +25 -25
  103. package/components/ln-number/ln-number.js +1 -1
  104. package/components/ln-number/ln-number.schema.json +63 -63
  105. package/components/ln-number/src/ln-number.js +26 -0
  106. package/components/ln-number/src/number-model.js +31 -31
  107. package/components/ln-obfuscator/README.md +60 -60
  108. package/components/ln-obfuscator/ln-obfuscator.js +1 -1
  109. package/components/ln-obfuscator/ln-obfuscator.schema.json +49 -49
  110. package/components/ln-obfuscator/src/ln-obfuscator.js +159 -159
  111. package/components/ln-obfuscator/src/obfuscator-model.js +250 -250
  112. package/components/ln-options/ln-options.js +1 -1
  113. package/components/ln-options/ln-options.schema.json +6 -0
  114. package/components/ln-options/src/ln-options.js +4 -4
  115. package/components/ln-persist/README.md +151 -151
  116. package/components/ln-persist/ln-persist.js +1 -1
  117. package/components/ln-persist/ln-persist.schema.json +28 -28
  118. package/components/ln-persist/src/ln-persist.js +83 -83
  119. package/components/ln-picklist/README.md +230 -230
  120. package/components/ln-picklist/ln-picklist-dev.scss +22 -22
  121. package/components/ln-picklist/ln-picklist.js +1 -1
  122. package/components/ln-picklist/ln-picklist.schema.json +49 -49
  123. package/components/ln-picklist/src/ln-picklist.js +228 -228
  124. package/components/ln-popover/ln-popover-dev.scss +13 -13
  125. package/components/ln-popover/ln-popover.js +1 -1
  126. package/components/ln-popover/ln-popover.schema.json +82 -82
  127. package/components/ln-progress/ln-progress-dev.scss +8 -8
  128. package/components/ln-progress/ln-progress.js +1 -1
  129. package/components/ln-progress/ln-progress.schema.json +40 -40
  130. package/components/ln-router/ln-router.js +1 -1
  131. package/components/ln-router/ln-router.schema.json +76 -76
  132. package/components/ln-scroll/README.md +45 -45
  133. package/components/ln-scroll/ln-scroll.js +1 -1
  134. package/components/ln-scroll/ln-scroll.schema.json +85 -85
  135. package/components/ln-scroll/src/ln-scroll.js +150 -150
  136. package/components/ln-search/README.md +188 -188
  137. package/components/ln-search/ln-search.js +1 -1
  138. package/components/ln-search/ln-search.schema.json +103 -103
  139. package/components/ln-slug/ln-slug.js +1 -1
  140. package/components/ln-slug/ln-slug.schema.json +23 -23
  141. package/components/ln-slug/src/ln-slug.js +25 -1
  142. package/components/ln-sort/ln-sort.js +1 -1
  143. package/components/ln-sort/ln-sort.schema.json +104 -104
  144. package/components/ln-sort/src/ln-sort.js +321 -321
  145. package/components/ln-sort/src/sort-model.js +117 -117
  146. package/components/ln-sortable/ln-sortable-dev.scss +13 -13
  147. package/components/ln-sortable/ln-sortable.js +1 -1
  148. package/components/ln-sortable/ln-sortable.schema.json +36 -36
  149. package/components/ln-stat/ln-stat-dev.scss +8 -8
  150. package/components/ln-stat/ln-stat.js +1 -1
  151. package/components/ln-stat/ln-stat.schema.json +32 -32
  152. package/components/ln-stat/src/stat-model.js +28 -28
  153. package/components/ln-table/ln-table.js +1 -1
  154. package/components/ln-table/ln-table.schema.json +211 -211
  155. package/components/ln-table/src/table-model.js +98 -98
  156. package/components/ln-table-coordinator/ln-table-coordinator.js +1 -1
  157. package/components/ln-table-coordinator/ln-table-coordinator.schema.json +93 -93
  158. package/components/ln-tabs/ln-tabs.js +1 -1
  159. package/components/ln-tabs/ln-tabs.schema.json +75 -75
  160. package/components/ln-tabs/src/ln-tabs.js +208 -208
  161. package/components/ln-tabs/src/tabs-model.js +82 -82
  162. package/components/ln-time/ln-time-dev.scss +13 -13
  163. package/components/ln-time/ln-time.js +1 -1
  164. package/components/ln-time/ln-time.schema.json +38 -38
  165. package/components/ln-toast/ln-toast.js +1 -1
  166. package/components/ln-toast/ln-toast.schema.json +65 -65
  167. package/components/ln-toggle/ln-toggle.js +1 -1
  168. package/components/ln-toggle/ln-toggle.schema.json +52 -52
  169. package/components/ln-tooltip/ln-tooltip.js +1 -1
  170. package/components/ln-tooltip/ln-tooltip.schema.json +86 -86
  171. package/components/ln-translations/ln-translations.js +1 -1
  172. package/components/ln-translations/ln-translations.schema.json +120 -120
  173. package/components/ln-ui-coordinator/README.md +62 -62
  174. package/components/ln-ui-coordinator/ln-ui-coordinator.js +1 -1
  175. package/components/ln-ui-coordinator/ln-ui-coordinator.schema.json +28 -28
  176. package/components/ln-ui-coordinator/src/ln-ui-coordinator.js +140 -140
  177. package/components/ln-upload/ln-upload.js +1 -1
  178. package/components/ln-upload/ln-upload.schema.json +193 -193
  179. package/components/ln-upload/src/upload-model.js +78 -78
  180. package/components/ln-validate/ln-validate.js +1 -1
  181. package/components/ln-validate/ln-validate.schema.json +50 -50
  182. package/components/ln-validate/src/validate-model.js +64 -64
  183. package/components/ln-websocket-connector/README.md +166 -166
  184. package/components/ln-websocket-connector/ln-websocket-connector.js +1 -1
  185. package/components/ln-websocket-connector/ln-websocket-connector.schema.json +29 -29
  186. package/components/ln-websocket-connector/src/ln-websocket-connector.js +283 -279
  187. package/ln-ashlar.html-data.json +15 -8
  188. package/package.json +85 -84
  189. package/theme/brand.css +73 -73
  190. package/theme/components/_confirm.scss +6 -6
  191. package/theme/components/_picklist.scss +3 -3
  192. package/theme/config/_palette.scss +274 -274
  193. package/theme/config/_theme.scss +292 -292
  194. package/theme/config/_tokens.scss +285 -285
  195. package/theme/config/mixins/_index.scss +62 -62
  196. package/theme/config/mixins/_picklist.scss +66 -66
  197. package/theme/ln-ashlar-dev.scss +66 -66
  198. package/theme/ln-ashlar-landing.scss +56 -56
  199. package/theme/ln-ashlar-theme.scss +63 -63
  200. package/theme/presentation/_bento.scss +151 -151
  201. package/theme/presentation/_cta.scss +111 -111
  202. package/theme/presentation/_effects.scss +216 -216
  203. package/theme/presentation/_features.scss +108 -108
  204. package/theme/presentation/_footer.scss +129 -129
  205. package/theme/presentation/_hero.scss +158 -158
  206. package/theme/presentation/_layout.scss +170 -170
  207. package/theme/presentation/_navbar.scss +140 -140
  208. package/theme/presentation/_pricing.scss +262 -262
  209. package/theme/presentation/_stats.scss +87 -87
  210. package/theme/presentation/_testimonials.scss +124 -124
  211. package/theme/presentation/_tokens.scss +39 -39
  212. package/theme/presentation/_typography.scss +163 -163
  213. package/web-types.json +26 -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
+ }