@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.
- package/LICENSE +641 -641
- package/components/COMPONENTS.md +1116 -1116
- package/components/index.js +81 -81
- package/components/ln-accordion/ln-accordion-dev.scss +8 -8
- package/components/ln-accordion/ln-accordion.js +1 -1
- package/components/ln-accordion/ln-accordion.schema.json +30 -30
- package/components/ln-ajax/ln-ajax-dev.scss +8 -8
- package/components/ln-ajax/ln-ajax.js +1 -1
- package/components/ln-ajax/ln-ajax.schema.json +30 -30
- package/components/ln-api-connector/ln-api-connector.js +1 -1
- package/components/ln-api-connector/ln-api-connector.schema.json +96 -96
- package/components/ln-api-connector/src/ln-api-connector.js +1 -0
- package/components/ln-api-queue/ln-api-queue.js +1 -1
- package/components/ln-api-queue/ln-api-queue.schema.json +34 -34
- package/components/ln-api-queue/src/ln-api-queue.js +3 -13
- package/components/ln-autoresize/ln-autoresize-dev.scss +8 -8
- package/components/ln-autoresize/ln-autoresize.js +1 -1
- package/components/ln-autoresize/ln-autoresize.schema.json +22 -22
- package/components/ln-autosave/ln-autosave-dev.scss +13 -13
- package/components/ln-autosave/ln-autosave.js +1 -1
- package/components/ln-autosave/ln-autosave.schema.json +48 -48
- package/components/ln-autosave/src/autosave-model.js +34 -34
- package/components/ln-chart/ln-chart.js +1 -1
- package/components/ln-chart/ln-chart.schema.json +146 -146
- package/components/ln-chart/src/chart-model.js +154 -154
- package/components/ln-circular-progress/ln-circular-progress.js +1 -1
- package/components/ln-circular-progress/ln-circular-progress.schema.json +42 -42
- package/components/ln-confirm/ln-confirm-dev.scss +24 -24
- package/components/ln-confirm/ln-confirm.js +1 -1
- package/components/ln-confirm/ln-confirm.schema.json +66 -66
- package/components/ln-confirm/src/ln-confirm.js +178 -178
- package/components/ln-core/crypto.js +286 -265
- package/components/ln-core/dom.js +115 -115
- package/components/ln-core/form.js +136 -136
- package/components/ln-core/helpers.js +33 -33
- package/components/ln-core/http.js +48 -48
- package/components/ln-core/index.js +1 -1
- package/components/ln-core/ln-core.schema.json +109 -109
- package/components/ln-core/locale.js +55 -55
- package/components/ln-core/matching.js +78 -78
- package/components/ln-core/number.js +125 -125
- package/components/ln-core/template.js +206 -206
- package/components/ln-couchdb-connector/ln-couchdb-connector.js +1 -1
- package/components/ln-couchdb-connector/ln-couchdb-connector.schema.json +50 -50
- package/components/ln-couchdb-connector/src/ln-couchdb-connector.js +1 -0
- package/components/ln-data-coordinator/README.md +9 -38
- package/components/ln-data-coordinator/ln-data-coordinator-dev.scss +8 -8
- package/components/ln-data-coordinator/ln-data-coordinator.js +1 -1
- package/components/ln-data-coordinator/ln-data-coordinator.schema.json +16 -16
- package/components/ln-data-coordinator/src/data-read-policy.js +0 -7
- package/components/ln-data-coordinator/src/ln-data-coordinator.js +428 -1308
- package/components/ln-data-store/ln-data-store-dev.scss +13 -13
- package/components/ln-data-store/ln-data-store.js +1 -1
- package/components/ln-data-store/ln-data-store.schema.json +84 -84
- package/components/ln-data-store/src/data-store-model.js +184 -184
- package/components/ln-data-store/src/ln-data-store.js +1 -1
- package/components/ln-date/ln-date.js +1 -1
- package/components/ln-date/ln-date.schema.json +92 -92
- package/components/ln-date/src/date-model.js +186 -186
- package/components/ln-debug/ln-debug.js +1 -1
- package/components/ln-debug/ln-debug.schema.json +28 -28
- package/components/ln-debug/src/console-sink.js +19 -19
- package/components/ln-debug/src/gate.js +95 -95
- package/components/ln-debug/src/generated-attributes.js +2 -2
- package/components/ln-dropdown/ln-dropdown-dev.scss +13 -13
- package/components/ln-dropdown/ln-dropdown.js +1 -1
- package/components/ln-dropdown/ln-dropdown.schema.json +90 -90
- package/components/ln-editor/ln-editor.js +1 -1
- package/components/ln-editor/ln-editor.schema.json +58 -58
- package/components/ln-external-links/ln-external-links.js +1 -1
- package/components/ln-external-links/ln-external-links.schema.json +18 -18
- package/components/ln-fill/ln-fill.js +1 -1
- package/components/ln-fill/ln-fill.schema.json +23 -23
- package/components/ln-filter/ln-filter.js +1 -1
- package/components/ln-filter/ln-filter.schema.json +153 -153
- package/components/ln-filter/src/filter-model.js +185 -185
- package/components/ln-form/ln-form-dev.scss +8 -8
- package/components/ln-form/ln-form.js +1 -1
- package/components/ln-form/ln-form.schema.json +45 -45
- package/components/ln-http/ln-http.js +1 -1
- package/components/ln-http/ln-http.schema.json +6 -6
- package/components/ln-icon/ln-icon.schema.json +6 -6
- package/components/ln-include/ln-include.js +1 -1
- package/components/ln-include/ln-include.schema.json +23 -23
- package/components/ln-key/ln-key-dev.scss +18 -18
- package/components/ln-key/ln-key.js +1 -1
- package/components/ln-key/ln-key.schema.json +56 -56
- package/components/ln-key/src/ln-key.js +10 -10
- package/components/ln-link/ln-link-dev.scss +8 -8
- package/components/ln-link/ln-link.js +1 -1
- package/components/ln-link/ln-link.schema.json +22 -22
- package/components/ln-list/README.md +152 -152
- package/components/ln-list/ln-list-dev.scss +8 -8
- package/components/ln-list/ln-list.js +1 -1
- package/components/ln-list/ln-list.schema.json +166 -166
- package/components/ln-modal/ln-modal.js +1 -1
- package/components/ln-modal/ln-modal.schema.json +65 -65
- package/components/ln-nav/ln-nav.js +1 -1
- package/components/ln-nav/ln-nav.schema.json +25 -25
- package/components/ln-number/ln-number.js +1 -1
- package/components/ln-number/ln-number.schema.json +63 -63
- package/components/ln-number/src/number-model.js +31 -31
- package/components/ln-obfuscator/README.md +60 -60
- package/components/ln-obfuscator/ln-obfuscator.js +1 -1
- package/components/ln-obfuscator/ln-obfuscator.schema.json +49 -49
- package/components/ln-obfuscator/src/ln-obfuscator.js +159 -159
- package/components/ln-obfuscator/src/obfuscator-model.js +250 -250
- package/components/ln-options/ln-options.js +1 -1
- package/components/ln-options/ln-options.schema.json +41 -41
- package/components/ln-persist/README.md +151 -151
- package/components/ln-persist/ln-persist.js +1 -1
- package/components/ln-persist/ln-persist.schema.json +28 -28
- package/components/ln-persist/src/ln-persist.js +83 -83
- package/components/ln-picklist/README.md +230 -230
- package/components/ln-picklist/ln-picklist-dev.scss +22 -22
- package/components/ln-picklist/ln-picklist.js +1 -1
- package/components/ln-picklist/ln-picklist.schema.json +49 -49
- package/components/ln-picklist/src/ln-picklist.js +228 -228
- package/components/ln-popover/ln-popover-dev.scss +13 -13
- package/components/ln-popover/ln-popover.js +1 -1
- package/components/ln-popover/ln-popover.schema.json +82 -82
- package/components/ln-progress/ln-progress-dev.scss +8 -8
- package/components/ln-progress/ln-progress.js +1 -1
- package/components/ln-progress/ln-progress.schema.json +40 -40
- package/components/ln-router/ln-router.js +1 -1
- package/components/ln-router/ln-router.schema.json +76 -76
- package/components/ln-scroll/README.md +45 -45
- package/components/ln-scroll/ln-scroll.js +1 -1
- package/components/ln-scroll/ln-scroll.schema.json +85 -85
- package/components/ln-scroll/src/ln-scroll.js +150 -150
- package/components/ln-search/README.md +188 -188
- package/components/ln-search/ln-search.js +1 -1
- package/components/ln-search/ln-search.schema.json +103 -103
- package/components/ln-slug/ln-slug.js +1 -1
- package/components/ln-slug/ln-slug.schema.json +23 -23
- package/components/ln-sort/ln-sort.js +1 -1
- package/components/ln-sort/ln-sort.schema.json +104 -104
- package/components/ln-sort/src/ln-sort.js +321 -321
- package/components/ln-sort/src/sort-model.js +117 -117
- package/components/ln-sortable/ln-sortable-dev.scss +13 -13
- package/components/ln-sortable/ln-sortable.js +1 -1
- package/components/ln-sortable/ln-sortable.schema.json +36 -36
- package/components/ln-stat/ln-stat-dev.scss +8 -8
- package/components/ln-stat/ln-stat.js +1 -1
- package/components/ln-stat/ln-stat.schema.json +32 -32
- package/components/ln-stat/src/stat-model.js +28 -28
- package/components/ln-table/ln-table.js +1 -1
- package/components/ln-table/ln-table.schema.json +211 -211
- package/components/ln-table/src/table-model.js +98 -98
- package/components/ln-table-coordinator/ln-table-coordinator.js +1 -1
- package/components/ln-table-coordinator/ln-table-coordinator.schema.json +93 -93
- package/components/ln-tabs/ln-tabs.js +1 -1
- package/components/ln-tabs/ln-tabs.schema.json +75 -75
- package/components/ln-tabs/src/ln-tabs.js +208 -208
- package/components/ln-tabs/src/tabs-model.js +82 -82
- package/components/ln-time/ln-time-dev.scss +13 -13
- package/components/ln-time/ln-time.js +1 -1
- package/components/ln-time/ln-time.schema.json +38 -38
- package/components/ln-toast/ln-toast.js +1 -1
- package/components/ln-toast/ln-toast.schema.json +65 -65
- package/components/ln-toggle/ln-toggle.js +1 -1
- package/components/ln-toggle/ln-toggle.schema.json +52 -52
- package/components/ln-tooltip/ln-tooltip.js +1 -1
- package/components/ln-tooltip/ln-tooltip.schema.json +86 -86
- package/components/ln-translations/ln-translations.js +1 -1
- package/components/ln-translations/ln-translations.schema.json +120 -120
- package/components/ln-ui-coordinator/README.md +62 -62
- package/components/ln-ui-coordinator/ln-ui-coordinator.js +1 -1
- package/components/ln-ui-coordinator/ln-ui-coordinator.schema.json +28 -28
- package/components/ln-ui-coordinator/src/ln-ui-coordinator.js +140 -140
- package/components/ln-upload/ln-upload.js +1 -1
- package/components/ln-upload/ln-upload.schema.json +193 -193
- package/components/ln-upload/src/upload-model.js +78 -78
- package/components/ln-validate/ln-validate.js +1 -1
- package/components/ln-validate/ln-validate.schema.json +50 -50
- package/components/ln-validate/src/validate-model.js +64 -64
- package/components/ln-websocket-connector/README.md +166 -166
- package/components/ln-websocket-connector/ln-websocket-connector.js +1 -1
- package/components/ln-websocket-connector/ln-websocket-connector.schema.json +29 -29
- package/components/ln-websocket-connector/src/ln-websocket-connector.js +283 -279
- package/ln-ashlar.html-data.json +7 -8
- package/package.json +84 -84
- package/theme/brand.css +73 -73
- package/theme/components/_confirm.scss +6 -6
- package/theme/components/_picklist.scss +3 -3
- package/theme/config/_palette.scss +274 -274
- package/theme/config/_theme.scss +292 -292
- package/theme/config/_tokens.scss +285 -285
- package/theme/config/mixins/_index.scss +62 -62
- package/theme/config/mixins/_picklist.scss +66 -66
- package/theme/ln-ashlar-dev.scss +66 -66
- package/theme/ln-ashlar-landing.scss +56 -56
- package/theme/ln-ashlar-theme.scss +63 -63
- package/theme/presentation/_bento.scss +151 -151
- package/theme/presentation/_cta.scss +111 -111
- package/theme/presentation/_effects.scss +216 -216
- package/theme/presentation/_features.scss +108 -108
- package/theme/presentation/_footer.scss +129 -129
- package/theme/presentation/_hero.scss +158 -158
- package/theme/presentation/_layout.scss +170 -170
- package/theme/presentation/_navbar.scss +140 -140
- package/theme/presentation/_pricing.scss +262 -262
- package/theme/presentation/_stats.scss +87 -87
- package/theme/presentation/_testimonials.scss +124 -124
- package/theme/presentation/_tokens.scss +39 -39
- package/theme/presentation/_typography.scss +163 -163
- 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
|
+
}
|