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