react-native-nitro-storage 0.5.7 → 0.5.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/README.md +1 -0
- package/lib/commonjs/index.js +119 -1599
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +83 -1550
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/shared.js +102 -0
- package/lib/commonjs/shared.js.map +1 -0
- package/lib/commonjs/storage-core.js +1505 -0
- package/lib/commonjs/storage-core.js.map +1 -0
- package/lib/module/index.js +111 -1591
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +69 -1536
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/shared.js +82 -0
- package/lib/module/shared.js.map +1 -0
- package/lib/module/storage-core.js +1501 -0
- package/lib/module/storage-core.js.map +1 -0
- package/lib/typescript/index.d.ts +30 -133
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +30 -133
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/shared.d.ts +96 -0
- package/lib/typescript/shared.d.ts.map +1 -0
- package/lib/typescript/storage-core.d.ts +157 -0
- package/lib/typescript/storage-core.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +260 -2519
- package/src/index.web.ts +132 -2331
- package/src/shared.ts +249 -0
- package/src/storage-core.ts +2349 -0
package/lib/module/index.web.js
CHANGED
|
@@ -1,93 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { assertAccessControlLevel, assertBiometricLevel, notifyAllListeners, notifyKeyListeners } from "./shared";
|
|
4
|
+
import { StorageScope, BiometricLevel } from "./Storage.types";
|
|
5
5
|
import { createLocalStorageWebBackend } from "./web-storage-backend";
|
|
6
|
-
import {
|
|
7
|
-
|
|
6
|
+
import { createStorageCore } from "./storage-core";
|
|
7
|
+
export { isKeychainLockedError } from "./shared";
|
|
8
8
|
export { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
|
|
9
9
|
export { migrateFromMMKV } from "./migration";
|
|
10
10
|
export { getStorageErrorCode } from "./storage-runtime";
|
|
11
|
-
function asInternal(item) {
|
|
12
|
-
return item;
|
|
13
|
-
}
|
|
14
|
-
function isUpdater(valueOrFn) {
|
|
15
|
-
return typeof valueOrFn === "function";
|
|
16
|
-
}
|
|
17
|
-
function typedKeys(record) {
|
|
18
|
-
return Object.keys(record);
|
|
19
|
-
}
|
|
20
|
-
function assertEnumInteger(value, min, max, label) {
|
|
21
|
-
if (!Number.isFinite(value) || value < min || value > max) {
|
|
22
|
-
throw new Error(`NitroStorage: Invalid ${label}`);
|
|
23
|
-
}
|
|
24
|
-
if (value !== Math.trunc(value)) {
|
|
25
|
-
throw new Error(`NitroStorage: Invalid ${label}`);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function assertAccessControlLevel(level) {
|
|
29
|
-
assertEnumInteger(level, 0, 4, "access control level");
|
|
30
|
-
}
|
|
31
|
-
function assertBiometricLevel(level) {
|
|
32
|
-
assertEnumInteger(level, 0, 2, "biometric level");
|
|
33
|
-
}
|
|
34
|
-
const registeredMigrations = new Map();
|
|
35
|
-
const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
|
|
36
|
-
Promise.resolve().then(task);
|
|
37
|
-
};
|
|
38
|
-
const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
|
|
39
|
-
const memoryStore = new Map();
|
|
40
|
-
const memoryListeners = new Map();
|
|
41
|
-
const webScopeListeners = new Map([[StorageScope.Disk, new Map()], [StorageScope.Secure, new Map()]]);
|
|
42
|
-
const scopedRawCache = new Map([[StorageScope.Disk, new Map()], [StorageScope.Secure, new Map()]]);
|
|
43
11
|
const webScopeKeyIndex = new Map([[StorageScope.Disk, new Set()], [StorageScope.Secure, new Set()]]);
|
|
44
12
|
const hydratedWebScopeKeyIndex = new Set();
|
|
45
|
-
const pendingDiskWrites = new Map();
|
|
46
|
-
let diskFlushScheduled = false;
|
|
47
|
-
let diskWritesAsync = false;
|
|
48
|
-
const pendingSecureWrites = new Map();
|
|
49
|
-
let secureFlushScheduled = false;
|
|
50
|
-
let secureDefaultAccessControl = AccessControl.WhenUnlocked;
|
|
51
13
|
const SECURE_WEB_PREFIX = "__secure_";
|
|
52
14
|
const BIOMETRIC_WEB_PREFIX = "__bio_";
|
|
53
15
|
let hasWarnedAboutWebBiometricFallback = false;
|
|
54
16
|
let hasWindowStorageEventSubscription = false;
|
|
55
|
-
let
|
|
56
|
-
let eventObserver;
|
|
57
|
-
let eventObserverRedactSecureValues = true;
|
|
58
|
-
const metricsCounters = new Map();
|
|
59
|
-
const storageEvents = new StorageEventRegistry();
|
|
60
|
-
function recordMetric(operation, scope, durationMs, keysCount = 1) {
|
|
61
|
-
const existing = metricsCounters.get(operation);
|
|
62
|
-
if (!existing) {
|
|
63
|
-
metricsCounters.set(operation, {
|
|
64
|
-
count: 1,
|
|
65
|
-
totalDurationMs: durationMs,
|
|
66
|
-
maxDurationMs: durationMs
|
|
67
|
-
});
|
|
68
|
-
} else {
|
|
69
|
-
existing.count += 1;
|
|
70
|
-
existing.totalDurationMs += durationMs;
|
|
71
|
-
existing.maxDurationMs = Math.max(existing.maxDurationMs, durationMs);
|
|
72
|
-
}
|
|
73
|
-
metricsObserver?.({
|
|
74
|
-
operation,
|
|
75
|
-
scope,
|
|
76
|
-
durationMs,
|
|
77
|
-
keysCount
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
function measureOperation(operation, scope, fn, keysCount = 1) {
|
|
81
|
-
if (!metricsObserver) {
|
|
82
|
-
return fn();
|
|
83
|
-
}
|
|
84
|
-
const start = now();
|
|
85
|
-
try {
|
|
86
|
-
return fn();
|
|
87
|
-
} finally {
|
|
88
|
-
recordMetric(operation, scope, now() - start, keysCount);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
17
|
+
let internals;
|
|
91
18
|
function createDefaultDiskBackend() {
|
|
92
19
|
return createLocalStorageWebBackend({
|
|
93
20
|
name: "localStorage:disk",
|
|
@@ -174,51 +101,51 @@ function ensureWebScopeKeyIndex(scope) {
|
|
|
174
101
|
}
|
|
175
102
|
function applyExternalChangeEvent(scope, key, newValue) {
|
|
176
103
|
if (key === null) {
|
|
177
|
-
clearScopeRawCache(scope);
|
|
104
|
+
internals.clearScopeRawCache(scope);
|
|
178
105
|
ensureWebScopeKeyIndex(scope).clear();
|
|
179
|
-
notifyAllListeners(getScopedListeners(scope));
|
|
106
|
+
notifyAllListeners(internals.getScopedListeners(scope));
|
|
180
107
|
return;
|
|
181
108
|
}
|
|
182
109
|
if (scope === StorageScope.Secure && key.startsWith(SECURE_WEB_PREFIX)) {
|
|
183
110
|
const plainKey = fromSecureStorageKey(key);
|
|
184
|
-
const oldValue = readCachedRawValue(StorageScope.Secure, plainKey);
|
|
111
|
+
const oldValue = internals.readCachedRawValue(StorageScope.Secure, plainKey);
|
|
185
112
|
if (newValue === null) {
|
|
186
113
|
ensureWebScopeKeyIndex(StorageScope.Secure).delete(plainKey);
|
|
187
|
-
cacheRawValue(StorageScope.Secure, plainKey, undefined);
|
|
114
|
+
internals.cacheRawValue(StorageScope.Secure, plainKey, undefined);
|
|
188
115
|
} else {
|
|
189
116
|
ensureWebScopeKeyIndex(StorageScope.Secure).add(plainKey);
|
|
190
|
-
cacheRawValue(StorageScope.Secure, plainKey, newValue);
|
|
117
|
+
internals.cacheRawValue(StorageScope.Secure, plainKey, newValue);
|
|
191
118
|
}
|
|
192
|
-
notifyKeyListeners(getScopedListeners(StorageScope.Secure), plainKey);
|
|
193
|
-
emitKeyChange(StorageScope.Secure, plainKey, oldValue, newValue ?? undefined, "external", "external");
|
|
119
|
+
notifyKeyListeners(internals.getScopedListeners(StorageScope.Secure), plainKey);
|
|
120
|
+
internals.emitKeyChange(StorageScope.Secure, plainKey, oldValue, newValue ?? undefined, "external", "external");
|
|
194
121
|
return;
|
|
195
122
|
}
|
|
196
123
|
if (scope === StorageScope.Secure && key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
197
124
|
const plainKey = fromBiometricStorageKey(key);
|
|
198
|
-
const oldValue = readCachedRawValue(StorageScope.Secure, plainKey);
|
|
125
|
+
const oldValue = internals.readCachedRawValue(StorageScope.Secure, plainKey);
|
|
199
126
|
if (newValue === null) {
|
|
200
127
|
if (withWebBackendOperation(StorageScope.Secure, "external-sync:getItem", backend => backend.getItem(toSecureStorageKey(plainKey))) === null) {
|
|
201
128
|
ensureWebScopeKeyIndex(StorageScope.Secure).delete(plainKey);
|
|
202
129
|
}
|
|
203
|
-
cacheRawValue(StorageScope.Secure, plainKey, undefined);
|
|
130
|
+
internals.cacheRawValue(StorageScope.Secure, plainKey, undefined);
|
|
204
131
|
} else {
|
|
205
132
|
ensureWebScopeKeyIndex(StorageScope.Secure).add(plainKey);
|
|
206
|
-
cacheRawValue(StorageScope.Secure, plainKey, newValue);
|
|
133
|
+
internals.cacheRawValue(StorageScope.Secure, plainKey, newValue);
|
|
207
134
|
}
|
|
208
|
-
notifyKeyListeners(getScopedListeners(StorageScope.Secure), plainKey);
|
|
209
|
-
emitKeyChange(StorageScope.Secure, plainKey, oldValue, newValue ?? undefined, "external", "external");
|
|
135
|
+
notifyKeyListeners(internals.getScopedListeners(StorageScope.Secure), plainKey);
|
|
136
|
+
internals.emitKeyChange(StorageScope.Secure, plainKey, oldValue, newValue ?? undefined, "external", "external");
|
|
210
137
|
return;
|
|
211
138
|
}
|
|
212
|
-
const oldValue = readCachedRawValue(scope, key);
|
|
139
|
+
const oldValue = internals.readCachedRawValue(scope, key);
|
|
213
140
|
if (newValue === null) {
|
|
214
141
|
ensureWebScopeKeyIndex(scope).delete(key);
|
|
215
|
-
cacheRawValue(scope, key, undefined);
|
|
142
|
+
internals.cacheRawValue(scope, key, undefined);
|
|
216
143
|
} else {
|
|
217
144
|
ensureWebScopeKeyIndex(scope).add(key);
|
|
218
|
-
cacheRawValue(scope, key, newValue);
|
|
145
|
+
internals.cacheRawValue(scope, key, newValue);
|
|
219
146
|
}
|
|
220
|
-
notifyKeyListeners(getScopedListeners(scope), key);
|
|
221
|
-
emitKeyChange(scope, key, oldValue, newValue ?? undefined, "external", "external");
|
|
147
|
+
notifyKeyListeners(internals.getScopedListeners(scope), key);
|
|
148
|
+
internals.emitKeyChange(scope, key, oldValue, newValue ?? undefined, "external", "external");
|
|
222
149
|
}
|
|
223
150
|
function handleWebStorageEvent(event) {
|
|
224
151
|
const key = event.key;
|
|
@@ -268,239 +195,6 @@ function ensureExternalSyncSubscriptions() {
|
|
|
268
195
|
subscribeToBackendChanges(StorageScope.Disk);
|
|
269
196
|
subscribeToBackendChanges(StorageScope.Secure);
|
|
270
197
|
}
|
|
271
|
-
function getScopedListeners(scope) {
|
|
272
|
-
return webScopeListeners.get(scope);
|
|
273
|
-
}
|
|
274
|
-
function getScopeRawCache(scope) {
|
|
275
|
-
return scopedRawCache.get(scope);
|
|
276
|
-
}
|
|
277
|
-
function cacheRawValue(scope, key, value) {
|
|
278
|
-
getScopeRawCache(scope).set(key, value);
|
|
279
|
-
}
|
|
280
|
-
function readCachedRawValue(scope, key) {
|
|
281
|
-
return getScopeRawCache(scope).get(key);
|
|
282
|
-
}
|
|
283
|
-
function hasCachedRawValue(scope, key) {
|
|
284
|
-
return getScopeRawCache(scope).has(key);
|
|
285
|
-
}
|
|
286
|
-
function clearScopeRawCache(scope) {
|
|
287
|
-
getScopeRawCache(scope).clear();
|
|
288
|
-
}
|
|
289
|
-
function notifyKeyListeners(registry, key) {
|
|
290
|
-
const listeners = registry.get(key);
|
|
291
|
-
if (listeners) {
|
|
292
|
-
for (const listener of listeners) {
|
|
293
|
-
listener();
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
function notifyAllListeners(registry) {
|
|
298
|
-
for (const listeners of registry.values()) {
|
|
299
|
-
for (const listener of listeners) {
|
|
300
|
-
listener();
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
function addKeyListener(registry, key, listener) {
|
|
305
|
-
let listeners = registry.get(key);
|
|
306
|
-
if (!listeners) {
|
|
307
|
-
listeners = new Set();
|
|
308
|
-
registry.set(key, listeners);
|
|
309
|
-
}
|
|
310
|
-
listeners.add(listener);
|
|
311
|
-
return () => {
|
|
312
|
-
const scopedListeners = registry.get(key);
|
|
313
|
-
if (!scopedListeners) {
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
scopedListeners.delete(listener);
|
|
317
|
-
if (scopedListeners.size === 0) {
|
|
318
|
-
registry.delete(key);
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
function getEventRawValue(scope, key) {
|
|
323
|
-
if (scope === StorageScope.Memory) {
|
|
324
|
-
const value = memoryStore.get(key);
|
|
325
|
-
return typeof value === "string" ? value : undefined;
|
|
326
|
-
}
|
|
327
|
-
return getRawValue(key, scope);
|
|
328
|
-
}
|
|
329
|
-
function createKeyChange(scope, key, oldValue, newValue, operation, source) {
|
|
330
|
-
return {
|
|
331
|
-
type: "key",
|
|
332
|
-
scope,
|
|
333
|
-
key,
|
|
334
|
-
oldValue,
|
|
335
|
-
newValue,
|
|
336
|
-
operation,
|
|
337
|
-
source
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
function hasStorageChangeObservers(scope) {
|
|
341
|
-
return storageEvents.hasListeners(scope) || eventObserver !== undefined;
|
|
342
|
-
}
|
|
343
|
-
function shouldReadPreviousEventValues(scope) {
|
|
344
|
-
if (storageEvents.hasListeners(scope)) {
|
|
345
|
-
return true;
|
|
346
|
-
}
|
|
347
|
-
if (!eventObserver) {
|
|
348
|
-
return false;
|
|
349
|
-
}
|
|
350
|
-
return scope !== StorageScope.Secure || !eventObserverRedactSecureValues;
|
|
351
|
-
}
|
|
352
|
-
const SECURE_EVENT_REDACTED_VALUE = "[secure]";
|
|
353
|
-
function redactSecureKeyChange(event) {
|
|
354
|
-
if (event.scope !== StorageScope.Secure) {
|
|
355
|
-
return event;
|
|
356
|
-
}
|
|
357
|
-
return {
|
|
358
|
-
...event,
|
|
359
|
-
oldValue: event.oldValue === undefined ? undefined : SECURE_EVENT_REDACTED_VALUE,
|
|
360
|
-
newValue: event.newValue === undefined ? undefined : SECURE_EVENT_REDACTED_VALUE
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
function eventForGlobalObserver(event) {
|
|
364
|
-
if (!eventObserverRedactSecureValues || event.scope !== StorageScope.Secure) {
|
|
365
|
-
return event;
|
|
366
|
-
}
|
|
367
|
-
if (event.type === "key") {
|
|
368
|
-
return redactSecureKeyChange(event);
|
|
369
|
-
}
|
|
370
|
-
return {
|
|
371
|
-
...event,
|
|
372
|
-
changes: event.changes.map(redactSecureKeyChange)
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
function emitKeyChange(scope, key, oldValue, newValue, operation, source) {
|
|
376
|
-
const event = createKeyChange(scope, key, oldValue, newValue, operation, source);
|
|
377
|
-
storageEvents.emitKey(event);
|
|
378
|
-
eventObserver?.(eventForGlobalObserver(event));
|
|
379
|
-
}
|
|
380
|
-
function emitBatchChange(scope, operation, source, changes) {
|
|
381
|
-
if (changes.length === 0) {
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
const event = {
|
|
385
|
-
type: "batch",
|
|
386
|
-
scope,
|
|
387
|
-
operation,
|
|
388
|
-
source,
|
|
389
|
-
changes
|
|
390
|
-
};
|
|
391
|
-
storageEvents.emitBatch(event);
|
|
392
|
-
eventObserver?.(eventForGlobalObserver(event));
|
|
393
|
-
}
|
|
394
|
-
function readPendingSecureWrite(key) {
|
|
395
|
-
return pendingSecureWrites.get(key)?.value;
|
|
396
|
-
}
|
|
397
|
-
function readPendingDiskWrite(key) {
|
|
398
|
-
return pendingDiskWrites.get(key)?.value;
|
|
399
|
-
}
|
|
400
|
-
function hasPendingDiskWrite(key) {
|
|
401
|
-
return pendingDiskWrites.has(key);
|
|
402
|
-
}
|
|
403
|
-
function hasPendingSecureWrite(key) {
|
|
404
|
-
return pendingSecureWrites.has(key);
|
|
405
|
-
}
|
|
406
|
-
function clearPendingDiskWrite(key) {
|
|
407
|
-
pendingDiskWrites.delete(key);
|
|
408
|
-
}
|
|
409
|
-
function clearPendingSecureWrite(key) {
|
|
410
|
-
pendingSecureWrites.delete(key);
|
|
411
|
-
}
|
|
412
|
-
function flushDiskWrites() {
|
|
413
|
-
diskFlushScheduled = false;
|
|
414
|
-
if (pendingDiskWrites.size === 0) {
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
const writes = Array.from(pendingDiskWrites.values());
|
|
418
|
-
pendingDiskWrites.clear();
|
|
419
|
-
const keysToSet = [];
|
|
420
|
-
const valuesToSet = [];
|
|
421
|
-
const keysToRemove = [];
|
|
422
|
-
writes.forEach(({
|
|
423
|
-
key,
|
|
424
|
-
value
|
|
425
|
-
}) => {
|
|
426
|
-
if (value === undefined) {
|
|
427
|
-
keysToRemove.push(key);
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
keysToSet.push(key);
|
|
431
|
-
valuesToSet.push(value);
|
|
432
|
-
});
|
|
433
|
-
if (keysToSet.length > 0) {
|
|
434
|
-
WebStorage.setBatch(keysToSet, valuesToSet, StorageScope.Disk);
|
|
435
|
-
}
|
|
436
|
-
if (keysToRemove.length > 0) {
|
|
437
|
-
WebStorage.removeBatch(keysToRemove, StorageScope.Disk);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
function flushSecureWrites() {
|
|
441
|
-
secureFlushScheduled = false;
|
|
442
|
-
if (pendingSecureWrites.size === 0) {
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
const writes = Array.from(pendingSecureWrites.values());
|
|
446
|
-
pendingSecureWrites.clear();
|
|
447
|
-
const groupedSetWrites = new Map();
|
|
448
|
-
const keysToRemove = [];
|
|
449
|
-
writes.forEach(({
|
|
450
|
-
key,
|
|
451
|
-
value,
|
|
452
|
-
accessControl
|
|
453
|
-
}) => {
|
|
454
|
-
if (value === undefined) {
|
|
455
|
-
keysToRemove.push(key);
|
|
456
|
-
} else {
|
|
457
|
-
const resolvedAccessControl = accessControl ?? secureDefaultAccessControl;
|
|
458
|
-
const existingGroup = groupedSetWrites.get(resolvedAccessControl);
|
|
459
|
-
const group = existingGroup ?? {
|
|
460
|
-
keys: [],
|
|
461
|
-
values: []
|
|
462
|
-
};
|
|
463
|
-
group.keys.push(key);
|
|
464
|
-
group.values.push(value);
|
|
465
|
-
if (!existingGroup) {
|
|
466
|
-
groupedSetWrites.set(resolvedAccessControl, group);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
groupedSetWrites.forEach((group, accessControl) => {
|
|
471
|
-
WebStorage.setSecureAccessControl(accessControl);
|
|
472
|
-
WebStorage.setBatch(group.keys, group.values, StorageScope.Secure);
|
|
473
|
-
});
|
|
474
|
-
if (keysToRemove.length > 0) {
|
|
475
|
-
WebStorage.removeBatch(keysToRemove, StorageScope.Secure);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
function scheduleDiskWrite(key, value) {
|
|
479
|
-
pendingDiskWrites.set(key, {
|
|
480
|
-
key,
|
|
481
|
-
value
|
|
482
|
-
});
|
|
483
|
-
if (diskFlushScheduled) {
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
diskFlushScheduled = true;
|
|
487
|
-
runMicrotask(flushDiskWrites);
|
|
488
|
-
}
|
|
489
|
-
function scheduleSecureWrite(key, value, accessControl) {
|
|
490
|
-
const pendingWrite = {
|
|
491
|
-
key,
|
|
492
|
-
value
|
|
493
|
-
};
|
|
494
|
-
if (accessControl !== undefined) {
|
|
495
|
-
pendingWrite.accessControl = accessControl;
|
|
496
|
-
}
|
|
497
|
-
pendingSecureWrites.set(key, pendingWrite);
|
|
498
|
-
if (secureFlushScheduled) {
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
secureFlushScheduled = true;
|
|
502
|
-
runMicrotask(flushSecureWrites);
|
|
503
|
-
}
|
|
504
198
|
const WebStorage = {
|
|
505
199
|
name: "Storage",
|
|
506
200
|
equals: other => other === WebStorage,
|
|
@@ -514,7 +208,7 @@ const WebStorage = {
|
|
|
514
208
|
backend.setItem(storageKey, value);
|
|
515
209
|
});
|
|
516
210
|
ensureWebScopeKeyIndex(scope).add(key);
|
|
517
|
-
notifyKeyListeners(getScopedListeners(scope), key);
|
|
211
|
+
notifyKeyListeners(internals.getScopedListeners(scope), key);
|
|
518
212
|
},
|
|
519
213
|
get: (key, scope) => {
|
|
520
214
|
if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
|
|
@@ -543,7 +237,7 @@ const WebStorage = {
|
|
|
543
237
|
});
|
|
544
238
|
}
|
|
545
239
|
ensureWebScopeKeyIndex(scope).delete(key);
|
|
546
|
-
notifyKeyListeners(getScopedListeners(scope), key);
|
|
240
|
+
notifyKeyListeners(internals.getScopedListeners(scope), key);
|
|
547
241
|
},
|
|
548
242
|
clear: scope => {
|
|
549
243
|
if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
|
|
@@ -553,7 +247,7 @@ const WebStorage = {
|
|
|
553
247
|
backend.clear();
|
|
554
248
|
});
|
|
555
249
|
ensureWebScopeKeyIndex(scope).clear();
|
|
556
|
-
notifyAllListeners(getScopedListeners(scope));
|
|
250
|
+
notifyAllListeners(internals.getScopedListeners(scope));
|
|
557
251
|
},
|
|
558
252
|
setBatch: (keys, values, scope) => {
|
|
559
253
|
if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
|
|
@@ -581,7 +275,7 @@ const WebStorage = {
|
|
|
581
275
|
});
|
|
582
276
|
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
583
277
|
entries.forEach(([storageKey]) => keyIndex.add(scope === StorageScope.Secure ? storageKey.slice(SECURE_WEB_PREFIX.length) : storageKey));
|
|
584
|
-
const listeners = getScopedListeners(scope);
|
|
278
|
+
const listeners = internals.getScopedListeners(scope);
|
|
585
279
|
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
586
280
|
},
|
|
587
281
|
getBatch: (keys, scope) => {
|
|
@@ -625,7 +319,7 @@ const WebStorage = {
|
|
|
625
319
|
}
|
|
626
320
|
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
627
321
|
keys.forEach(key => keyIndex.delete(key));
|
|
628
|
-
const listeners = getScopedListeners(scope);
|
|
322
|
+
const listeners = internals.getScopedListeners(scope);
|
|
629
323
|
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
630
324
|
},
|
|
631
325
|
removeByPrefix: (prefix, scope) => {
|
|
@@ -682,7 +376,7 @@ const WebStorage = {
|
|
|
682
376
|
backend.setItem(toSecureStorageKey(key), value);
|
|
683
377
|
});
|
|
684
378
|
ensureWebScopeKeyIndex(StorageScope.Secure).add(key);
|
|
685
|
-
notifyKeyListeners(getScopedListeners(StorageScope.Secure), key);
|
|
379
|
+
notifyKeyListeners(internals.getScopedListeners(StorageScope.Secure), key);
|
|
686
380
|
return;
|
|
687
381
|
}
|
|
688
382
|
if (typeof __DEV__ !== "undefined" && __DEV__ && !hasWarnedAboutWebBiometricFallback) {
|
|
@@ -691,7 +385,7 @@ const WebStorage = {
|
|
|
691
385
|
}
|
|
692
386
|
withWebBackendOperation(StorageScope.Secure, "setSecureBiometric", backend => backend.setItem(toBiometricStorageKey(key), value));
|
|
693
387
|
ensureWebScopeKeyIndex(StorageScope.Secure).add(key);
|
|
694
|
-
notifyKeyListeners(getScopedListeners(StorageScope.Secure), key);
|
|
388
|
+
notifyKeyListeners(internals.getScopedListeners(StorageScope.Secure), key);
|
|
695
389
|
},
|
|
696
390
|
getSecureBiometric: key => {
|
|
697
391
|
const value = withWebBackendOperation(StorageScope.Secure, "getSecureBiometric", backend => backend.getItem(toBiometricStorageKey(key)));
|
|
@@ -702,7 +396,7 @@ const WebStorage = {
|
|
|
702
396
|
if (withWebBackendOperation(StorageScope.Secure, "deleteSecureBiometric:getItem", backend => backend.getItem(toSecureStorageKey(key))) === null) {
|
|
703
397
|
ensureWebScopeKeyIndex(StorageScope.Secure).delete(key);
|
|
704
398
|
}
|
|
705
|
-
notifyKeyListeners(getScopedListeners(StorageScope.Secure), key);
|
|
399
|
+
notifyKeyListeners(internals.getScopedListeners(StorageScope.Secure), key);
|
|
706
400
|
},
|
|
707
401
|
hasSecureBiometric: key => {
|
|
708
402
|
return withWebBackendOperation(StorageScope.Secure, "hasSecureBiometric", backend => backend.getItem(toBiometricStorageKey(key))) !== null;
|
|
@@ -729,364 +423,42 @@ const WebStorage = {
|
|
|
729
423
|
keyIndex.delete(key);
|
|
730
424
|
}
|
|
731
425
|
});
|
|
732
|
-
const listeners = getScopedListeners(StorageScope.Secure);
|
|
426
|
+
const listeners = internals.getScopedListeners(StorageScope.Secure);
|
|
733
427
|
keysToNotify.forEach(key => notifyKeyListeners(listeners, key));
|
|
734
428
|
}
|
|
735
429
|
};
|
|
736
|
-
function
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
memoryStore.set(key, value);
|
|
755
|
-
notifyKeyListeners(memoryListeners, key);
|
|
756
|
-
emitKeyChange(scope, key, oldValue, value, "set", "memory");
|
|
757
|
-
return;
|
|
758
|
-
}
|
|
759
|
-
if (scope === StorageScope.Disk) {
|
|
760
|
-
cacheRawValue(scope, key, value);
|
|
761
|
-
if (diskWritesAsync) {
|
|
762
|
-
scheduleDiskWrite(key, value);
|
|
763
|
-
emitKeyChange(scope, key, oldValue, value, "set", "web");
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
flushDiskWrites();
|
|
767
|
-
clearPendingDiskWrite(key);
|
|
768
|
-
}
|
|
769
|
-
if (scope === StorageScope.Secure) {
|
|
770
|
-
flushSecureWrites();
|
|
771
|
-
clearPendingSecureWrite(key);
|
|
772
|
-
}
|
|
773
|
-
WebStorage.set(key, value, scope);
|
|
774
|
-
cacheRawValue(scope, key, value);
|
|
775
|
-
emitKeyChange(scope, key, oldValue, value, "set", "web");
|
|
776
|
-
}
|
|
777
|
-
function removeRawValue(key, scope) {
|
|
778
|
-
assertValidScope(scope);
|
|
779
|
-
const oldValue = getEventRawValue(scope, key);
|
|
780
|
-
if (scope === StorageScope.Memory) {
|
|
781
|
-
memoryStore.delete(key);
|
|
782
|
-
notifyKeyListeners(memoryListeners, key);
|
|
783
|
-
emitKeyChange(scope, key, oldValue, undefined, "remove", "memory");
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
if (scope === StorageScope.Disk) {
|
|
787
|
-
cacheRawValue(scope, key, undefined);
|
|
788
|
-
if (diskWritesAsync) {
|
|
789
|
-
scheduleDiskWrite(key, undefined);
|
|
790
|
-
emitKeyChange(scope, key, oldValue, undefined, "remove", "web");
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
|
-
flushDiskWrites();
|
|
794
|
-
clearPendingDiskWrite(key);
|
|
795
|
-
}
|
|
796
|
-
if (scope === StorageScope.Secure) {
|
|
797
|
-
flushSecureWrites();
|
|
798
|
-
clearPendingSecureWrite(key);
|
|
799
|
-
}
|
|
800
|
-
WebStorage.remove(key, scope);
|
|
801
|
-
cacheRawValue(scope, key, undefined);
|
|
802
|
-
emitKeyChange(scope, key, oldValue, undefined, "remove", "web");
|
|
803
|
-
}
|
|
804
|
-
function readMigrationVersion(scope) {
|
|
805
|
-
const raw = getRawValue(MIGRATION_VERSION_KEY, scope);
|
|
806
|
-
if (raw === undefined) {
|
|
807
|
-
return 0;
|
|
808
|
-
}
|
|
809
|
-
const parsed = Number.parseInt(raw, 10);
|
|
810
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
811
|
-
}
|
|
812
|
-
function writeMigrationVersion(scope, version) {
|
|
813
|
-
setRawValue(MIGRATION_VERSION_KEY, String(version), scope);
|
|
430
|
+
function buildWebAdapter(coreInternals) {
|
|
431
|
+
internals = coreInternals;
|
|
432
|
+
return {
|
|
433
|
+
backend: WebStorage,
|
|
434
|
+
changeSource: "web",
|
|
435
|
+
applyAccessControlOnSecureRawWrite: false,
|
|
436
|
+
flushDiskWritesOnImport: true,
|
|
437
|
+
ensureScopeSubscription: () => {
|
|
438
|
+
ensureExternalSyncSubscriptions();
|
|
439
|
+
},
|
|
440
|
+
maybeCleanupScopeSubscription: () => {},
|
|
441
|
+
onWillEmitChanges: () => {},
|
|
442
|
+
getSecureMetadataProfile: () => ({
|
|
443
|
+
backend: getBackendName(StorageScope.Secure, webSecureStorageBackend),
|
|
444
|
+
encrypted: getWebSecureEncryptionStatus(webSecureStorageBackend),
|
|
445
|
+
hardwareBacked: "unavailable"
|
|
446
|
+
})
|
|
447
|
+
};
|
|
814
448
|
}
|
|
449
|
+
const core = createStorageCore(buildWebAdapter);
|
|
815
450
|
export const storage = {
|
|
816
|
-
|
|
817
|
-
assertValidScope(scope);
|
|
818
|
-
if (scope !== StorageScope.Memory) {
|
|
819
|
-
ensureExternalSyncSubscriptions();
|
|
820
|
-
}
|
|
821
|
-
return storageEvents.subscribe(scope, listener);
|
|
822
|
-
},
|
|
823
|
-
subscribeKey: (scope, key, listener) => {
|
|
824
|
-
assertValidScope(scope);
|
|
825
|
-
if (scope !== StorageScope.Memory) {
|
|
826
|
-
ensureExternalSyncSubscriptions();
|
|
827
|
-
}
|
|
828
|
-
return storageEvents.subscribeKey(scope, key, listener);
|
|
829
|
-
},
|
|
830
|
-
subscribePrefix: (scope, prefix, listener) => {
|
|
831
|
-
assertValidScope(scope);
|
|
832
|
-
if (scope !== StorageScope.Memory) {
|
|
833
|
-
ensureExternalSyncSubscriptions();
|
|
834
|
-
}
|
|
835
|
-
return storageEvents.subscribePrefix(scope, prefix, listener);
|
|
836
|
-
},
|
|
837
|
-
subscribeNamespace: (namespace, scope, listener) => {
|
|
838
|
-
return storage.subscribePrefix(scope, prefixKey(namespace, ""), listener);
|
|
839
|
-
},
|
|
840
|
-
setEventObserver: (observer, options = {}) => {
|
|
841
|
-
eventObserver = observer;
|
|
842
|
-
eventObserverRedactSecureValues = options.redactSecureValues !== false;
|
|
843
|
-
if (observer) {
|
|
844
|
-
ensureExternalSyncSubscriptions();
|
|
845
|
-
}
|
|
846
|
-
},
|
|
847
|
-
clear: scope => {
|
|
848
|
-
measureOperation("storage:clear", scope, () => {
|
|
849
|
-
const previousValues = shouldReadPreviousEventValues(scope) ? storage.getAll(scope) : {};
|
|
850
|
-
if (scope === StorageScope.Memory) {
|
|
851
|
-
memoryStore.clear();
|
|
852
|
-
notifyAllListeners(memoryListeners);
|
|
853
|
-
emitBatchChange(scope, "clear", "memory", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clear", "memory")));
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
if (scope === StorageScope.Disk) {
|
|
857
|
-
flushDiskWrites();
|
|
858
|
-
pendingDiskWrites.clear();
|
|
859
|
-
}
|
|
860
|
-
if (scope === StorageScope.Secure) {
|
|
861
|
-
flushSecureWrites();
|
|
862
|
-
pendingSecureWrites.clear();
|
|
863
|
-
}
|
|
864
|
-
clearScopeRawCache(scope);
|
|
865
|
-
WebStorage.clear(scope);
|
|
866
|
-
emitBatchChange(scope, "clear", "web", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clear", "web")));
|
|
867
|
-
});
|
|
868
|
-
},
|
|
869
|
-
clearAll: () => {
|
|
870
|
-
measureOperation("storage:clearAll", StorageScope.Memory, () => {
|
|
871
|
-
storage.clear(StorageScope.Memory);
|
|
872
|
-
storage.clear(StorageScope.Disk);
|
|
873
|
-
storage.clear(StorageScope.Secure);
|
|
874
|
-
}, 3);
|
|
875
|
-
},
|
|
876
|
-
clearNamespace: (namespace, scope) => {
|
|
877
|
-
measureOperation("storage:clearNamespace", scope, () => {
|
|
878
|
-
assertValidScope(scope);
|
|
879
|
-
if (scope === StorageScope.Memory) {
|
|
880
|
-
const affectedKeys = Array.from(memoryStore.keys()).filter(key => isNamespaced(key, namespace));
|
|
881
|
-
const previousValues = affectedKeys.map(key => ({
|
|
882
|
-
key,
|
|
883
|
-
value: getEventRawValue(scope, key)
|
|
884
|
-
}));
|
|
885
|
-
if (affectedKeys.length === 0) {
|
|
886
|
-
return;
|
|
887
|
-
}
|
|
888
|
-
affectedKeys.forEach(key => {
|
|
889
|
-
memoryStore.delete(key);
|
|
890
|
-
});
|
|
891
|
-
affectedKeys.forEach(key => notifyKeyListeners(memoryListeners, key));
|
|
892
|
-
emitBatchChange(scope, "clearNamespace", "memory", previousValues.map(({
|
|
893
|
-
key,
|
|
894
|
-
value
|
|
895
|
-
}) => createKeyChange(scope, key, value, undefined, "clearNamespace", "memory")));
|
|
896
|
-
return;
|
|
897
|
-
}
|
|
898
|
-
const keyPrefix = prefixKey(namespace, "");
|
|
899
|
-
const previousValues = shouldReadPreviousEventValues(scope) ? storage.getByPrefix(keyPrefix, scope) : {};
|
|
900
|
-
if (scope === StorageScope.Disk) {
|
|
901
|
-
flushDiskWrites();
|
|
902
|
-
}
|
|
903
|
-
if (scope === StorageScope.Secure) {
|
|
904
|
-
flushSecureWrites();
|
|
905
|
-
}
|
|
906
|
-
const scopeCache = getScopeRawCache(scope);
|
|
907
|
-
for (const key of scopeCache.keys()) {
|
|
908
|
-
if (isNamespaced(key, namespace)) {
|
|
909
|
-
scopeCache.delete(key);
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
WebStorage.removeByPrefix(keyPrefix, scope);
|
|
913
|
-
emitBatchChange(scope, "clearNamespace", "web", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clearNamespace", "web")));
|
|
914
|
-
});
|
|
915
|
-
},
|
|
916
|
-
clearBiometric: () => {
|
|
917
|
-
measureOperation("storage:clearBiometric", StorageScope.Secure, () => {
|
|
918
|
-
WebStorage.clearSecureBiometric();
|
|
919
|
-
});
|
|
920
|
-
},
|
|
921
|
-
has: (key, scope) => {
|
|
922
|
-
return measureOperation("storage:has", scope, () => {
|
|
923
|
-
assertValidScope(scope);
|
|
924
|
-
if (scope === StorageScope.Memory) return memoryStore.has(key);
|
|
925
|
-
if (scope === StorageScope.Disk) {
|
|
926
|
-
flushDiskWrites();
|
|
927
|
-
}
|
|
928
|
-
if (scope === StorageScope.Secure) {
|
|
929
|
-
flushSecureWrites();
|
|
930
|
-
}
|
|
931
|
-
return WebStorage.has(key, scope);
|
|
932
|
-
});
|
|
933
|
-
},
|
|
934
|
-
getAllKeys: scope => {
|
|
935
|
-
return measureOperation("storage:getAllKeys", scope, () => {
|
|
936
|
-
assertValidScope(scope);
|
|
937
|
-
if (scope === StorageScope.Memory) return Array.from(memoryStore.keys());
|
|
938
|
-
if (scope === StorageScope.Disk) {
|
|
939
|
-
flushDiskWrites();
|
|
940
|
-
}
|
|
941
|
-
if (scope === StorageScope.Secure) {
|
|
942
|
-
flushSecureWrites();
|
|
943
|
-
}
|
|
944
|
-
return WebStorage.getAllKeys(scope);
|
|
945
|
-
});
|
|
946
|
-
},
|
|
947
|
-
getKeysByPrefix: (prefix, scope) => {
|
|
948
|
-
return measureOperation("storage:getKeysByPrefix", scope, () => {
|
|
949
|
-
assertValidScope(scope);
|
|
950
|
-
if (scope === StorageScope.Memory) {
|
|
951
|
-
return Array.from(memoryStore.keys()).filter(key => key.startsWith(prefix));
|
|
952
|
-
}
|
|
953
|
-
if (scope === StorageScope.Disk) {
|
|
954
|
-
flushDiskWrites();
|
|
955
|
-
}
|
|
956
|
-
if (scope === StorageScope.Secure) {
|
|
957
|
-
flushSecureWrites();
|
|
958
|
-
}
|
|
959
|
-
return WebStorage.getKeysByPrefix(prefix, scope);
|
|
960
|
-
});
|
|
961
|
-
},
|
|
962
|
-
getByPrefix: (prefix, scope) => {
|
|
963
|
-
return measureOperation("storage:getByPrefix", scope, () => {
|
|
964
|
-
const result = {};
|
|
965
|
-
const keys = storage.getKeysByPrefix(prefix, scope);
|
|
966
|
-
if (keys.length === 0) {
|
|
967
|
-
return result;
|
|
968
|
-
}
|
|
969
|
-
if (scope === StorageScope.Memory) {
|
|
970
|
-
keys.forEach(key => {
|
|
971
|
-
const value = memoryStore.get(key);
|
|
972
|
-
if (typeof value === "string") {
|
|
973
|
-
result[key] = value;
|
|
974
|
-
}
|
|
975
|
-
});
|
|
976
|
-
return result;
|
|
977
|
-
}
|
|
978
|
-
if (scope === StorageScope.Disk) {
|
|
979
|
-
flushDiskWrites();
|
|
980
|
-
}
|
|
981
|
-
if (scope === StorageScope.Secure) {
|
|
982
|
-
flushSecureWrites();
|
|
983
|
-
}
|
|
984
|
-
const values = WebStorage.getBatch(keys, scope);
|
|
985
|
-
keys.forEach((key, index) => {
|
|
986
|
-
const value = values[index];
|
|
987
|
-
if (value !== undefined) {
|
|
988
|
-
result[key] = value;
|
|
989
|
-
}
|
|
990
|
-
});
|
|
991
|
-
return result;
|
|
992
|
-
});
|
|
993
|
-
},
|
|
994
|
-
getAll: scope => {
|
|
995
|
-
return measureOperation("storage:getAll", scope, () => {
|
|
996
|
-
assertValidScope(scope);
|
|
997
|
-
const result = {};
|
|
998
|
-
if (scope === StorageScope.Memory) {
|
|
999
|
-
memoryStore.forEach((value, key) => {
|
|
1000
|
-
if (typeof value === "string") result[key] = value;
|
|
1001
|
-
});
|
|
1002
|
-
return result;
|
|
1003
|
-
}
|
|
1004
|
-
if (scope === StorageScope.Disk) {
|
|
1005
|
-
flushDiskWrites();
|
|
1006
|
-
}
|
|
1007
|
-
if (scope === StorageScope.Secure) {
|
|
1008
|
-
flushSecureWrites();
|
|
1009
|
-
}
|
|
1010
|
-
const keys = WebStorage.getAllKeys(scope);
|
|
1011
|
-
if (keys.length === 0) return {};
|
|
1012
|
-
const values = WebStorage.getBatch(keys, scope);
|
|
1013
|
-
keys.forEach((key, index) => {
|
|
1014
|
-
const val = values[index];
|
|
1015
|
-
if (val !== undefined && val !== null) {
|
|
1016
|
-
result[key] = val;
|
|
1017
|
-
}
|
|
1018
|
-
});
|
|
1019
|
-
return result;
|
|
1020
|
-
});
|
|
1021
|
-
},
|
|
1022
|
-
export: (scope, options = {}) => {
|
|
1023
|
-
if (scope === StorageScope.Secure && options.includeSecureValues !== true) {
|
|
1024
|
-
throw new Error("NitroStorage: exporting Secure scope exposes raw secret values. Pass { includeSecureValues: true } or use exportSecureUnsafe().");
|
|
1025
|
-
}
|
|
1026
|
-
return measureOperation("storage:export", scope, () => storage.getAll(scope));
|
|
1027
|
-
},
|
|
1028
|
-
exportSecureUnsafe: () => {
|
|
1029
|
-
return measureOperation("storage:exportSecureUnsafe", StorageScope.Secure, () => storage.getAll(StorageScope.Secure));
|
|
1030
|
-
},
|
|
1031
|
-
size: scope => {
|
|
1032
|
-
return measureOperation("storage:size", scope, () => {
|
|
1033
|
-
assertValidScope(scope);
|
|
1034
|
-
if (scope === StorageScope.Memory) return memoryStore.size;
|
|
1035
|
-
if (scope === StorageScope.Disk) {
|
|
1036
|
-
flushDiskWrites();
|
|
1037
|
-
}
|
|
1038
|
-
if (scope === StorageScope.Secure) {
|
|
1039
|
-
flushSecureWrites();
|
|
1040
|
-
}
|
|
1041
|
-
return WebStorage.size(scope);
|
|
1042
|
-
});
|
|
1043
|
-
},
|
|
451
|
+
...core.storage,
|
|
1044
452
|
setAccessControl: level => {
|
|
1045
453
|
assertAccessControlLevel(level);
|
|
1046
|
-
|
|
1047
|
-
recordMetric("storage:setAccessControl", StorageScope.Secure, 0);
|
|
454
|
+
internals.setSecureDefaultAccessControl(level);
|
|
455
|
+
internals.recordMetric("storage:setAccessControl", StorageScope.Secure, 0);
|
|
1048
456
|
},
|
|
1049
457
|
setSecureWritesAsync: _enabled => {
|
|
1050
|
-
recordMetric("storage:setSecureWritesAsync", StorageScope.Secure, 0);
|
|
1051
|
-
},
|
|
1052
|
-
setDiskWritesAsync: enabled => {
|
|
1053
|
-
measureOperation("storage:setDiskWritesAsync", StorageScope.Disk, () => {
|
|
1054
|
-
diskWritesAsync = enabled;
|
|
1055
|
-
if (!enabled) {
|
|
1056
|
-
flushDiskWrites();
|
|
1057
|
-
}
|
|
1058
|
-
});
|
|
1059
|
-
},
|
|
1060
|
-
flushDiskWrites: () => {
|
|
1061
|
-
measureOperation("storage:flushDiskWrites", StorageScope.Disk, () => {
|
|
1062
|
-
flushDiskWrites();
|
|
1063
|
-
});
|
|
1064
|
-
},
|
|
1065
|
-
flushSecureWrites: () => {
|
|
1066
|
-
measureOperation("storage:flushSecureWrites", StorageScope.Secure, () => {
|
|
1067
|
-
flushSecureWrites();
|
|
1068
|
-
});
|
|
458
|
+
internals.recordMetric("storage:setSecureWritesAsync", StorageScope.Secure, 0);
|
|
1069
459
|
},
|
|
1070
460
|
setKeychainAccessGroup: _group => {
|
|
1071
|
-
recordMetric("storage:setKeychainAccessGroup", StorageScope.Secure, 0);
|
|
1072
|
-
},
|
|
1073
|
-
setMetricsObserver: observer => {
|
|
1074
|
-
metricsObserver = observer;
|
|
1075
|
-
},
|
|
1076
|
-
getMetricsSnapshot: () => {
|
|
1077
|
-
const snapshot = {};
|
|
1078
|
-
metricsCounters.forEach((value, key) => {
|
|
1079
|
-
snapshot[key] = {
|
|
1080
|
-
count: value.count,
|
|
1081
|
-
totalDurationMs: value.totalDurationMs,
|
|
1082
|
-
avgDurationMs: value.count === 0 ? 0 : value.totalDurationMs / value.count,
|
|
1083
|
-
maxDurationMs: value.maxDurationMs
|
|
1084
|
-
};
|
|
1085
|
-
});
|
|
1086
|
-
return snapshot;
|
|
1087
|
-
},
|
|
1088
|
-
resetMetrics: () => {
|
|
1089
|
-
metricsCounters.clear();
|
|
461
|
+
internals.recordMetric("storage:setKeychainAccessGroup", StorageScope.Secure, 0);
|
|
1090
462
|
},
|
|
1091
463
|
getCapabilities: () => ({
|
|
1092
464
|
platform: "web",
|
|
@@ -1123,85 +495,24 @@ export const storage = {
|
|
|
1123
495
|
persistsTimestamps: false
|
|
1124
496
|
}
|
|
1125
497
|
};
|
|
1126
|
-
},
|
|
1127
|
-
getSecureMetadata: key => {
|
|
1128
|
-
return measureOperation("storage:getSecureMetadata", StorageScope.Secure, () => {
|
|
1129
|
-
flushSecureWrites();
|
|
1130
|
-
const biometricProtected = WebStorage.hasSecureBiometric(key);
|
|
1131
|
-
const exists = biometricProtected || WebStorage.has(key, StorageScope.Secure);
|
|
1132
|
-
let kind = "missing";
|
|
1133
|
-
if (exists) {
|
|
1134
|
-
kind = biometricProtected ? "biometric" : "secure";
|
|
1135
|
-
}
|
|
1136
|
-
return {
|
|
1137
|
-
key,
|
|
1138
|
-
exists,
|
|
1139
|
-
kind,
|
|
1140
|
-
backend: getBackendName(StorageScope.Secure, webSecureStorageBackend),
|
|
1141
|
-
encrypted: getWebSecureEncryptionStatus(webSecureStorageBackend),
|
|
1142
|
-
hardwareBacked: "unavailable",
|
|
1143
|
-
biometricProtected,
|
|
1144
|
-
valueExposed: false
|
|
1145
|
-
};
|
|
1146
|
-
});
|
|
1147
|
-
},
|
|
1148
|
-
getAllSecureMetadata: () => {
|
|
1149
|
-
return measureOperation("storage:getAllSecureMetadata", StorageScope.Secure, () => {
|
|
1150
|
-
flushSecureWrites();
|
|
1151
|
-
return WebStorage.getAllKeys(StorageScope.Secure).map(key => storage.getSecureMetadata(key));
|
|
1152
|
-
});
|
|
1153
|
-
},
|
|
1154
|
-
getString: (key, scope) => {
|
|
1155
|
-
return measureOperation("storage:getString", scope, () => {
|
|
1156
|
-
return getRawValue(key, scope);
|
|
1157
|
-
});
|
|
1158
|
-
},
|
|
1159
|
-
setString: (key, value, scope) => {
|
|
1160
|
-
measureOperation("storage:setString", scope, () => {
|
|
1161
|
-
setRawValue(key, value, scope);
|
|
1162
|
-
});
|
|
1163
|
-
},
|
|
1164
|
-
deleteString: (key, scope) => {
|
|
1165
|
-
measureOperation("storage:deleteString", scope, () => {
|
|
1166
|
-
removeRawValue(key, scope);
|
|
1167
|
-
});
|
|
1168
|
-
},
|
|
1169
|
-
import: (data, scope) => {
|
|
1170
|
-
const keys = Object.keys(data);
|
|
1171
|
-
measureOperation("storage:import", scope, () => {
|
|
1172
|
-
assertValidScope(scope);
|
|
1173
|
-
if (keys.length === 0) return;
|
|
1174
|
-
const values = keys.map(k => data[k]);
|
|
1175
|
-
const changes = keys.map((key, index) => createKeyChange(scope, key, getEventRawValue(scope, key), values[index], "import", scope === StorageScope.Memory ? "memory" : "web"));
|
|
1176
|
-
if (scope === StorageScope.Memory) {
|
|
1177
|
-
keys.forEach((key, index) => {
|
|
1178
|
-
memoryStore.set(key, values[index]);
|
|
1179
|
-
});
|
|
1180
|
-
keys.forEach(key => notifyKeyListeners(memoryListeners, key));
|
|
1181
|
-
emitBatchChange(scope, "import", "memory", changes);
|
|
1182
|
-
return;
|
|
1183
|
-
}
|
|
1184
|
-
if (scope === StorageScope.Secure) {
|
|
1185
|
-
flushSecureWrites();
|
|
1186
|
-
WebStorage.setSecureAccessControl(secureDefaultAccessControl);
|
|
1187
|
-
}
|
|
1188
|
-
if (scope === StorageScope.Disk) {
|
|
1189
|
-
flushDiskWrites();
|
|
1190
|
-
}
|
|
1191
|
-
WebStorage.setBatch(keys, values, scope);
|
|
1192
|
-
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
1193
|
-
emitBatchChange(scope, "import", "web", changes);
|
|
1194
|
-
}, keys.length);
|
|
1195
498
|
}
|
|
1196
499
|
};
|
|
500
|
+
export const createStorageItem = core.createStorageItem;
|
|
501
|
+
export const getBatch = core.getBatch;
|
|
502
|
+
export const setBatch = core.setBatch;
|
|
503
|
+
export const removeBatch = core.removeBatch;
|
|
504
|
+
export const registerMigration = core.registerMigration;
|
|
505
|
+
export const migrateToLatest = core.migrateToLatest;
|
|
506
|
+
export const runTransaction = core.runTransaction;
|
|
507
|
+
export const createSecureAuthStorage = core.createSecureAuthStorage;
|
|
1197
508
|
export function setWebSecureStorageBackend(backend) {
|
|
1198
509
|
const previousBackend = webSecureStorageBackend;
|
|
1199
510
|
const nextBackend = backend ?? createDefaultSecureBackend();
|
|
1200
|
-
|
|
511
|
+
internals.clearAllPendingSecureWrites();
|
|
1201
512
|
resetBackendChangeSubscription(StorageScope.Secure);
|
|
1202
513
|
webSecureStorageBackend = nextBackend;
|
|
1203
514
|
hydratedWebScopeKeyIndex.delete(StorageScope.Secure);
|
|
1204
|
-
clearScopeRawCache(StorageScope.Secure);
|
|
515
|
+
internals.clearScopeRawCache(StorageScope.Secure);
|
|
1205
516
|
ensureExternalSyncSubscriptions();
|
|
1206
517
|
if (previousBackend !== nextBackend) {
|
|
1207
518
|
closeWebBackend(StorageScope.Secure, previousBackend);
|
|
@@ -1213,11 +524,11 @@ export function getWebSecureStorageBackend() {
|
|
|
1213
524
|
export function setWebDiskStorageBackend(backend) {
|
|
1214
525
|
const previousBackend = webDiskStorageBackend;
|
|
1215
526
|
const nextBackend = backend ?? createDefaultDiskBackend();
|
|
1216
|
-
|
|
527
|
+
internals.clearAllPendingDiskWrites();
|
|
1217
528
|
resetBackendChangeSubscription(StorageScope.Disk);
|
|
1218
529
|
webDiskStorageBackend = nextBackend;
|
|
1219
530
|
hydratedWebScopeKeyIndex.delete(StorageScope.Disk);
|
|
1220
|
-
clearScopeRawCache(StorageScope.Disk);
|
|
531
|
+
internals.clearScopeRawCache(StorageScope.Disk);
|
|
1221
532
|
ensureExternalSyncSubscriptions();
|
|
1222
533
|
if (previousBackend !== nextBackend) {
|
|
1223
534
|
closeWebBackend(StorageScope.Disk, previousBackend);
|
|
@@ -1227,8 +538,8 @@ export function getWebDiskStorageBackend() {
|
|
|
1227
538
|
return webDiskStorageBackend;
|
|
1228
539
|
}
|
|
1229
540
|
export async function flushWebStorageBackends() {
|
|
1230
|
-
flushDiskWrites();
|
|
1231
|
-
flushSecureWrites();
|
|
541
|
+
internals.flushDiskWrites();
|
|
542
|
+
internals.flushSecureWrites();
|
|
1232
543
|
const flushes = [];
|
|
1233
544
|
const diskFlush = webDiskStorageBackend?.flush;
|
|
1234
545
|
const secureFlush = webSecureStorageBackend?.flush;
|
|
@@ -1240,784 +551,6 @@ export async function flushWebStorageBackends() {
|
|
|
1240
551
|
}
|
|
1241
552
|
await Promise.all(flushes);
|
|
1242
553
|
}
|
|
1243
|
-
function canUseRawBatchPath(item) {
|
|
1244
|
-
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
|
|
1245
|
-
}
|
|
1246
|
-
function canUseSecureRawBatchPath(item) {
|
|
1247
|
-
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true;
|
|
1248
|
-
}
|
|
1249
|
-
function defaultSerialize(value) {
|
|
1250
|
-
return serializeWithPrimitiveFastPath(value);
|
|
1251
|
-
}
|
|
1252
|
-
function defaultDeserialize(value) {
|
|
1253
|
-
return deserializeWithPrimitiveFastPath(value);
|
|
1254
|
-
}
|
|
1255
|
-
export function createStorageItem(config) {
|
|
1256
|
-
const storageKey = prefixKey(config.namespace, config.key);
|
|
1257
|
-
const serialize = config.serialize ?? defaultSerialize;
|
|
1258
|
-
const deserialize = config.deserialize ?? defaultDeserialize;
|
|
1259
|
-
const isMemory = config.scope === StorageScope.Memory;
|
|
1260
|
-
const resolvedBiometricLevel = config.scope === StorageScope.Secure ? config.biometricLevel ?? (config.biometric === true ? BiometricLevel.BiometryOnly : BiometricLevel.None) : BiometricLevel.None;
|
|
1261
|
-
const isBiometric = resolvedBiometricLevel !== BiometricLevel.None;
|
|
1262
|
-
const secureAccessControl = config.accessControl;
|
|
1263
|
-
const validate = config.validate;
|
|
1264
|
-
const onValidationError = config.onValidationError;
|
|
1265
|
-
const expiration = config.expiration;
|
|
1266
|
-
const onExpired = config.onExpired;
|
|
1267
|
-
const expirationTtlMs = expiration?.ttlMs;
|
|
1268
|
-
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
1269
|
-
const readCache = !isMemory && config.readCache === true;
|
|
1270
|
-
const coalesceDiskWrites = config.scope === StorageScope.Disk && config.coalesceDiskWrites === true;
|
|
1271
|
-
const coalesceSecureWrites = config.scope === StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
|
|
1272
|
-
const defaultValue = config.defaultValue;
|
|
1273
|
-
const nonMemoryScope = config.scope === StorageScope.Disk ? StorageScope.Disk : config.scope === StorageScope.Secure ? StorageScope.Secure : null;
|
|
1274
|
-
if (expiration && expiration.ttlMs <= 0) {
|
|
1275
|
-
throw new Error("expiration.ttlMs must be greater than 0.");
|
|
1276
|
-
}
|
|
1277
|
-
if (config.scope === StorageScope.Secure) {
|
|
1278
|
-
assertBiometricLevel(resolvedBiometricLevel);
|
|
1279
|
-
if (secureAccessControl !== undefined) {
|
|
1280
|
-
assertAccessControlLevel(secureAccessControl);
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
const listeners = new Set();
|
|
1284
|
-
let unsubscribe = null;
|
|
1285
|
-
let lastRaw = undefined;
|
|
1286
|
-
let lastValue;
|
|
1287
|
-
let hasLastValue = false;
|
|
1288
|
-
let lastExpiresAt = undefined;
|
|
1289
|
-
const invalidateParsedCache = () => {
|
|
1290
|
-
lastRaw = undefined;
|
|
1291
|
-
lastValue = undefined;
|
|
1292
|
-
hasLastValue = false;
|
|
1293
|
-
lastExpiresAt = undefined;
|
|
1294
|
-
};
|
|
1295
|
-
const ensureSubscription = () => {
|
|
1296
|
-
if (unsubscribe) {
|
|
1297
|
-
return;
|
|
1298
|
-
}
|
|
1299
|
-
const listener = () => {
|
|
1300
|
-
invalidateParsedCache();
|
|
1301
|
-
listeners.forEach(callback => callback());
|
|
1302
|
-
};
|
|
1303
|
-
if (isMemory) {
|
|
1304
|
-
unsubscribe = addKeyListener(memoryListeners, storageKey, listener);
|
|
1305
|
-
return;
|
|
1306
|
-
}
|
|
1307
|
-
ensureExternalSyncSubscriptions();
|
|
1308
|
-
unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), storageKey, listener);
|
|
1309
|
-
};
|
|
1310
|
-
const readStoredRaw = () => {
|
|
1311
|
-
if (isMemory) {
|
|
1312
|
-
if (memoryExpiration) {
|
|
1313
|
-
const expiresAt = memoryExpiration.get(storageKey);
|
|
1314
|
-
if (expiresAt !== undefined && expiresAt <= Date.now()) {
|
|
1315
|
-
memoryExpiration.delete(storageKey);
|
|
1316
|
-
memoryStore.delete(storageKey);
|
|
1317
|
-
notifyKeyListeners(memoryListeners, storageKey);
|
|
1318
|
-
onExpired?.(storageKey);
|
|
1319
|
-
return undefined;
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
return memoryStore.get(storageKey);
|
|
1323
|
-
}
|
|
1324
|
-
if (nonMemoryScope === StorageScope.Disk) {
|
|
1325
|
-
const pending = pendingDiskWrites.get(storageKey);
|
|
1326
|
-
if (pending !== undefined) {
|
|
1327
|
-
return pending.value;
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
if (nonMemoryScope === StorageScope.Secure && !isBiometric) {
|
|
1331
|
-
const pending = pendingSecureWrites.get(storageKey);
|
|
1332
|
-
if (pending !== undefined) {
|
|
1333
|
-
return pending.value;
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
if (readCache) {
|
|
1337
|
-
const cache = getScopeRawCache(nonMemoryScope);
|
|
1338
|
-
const cached = cache.get(storageKey);
|
|
1339
|
-
if (cached !== undefined || cache.has(storageKey)) {
|
|
1340
|
-
return cached;
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
if (isBiometric) {
|
|
1344
|
-
return WebStorage.getSecureBiometric(storageKey);
|
|
1345
|
-
}
|
|
1346
|
-
const raw = WebStorage.get(storageKey, config.scope);
|
|
1347
|
-
cacheRawValue(nonMemoryScope, storageKey, raw);
|
|
1348
|
-
return raw;
|
|
1349
|
-
};
|
|
1350
|
-
const writeStoredRaw = rawValue => {
|
|
1351
|
-
const oldValue = config.scope === StorageScope.Memory ? getEventRawValue(config.scope, storageKey) : undefined;
|
|
1352
|
-
if (isBiometric) {
|
|
1353
|
-
WebStorage.setSecureBiometricWithLevel(storageKey, rawValue, resolvedBiometricLevel);
|
|
1354
|
-
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "web");
|
|
1355
|
-
return;
|
|
1356
|
-
}
|
|
1357
|
-
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
1358
|
-
if (nonMemoryScope === StorageScope.Disk) {
|
|
1359
|
-
if (coalesceDiskWrites || diskWritesAsync) {
|
|
1360
|
-
scheduleDiskWrite(storageKey, rawValue);
|
|
1361
|
-
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "web");
|
|
1362
|
-
return;
|
|
1363
|
-
}
|
|
1364
|
-
clearPendingDiskWrite(storageKey);
|
|
1365
|
-
}
|
|
1366
|
-
if (coalesceSecureWrites) {
|
|
1367
|
-
scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
|
|
1368
|
-
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "web");
|
|
1369
|
-
return;
|
|
1370
|
-
}
|
|
1371
|
-
if (nonMemoryScope === StorageScope.Secure) {
|
|
1372
|
-
clearPendingSecureWrite(storageKey);
|
|
1373
|
-
}
|
|
1374
|
-
WebStorage.set(storageKey, rawValue, config.scope);
|
|
1375
|
-
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "web");
|
|
1376
|
-
};
|
|
1377
|
-
const removeStoredRaw = () => {
|
|
1378
|
-
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1379
|
-
if (isBiometric) {
|
|
1380
|
-
WebStorage.deleteSecureBiometric(storageKey);
|
|
1381
|
-
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "web");
|
|
1382
|
-
return;
|
|
1383
|
-
}
|
|
1384
|
-
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
1385
|
-
if (nonMemoryScope === StorageScope.Disk) {
|
|
1386
|
-
if (coalesceDiskWrites || diskWritesAsync) {
|
|
1387
|
-
scheduleDiskWrite(storageKey, undefined);
|
|
1388
|
-
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "web");
|
|
1389
|
-
return;
|
|
1390
|
-
}
|
|
1391
|
-
clearPendingDiskWrite(storageKey);
|
|
1392
|
-
}
|
|
1393
|
-
if (coalesceSecureWrites) {
|
|
1394
|
-
scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
|
|
1395
|
-
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "web");
|
|
1396
|
-
return;
|
|
1397
|
-
}
|
|
1398
|
-
if (nonMemoryScope === StorageScope.Secure) {
|
|
1399
|
-
clearPendingSecureWrite(storageKey);
|
|
1400
|
-
}
|
|
1401
|
-
WebStorage.remove(storageKey, config.scope);
|
|
1402
|
-
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "web");
|
|
1403
|
-
};
|
|
1404
|
-
const writeValueWithoutValidation = value => {
|
|
1405
|
-
if (isMemory) {
|
|
1406
|
-
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1407
|
-
if (memoryExpiration) {
|
|
1408
|
-
memoryExpiration.set(storageKey, Date.now() + (expirationTtlMs ?? 0));
|
|
1409
|
-
}
|
|
1410
|
-
memoryStore.set(storageKey, value);
|
|
1411
|
-
notifyKeyListeners(memoryListeners, storageKey);
|
|
1412
|
-
emitKeyChange(config.scope, storageKey, oldValue, typeof value === "string" ? value : undefined, "set", "memory");
|
|
1413
|
-
return;
|
|
1414
|
-
}
|
|
1415
|
-
const serialized = serialize(value);
|
|
1416
|
-
if (expiration) {
|
|
1417
|
-
const envelope = {
|
|
1418
|
-
__nitroStorageEnvelope: true,
|
|
1419
|
-
expiresAt: Date.now() + expiration.ttlMs,
|
|
1420
|
-
payload: serialized
|
|
1421
|
-
};
|
|
1422
|
-
writeStoredRaw(JSON.stringify(envelope));
|
|
1423
|
-
return;
|
|
1424
|
-
}
|
|
1425
|
-
writeStoredRaw(serialized);
|
|
1426
|
-
};
|
|
1427
|
-
const resolveInvalidValue = invalidValue => {
|
|
1428
|
-
if (onValidationError) {
|
|
1429
|
-
return onValidationError(invalidValue);
|
|
1430
|
-
}
|
|
1431
|
-
return defaultValue;
|
|
1432
|
-
};
|
|
1433
|
-
const ensureValidatedValue = (candidate, hadStoredValue) => {
|
|
1434
|
-
if (!validate || validate(candidate)) {
|
|
1435
|
-
return candidate;
|
|
1436
|
-
}
|
|
1437
|
-
const resolved = resolveInvalidValue(candidate);
|
|
1438
|
-
if (validate && !validate(resolved)) {
|
|
1439
|
-
return defaultValue;
|
|
1440
|
-
}
|
|
1441
|
-
if (hadStoredValue) {
|
|
1442
|
-
writeValueWithoutValidation(resolved);
|
|
1443
|
-
}
|
|
1444
|
-
return resolved;
|
|
1445
|
-
};
|
|
1446
|
-
const getInternal = () => {
|
|
1447
|
-
const raw = readStoredRaw();
|
|
1448
|
-
if (!memoryExpiration && raw === lastRaw && hasLastValue) {
|
|
1449
|
-
if (!expiration || lastExpiresAt === null) {
|
|
1450
|
-
return lastValue;
|
|
1451
|
-
}
|
|
1452
|
-
if (typeof lastExpiresAt === "number") {
|
|
1453
|
-
if (lastExpiresAt > Date.now()) {
|
|
1454
|
-
return lastValue;
|
|
1455
|
-
}
|
|
1456
|
-
removeStoredRaw();
|
|
1457
|
-
invalidateParsedCache();
|
|
1458
|
-
onExpired?.(storageKey);
|
|
1459
|
-
lastValue = ensureValidatedValue(defaultValue, false);
|
|
1460
|
-
hasLastValue = true;
|
|
1461
|
-
listeners.forEach(cb => cb());
|
|
1462
|
-
return lastValue;
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
lastRaw = raw;
|
|
1466
|
-
if (raw === undefined) {
|
|
1467
|
-
lastExpiresAt = undefined;
|
|
1468
|
-
lastValue = ensureValidatedValue(defaultValue, false);
|
|
1469
|
-
hasLastValue = true;
|
|
1470
|
-
return lastValue;
|
|
1471
|
-
}
|
|
1472
|
-
if (isMemory) {
|
|
1473
|
-
lastExpiresAt = undefined;
|
|
1474
|
-
lastValue = ensureValidatedValue(raw, true);
|
|
1475
|
-
hasLastValue = true;
|
|
1476
|
-
return lastValue;
|
|
1477
|
-
}
|
|
1478
|
-
if (typeof raw !== "string") {
|
|
1479
|
-
lastExpiresAt = undefined;
|
|
1480
|
-
lastValue = ensureValidatedValue(defaultValue, false);
|
|
1481
|
-
hasLastValue = true;
|
|
1482
|
-
return lastValue;
|
|
1483
|
-
}
|
|
1484
|
-
let deserializableRaw = raw;
|
|
1485
|
-
if (expiration) {
|
|
1486
|
-
let envelopeExpiresAt = null;
|
|
1487
|
-
try {
|
|
1488
|
-
const parsed = JSON.parse(raw);
|
|
1489
|
-
if (isStoredEnvelope(parsed)) {
|
|
1490
|
-
envelopeExpiresAt = parsed.expiresAt;
|
|
1491
|
-
if (parsed.expiresAt <= Date.now()) {
|
|
1492
|
-
removeStoredRaw();
|
|
1493
|
-
invalidateParsedCache();
|
|
1494
|
-
onExpired?.(storageKey);
|
|
1495
|
-
lastValue = ensureValidatedValue(defaultValue, false);
|
|
1496
|
-
hasLastValue = true;
|
|
1497
|
-
listeners.forEach(cb => cb());
|
|
1498
|
-
return lastValue;
|
|
1499
|
-
}
|
|
1500
|
-
deserializableRaw = parsed.payload;
|
|
1501
|
-
}
|
|
1502
|
-
} catch {
|
|
1503
|
-
// Keep backward compatibility with legacy raw values.
|
|
1504
|
-
}
|
|
1505
|
-
lastExpiresAt = envelopeExpiresAt;
|
|
1506
|
-
} else {
|
|
1507
|
-
lastExpiresAt = undefined;
|
|
1508
|
-
}
|
|
1509
|
-
lastValue = ensureValidatedValue(deserialize(deserializableRaw), true);
|
|
1510
|
-
hasLastValue = true;
|
|
1511
|
-
return lastValue;
|
|
1512
|
-
};
|
|
1513
|
-
const getCurrentVersion = () => {
|
|
1514
|
-
const raw = readStoredRaw();
|
|
1515
|
-
return toVersionToken(raw);
|
|
1516
|
-
};
|
|
1517
|
-
const get = () => measureOperation("item:get", config.scope, () => getInternal());
|
|
1518
|
-
const getWithVersion = () => measureOperation("item:getWithVersion", config.scope, () => ({
|
|
1519
|
-
value: getInternal(),
|
|
1520
|
-
version: getCurrentVersion()
|
|
1521
|
-
}));
|
|
1522
|
-
const set = valueOrFn => {
|
|
1523
|
-
measureOperation("item:set", config.scope, () => {
|
|
1524
|
-
const newValue = isUpdater(valueOrFn) ? valueOrFn(getInternal()) : valueOrFn;
|
|
1525
|
-
if (validate && !validate(newValue)) {
|
|
1526
|
-
throw new Error(`Validation failed for key "${storageKey}" in scope "${StorageScope[config.scope]}".`);
|
|
1527
|
-
}
|
|
1528
|
-
invalidateParsedCache();
|
|
1529
|
-
writeValueWithoutValidation(newValue);
|
|
1530
|
-
});
|
|
1531
|
-
};
|
|
1532
|
-
const setIfVersion = (version, valueOrFn) => measureOperation("item:setIfVersion", config.scope, () => {
|
|
1533
|
-
const currentVersion = getCurrentVersion();
|
|
1534
|
-
if (currentVersion !== version) {
|
|
1535
|
-
return false;
|
|
1536
|
-
}
|
|
1537
|
-
set(valueOrFn);
|
|
1538
|
-
return true;
|
|
1539
|
-
});
|
|
1540
|
-
const deleteItem = () => {
|
|
1541
|
-
measureOperation("item:delete", config.scope, () => {
|
|
1542
|
-
invalidateParsedCache();
|
|
1543
|
-
if (isMemory) {
|
|
1544
|
-
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1545
|
-
if (memoryExpiration) {
|
|
1546
|
-
memoryExpiration.delete(storageKey);
|
|
1547
|
-
}
|
|
1548
|
-
memoryStore.delete(storageKey);
|
|
1549
|
-
notifyKeyListeners(memoryListeners, storageKey);
|
|
1550
|
-
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "memory");
|
|
1551
|
-
return;
|
|
1552
|
-
}
|
|
1553
|
-
removeStoredRaw();
|
|
1554
|
-
});
|
|
1555
|
-
};
|
|
1556
|
-
const hasItem = () => measureOperation("item:has", config.scope, () => {
|
|
1557
|
-
if (isMemory) return memoryStore.has(storageKey);
|
|
1558
|
-
if (isBiometric) return WebStorage.hasSecureBiometric(storageKey);
|
|
1559
|
-
if (nonMemoryScope === StorageScope.Disk) {
|
|
1560
|
-
const pending = pendingDiskWrites.get(storageKey);
|
|
1561
|
-
if (pending !== undefined) {
|
|
1562
|
-
return pending.value !== undefined;
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
if (nonMemoryScope === StorageScope.Secure) {
|
|
1566
|
-
const pending = pendingSecureWrites.get(storageKey);
|
|
1567
|
-
if (pending !== undefined) {
|
|
1568
|
-
return pending.value !== undefined;
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
return WebStorage.has(storageKey, config.scope);
|
|
1572
|
-
});
|
|
1573
|
-
const subscribe = callback => {
|
|
1574
|
-
ensureSubscription();
|
|
1575
|
-
listeners.add(callback);
|
|
1576
|
-
return () => {
|
|
1577
|
-
listeners.delete(callback);
|
|
1578
|
-
if (listeners.size === 0 && unsubscribe) {
|
|
1579
|
-
unsubscribe();
|
|
1580
|
-
unsubscribe = null;
|
|
1581
|
-
}
|
|
1582
|
-
};
|
|
1583
|
-
};
|
|
1584
|
-
const subscribeSelector = (selector, listener, options = {}) => {
|
|
1585
|
-
const isEqual = options.isEqual ?? Object.is;
|
|
1586
|
-
let currentValue = selector(getInternal());
|
|
1587
|
-
if (options.fireImmediately === true) {
|
|
1588
|
-
listener(currentValue, currentValue);
|
|
1589
|
-
}
|
|
1590
|
-
return subscribe(() => {
|
|
1591
|
-
const nextValue = selector(getInternal());
|
|
1592
|
-
if (isEqual(currentValue, nextValue)) {
|
|
1593
|
-
return;
|
|
1594
|
-
}
|
|
1595
|
-
const previousValue = currentValue;
|
|
1596
|
-
currentValue = nextValue;
|
|
1597
|
-
listener(nextValue, previousValue);
|
|
1598
|
-
});
|
|
1599
|
-
};
|
|
1600
|
-
const storageItem = {
|
|
1601
|
-
get,
|
|
1602
|
-
getWithVersion,
|
|
1603
|
-
set,
|
|
1604
|
-
setIfVersion,
|
|
1605
|
-
delete: deleteItem,
|
|
1606
|
-
has: hasItem,
|
|
1607
|
-
subscribe,
|
|
1608
|
-
subscribeSelector,
|
|
1609
|
-
serialize,
|
|
1610
|
-
deserialize,
|
|
1611
|
-
_triggerListeners: () => {
|
|
1612
|
-
invalidateParsedCache();
|
|
1613
|
-
listeners.forEach(listener => listener());
|
|
1614
|
-
},
|
|
1615
|
-
_invalidateParsedCacheOnly: () => {
|
|
1616
|
-
invalidateParsedCache();
|
|
1617
|
-
},
|
|
1618
|
-
_hasValidation: validate !== undefined,
|
|
1619
|
-
_hasExpiration: expiration !== undefined,
|
|
1620
|
-
_readCacheEnabled: readCache,
|
|
1621
|
-
_isBiometric: isBiometric,
|
|
1622
|
-
_biometricLevel: resolvedBiometricLevel,
|
|
1623
|
-
_defaultValue: defaultValue,
|
|
1624
|
-
...(secureAccessControl !== undefined ? {
|
|
1625
|
-
_secureAccessControl: secureAccessControl
|
|
1626
|
-
} : {}),
|
|
1627
|
-
scope: config.scope,
|
|
1628
|
-
key: storageKey
|
|
1629
|
-
};
|
|
1630
|
-
return storageItem;
|
|
1631
|
-
}
|
|
1632
554
|
export { useStorage, useStorageSelector, useSetStorage } from "./storage-hooks";
|
|
1633
555
|
export { createIndexedDBBackend } from "./indexeddb-backend";
|
|
1634
|
-
export function getBatch(items, scope) {
|
|
1635
|
-
return measureOperation("batch:get", scope, () => {
|
|
1636
|
-
assertBatchScope(items, scope);
|
|
1637
|
-
if (scope === StorageScope.Memory) {
|
|
1638
|
-
return items.map(item => item.get());
|
|
1639
|
-
}
|
|
1640
|
-
const useRawBatchPath = items.every(item => scope === StorageScope.Secure ? canUseSecureRawBatchPath(item) : canUseRawBatchPath(item));
|
|
1641
|
-
if (!useRawBatchPath) {
|
|
1642
|
-
return items.map(item => item.get());
|
|
1643
|
-
}
|
|
1644
|
-
const rawValues = new Array(items.length);
|
|
1645
|
-
const keysToFetch = [];
|
|
1646
|
-
const keyIndexes = [];
|
|
1647
|
-
items.forEach((item, index) => {
|
|
1648
|
-
if (scope === StorageScope.Disk) {
|
|
1649
|
-
const pending = pendingDiskWrites.get(item.key);
|
|
1650
|
-
if (pending !== undefined) {
|
|
1651
|
-
rawValues[index] = pending.value;
|
|
1652
|
-
return;
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
|
-
if (scope === StorageScope.Secure) {
|
|
1656
|
-
const pending = pendingSecureWrites.get(item.key);
|
|
1657
|
-
if (pending !== undefined) {
|
|
1658
|
-
rawValues[index] = pending.value;
|
|
1659
|
-
return;
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
if (item._readCacheEnabled === true) {
|
|
1663
|
-
const cache = getScopeRawCache(scope);
|
|
1664
|
-
const cached = cache.get(item.key);
|
|
1665
|
-
if (cached !== undefined || cache.has(item.key)) {
|
|
1666
|
-
rawValues[index] = cached;
|
|
1667
|
-
return;
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
keysToFetch.push(item.key);
|
|
1671
|
-
keyIndexes.push(index);
|
|
1672
|
-
});
|
|
1673
|
-
if (keysToFetch.length > 0) {
|
|
1674
|
-
const fetchedValues = WebStorage.getBatch(keysToFetch, scope);
|
|
1675
|
-
fetchedValues.forEach((value, index) => {
|
|
1676
|
-
const key = keysToFetch[index];
|
|
1677
|
-
const targetIndex = keyIndexes[index];
|
|
1678
|
-
if (key === undefined || targetIndex === undefined) {
|
|
1679
|
-
return;
|
|
1680
|
-
}
|
|
1681
|
-
rawValues[targetIndex] = value;
|
|
1682
|
-
cacheRawValue(scope, key, value);
|
|
1683
|
-
});
|
|
1684
|
-
}
|
|
1685
|
-
return items.map((item, index) => {
|
|
1686
|
-
const raw = rawValues[index];
|
|
1687
|
-
if (raw === undefined) {
|
|
1688
|
-
return asInternal(item)._defaultValue;
|
|
1689
|
-
}
|
|
1690
|
-
return item.deserialize(raw);
|
|
1691
|
-
});
|
|
1692
|
-
}, items.length);
|
|
1693
|
-
}
|
|
1694
|
-
export function setBatch(items, scope) {
|
|
1695
|
-
measureOperation("batch:set", scope, () => {
|
|
1696
|
-
assertBatchScope(items.map(batchEntry => batchEntry.item), scope);
|
|
1697
|
-
if (scope === StorageScope.Memory) {
|
|
1698
|
-
// Determine if any item needs per-item handling (validation or TTL)
|
|
1699
|
-
const needsIndividualSets = items.some(({
|
|
1700
|
-
item
|
|
1701
|
-
}) => {
|
|
1702
|
-
const internal = asInternal(item);
|
|
1703
|
-
return internal._hasValidation || internal._hasExpiration;
|
|
1704
|
-
});
|
|
1705
|
-
if (needsIndividualSets) {
|
|
1706
|
-
items.forEach(({
|
|
1707
|
-
item,
|
|
1708
|
-
value
|
|
1709
|
-
}) => item.set(value));
|
|
1710
|
-
return;
|
|
1711
|
-
}
|
|
1712
|
-
const changes = items.map(({
|
|
1713
|
-
item,
|
|
1714
|
-
value
|
|
1715
|
-
}) => createKeyChange(scope, item.key, getEventRawValue(scope, item.key), typeof value === "string" ? value : undefined, "setBatch", "memory"));
|
|
1716
|
-
|
|
1717
|
-
// Atomic write: update all values in memoryStore, invalidate caches, then batch-notify
|
|
1718
|
-
items.forEach(({
|
|
1719
|
-
item,
|
|
1720
|
-
value
|
|
1721
|
-
}) => {
|
|
1722
|
-
memoryStore.set(item.key, value);
|
|
1723
|
-
asInternal(item)._invalidateParsedCacheOnly();
|
|
1724
|
-
});
|
|
1725
|
-
items.forEach(({
|
|
1726
|
-
item
|
|
1727
|
-
}) => notifyKeyListeners(memoryListeners, item.key));
|
|
1728
|
-
emitBatchChange(scope, "setBatch", "memory", changes);
|
|
1729
|
-
return;
|
|
1730
|
-
}
|
|
1731
|
-
if (scope === StorageScope.Secure) {
|
|
1732
|
-
const secureEntries = items.map(({
|
|
1733
|
-
item,
|
|
1734
|
-
value
|
|
1735
|
-
}) => ({
|
|
1736
|
-
item,
|
|
1737
|
-
value,
|
|
1738
|
-
internal: asInternal(item)
|
|
1739
|
-
}));
|
|
1740
|
-
const canUseSecureBatchPath = secureEntries.every(({
|
|
1741
|
-
internal
|
|
1742
|
-
}) => canUseSecureRawBatchPath(internal));
|
|
1743
|
-
if (!canUseSecureBatchPath) {
|
|
1744
|
-
items.forEach(({
|
|
1745
|
-
item,
|
|
1746
|
-
value
|
|
1747
|
-
}) => item.set(value));
|
|
1748
|
-
return;
|
|
1749
|
-
}
|
|
1750
|
-
flushSecureWrites();
|
|
1751
|
-
const keys = secureEntries.map(({
|
|
1752
|
-
item
|
|
1753
|
-
}) => item.key);
|
|
1754
|
-
const oldValues = shouldReadPreviousEventValues(scope) ? WebStorage.getBatch(keys, scope) : [];
|
|
1755
|
-
const groupedByAccessControl = new Map();
|
|
1756
|
-
secureEntries.forEach(({
|
|
1757
|
-
item,
|
|
1758
|
-
value,
|
|
1759
|
-
internal
|
|
1760
|
-
}) => {
|
|
1761
|
-
const accessControl = internal._secureAccessControl ?? secureDefaultAccessControl;
|
|
1762
|
-
const existingGroup = groupedByAccessControl.get(accessControl);
|
|
1763
|
-
const group = existingGroup ?? {
|
|
1764
|
-
keys: [],
|
|
1765
|
-
values: []
|
|
1766
|
-
};
|
|
1767
|
-
group.keys.push(item.key);
|
|
1768
|
-
group.values.push(item.serialize(value));
|
|
1769
|
-
if (!existingGroup) {
|
|
1770
|
-
groupedByAccessControl.set(accessControl, group);
|
|
1771
|
-
}
|
|
1772
|
-
});
|
|
1773
|
-
groupedByAccessControl.forEach((group, accessControl) => {
|
|
1774
|
-
WebStorage.setSecureAccessControl(accessControl);
|
|
1775
|
-
WebStorage.setBatch(group.keys, group.values, scope);
|
|
1776
|
-
group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
|
|
1777
|
-
});
|
|
1778
|
-
emitBatchChange(scope, "setBatch", "web", secureEntries.map(({
|
|
1779
|
-
item,
|
|
1780
|
-
value
|
|
1781
|
-
}, index) => createKeyChange(scope, item.key, oldValues[index], item.serialize(value), "setBatch", "web")));
|
|
1782
|
-
return;
|
|
1783
|
-
}
|
|
1784
|
-
flushDiskWrites();
|
|
1785
|
-
const useRawBatchPath = items.every(({
|
|
1786
|
-
item
|
|
1787
|
-
}) => canUseRawBatchPath(asInternal(item)));
|
|
1788
|
-
if (!useRawBatchPath) {
|
|
1789
|
-
items.forEach(({
|
|
1790
|
-
item,
|
|
1791
|
-
value
|
|
1792
|
-
}) => item.set(value));
|
|
1793
|
-
return;
|
|
1794
|
-
}
|
|
1795
|
-
const keys = items.map(entry => entry.item.key);
|
|
1796
|
-
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
1797
|
-
const oldValues = shouldReadPreviousEventValues(scope) ? WebStorage.getBatch(keys, scope) : [];
|
|
1798
|
-
WebStorage.setBatch(keys, values, scope);
|
|
1799
|
-
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
1800
|
-
emitBatchChange(scope, "setBatch", "web", keys.map((key, index) => createKeyChange(scope, key, oldValues[index], values[index], "setBatch", "web")));
|
|
1801
|
-
}, items.length);
|
|
1802
|
-
}
|
|
1803
|
-
export function removeBatch(items, scope) {
|
|
1804
|
-
measureOperation("batch:remove", scope, () => {
|
|
1805
|
-
assertBatchScope(items, scope);
|
|
1806
|
-
if (scope === StorageScope.Memory) {
|
|
1807
|
-
const changes = items.map(item => createKeyChange(scope, item.key, getEventRawValue(scope, item.key), undefined, "removeBatch", "memory"));
|
|
1808
|
-
items.forEach(item => item.delete());
|
|
1809
|
-
emitBatchChange(scope, "removeBatch", "memory", changes);
|
|
1810
|
-
return;
|
|
1811
|
-
}
|
|
1812
|
-
const keys = items.map(item => item.key);
|
|
1813
|
-
if (scope === StorageScope.Disk) {
|
|
1814
|
-
flushDiskWrites();
|
|
1815
|
-
}
|
|
1816
|
-
if (scope === StorageScope.Secure) {
|
|
1817
|
-
flushSecureWrites();
|
|
1818
|
-
}
|
|
1819
|
-
const oldValues = shouldReadPreviousEventValues(scope) ? WebStorage.getBatch(keys, scope) : [];
|
|
1820
|
-
WebStorage.removeBatch(keys, scope);
|
|
1821
|
-
keys.forEach(key => cacheRawValue(scope, key, undefined));
|
|
1822
|
-
emitBatchChange(scope, "removeBatch", "web", keys.map((key, index) => createKeyChange(scope, key, oldValues[index], undefined, "removeBatch", "web")));
|
|
1823
|
-
}, items.length);
|
|
1824
|
-
}
|
|
1825
|
-
export function registerMigration(version, migration) {
|
|
1826
|
-
if (!Number.isInteger(version) || version <= 0) {
|
|
1827
|
-
throw new Error("Migration version must be a positive integer.");
|
|
1828
|
-
}
|
|
1829
|
-
if (registeredMigrations.has(version)) {
|
|
1830
|
-
throw new Error(`Migration version ${version} is already registered.`);
|
|
1831
|
-
}
|
|
1832
|
-
registeredMigrations.set(version, migration);
|
|
1833
|
-
}
|
|
1834
|
-
export function migrateToLatest(scope = StorageScope.Disk) {
|
|
1835
|
-
return measureOperation("migration:run", scope, () => {
|
|
1836
|
-
assertValidScope(scope);
|
|
1837
|
-
const currentVersion = readMigrationVersion(scope);
|
|
1838
|
-
const versions = Array.from(registeredMigrations.keys()).filter(version => version > currentVersion).sort((a, b) => a - b);
|
|
1839
|
-
let appliedVersion = currentVersion;
|
|
1840
|
-
const context = {
|
|
1841
|
-
scope,
|
|
1842
|
-
getRaw: key => getRawValue(key, scope),
|
|
1843
|
-
setRaw: (key, value) => setRawValue(key, value, scope),
|
|
1844
|
-
removeRaw: key => removeRawValue(key, scope)
|
|
1845
|
-
};
|
|
1846
|
-
versions.forEach(version => {
|
|
1847
|
-
const migration = registeredMigrations.get(version);
|
|
1848
|
-
if (!migration) {
|
|
1849
|
-
return;
|
|
1850
|
-
}
|
|
1851
|
-
migration(context);
|
|
1852
|
-
appliedVersion = version;
|
|
1853
|
-
});
|
|
1854
|
-
if (appliedVersion !== currentVersion) {
|
|
1855
|
-
writeMigrationVersion(scope, appliedVersion);
|
|
1856
|
-
}
|
|
1857
|
-
return appliedVersion;
|
|
1858
|
-
});
|
|
1859
|
-
}
|
|
1860
|
-
export function runTransaction(scope, transaction) {
|
|
1861
|
-
return measureOperation("transaction:run", scope, () => {
|
|
1862
|
-
assertValidScope(scope);
|
|
1863
|
-
if (scope === StorageScope.Disk) {
|
|
1864
|
-
flushDiskWrites();
|
|
1865
|
-
}
|
|
1866
|
-
if (scope === StorageScope.Secure) {
|
|
1867
|
-
flushSecureWrites();
|
|
1868
|
-
}
|
|
1869
|
-
const NOT_SET = Symbol();
|
|
1870
|
-
const rollback = new Map();
|
|
1871
|
-
const rememberRollback = (key, item) => {
|
|
1872
|
-
if (rollback.has(key)) {
|
|
1873
|
-
return;
|
|
1874
|
-
}
|
|
1875
|
-
if (scope === StorageScope.Memory) {
|
|
1876
|
-
rollback.set(key, {
|
|
1877
|
-
kind: "memory",
|
|
1878
|
-
value: memoryStore.has(key) ? memoryStore.get(key) : NOT_SET
|
|
1879
|
-
});
|
|
1880
|
-
} else {
|
|
1881
|
-
const internal = item ? item : undefined;
|
|
1882
|
-
if (scope === StorageScope.Secure && internal?._isBiometric === true) {
|
|
1883
|
-
rollback.set(key, {
|
|
1884
|
-
kind: "biometric",
|
|
1885
|
-
value: WebStorage.getSecureBiometric(key),
|
|
1886
|
-
level: internal._biometricLevel
|
|
1887
|
-
});
|
|
1888
|
-
return;
|
|
1889
|
-
}
|
|
1890
|
-
rollback.set(key, {
|
|
1891
|
-
kind: "raw",
|
|
1892
|
-
value: getRawValue(key, scope),
|
|
1893
|
-
...(scope === StorageScope.Secure && internal?._secureAccessControl !== undefined ? {
|
|
1894
|
-
accessControl: internal._secureAccessControl
|
|
1895
|
-
} : {})
|
|
1896
|
-
});
|
|
1897
|
-
}
|
|
1898
|
-
};
|
|
1899
|
-
const tx = {
|
|
1900
|
-
scope,
|
|
1901
|
-
getRaw: key => getRawValue(key, scope),
|
|
1902
|
-
setRaw: (key, value) => {
|
|
1903
|
-
rememberRollback(key);
|
|
1904
|
-
setRawValue(key, value, scope);
|
|
1905
|
-
},
|
|
1906
|
-
removeRaw: key => {
|
|
1907
|
-
rememberRollback(key);
|
|
1908
|
-
removeRawValue(key, scope);
|
|
1909
|
-
},
|
|
1910
|
-
getItem: item => {
|
|
1911
|
-
assertBatchScope([item], scope);
|
|
1912
|
-
return item.get();
|
|
1913
|
-
},
|
|
1914
|
-
setItem: (item, value) => {
|
|
1915
|
-
assertBatchScope([item], scope);
|
|
1916
|
-
rememberRollback(item.key, item);
|
|
1917
|
-
item.set(value);
|
|
1918
|
-
},
|
|
1919
|
-
removeItem: item => {
|
|
1920
|
-
assertBatchScope([item], scope);
|
|
1921
|
-
rememberRollback(item.key, item);
|
|
1922
|
-
item.delete();
|
|
1923
|
-
}
|
|
1924
|
-
};
|
|
1925
|
-
try {
|
|
1926
|
-
return transaction(tx);
|
|
1927
|
-
} catch (error) {
|
|
1928
|
-
const rollbackEntries = Array.from(rollback.entries()).reverse();
|
|
1929
|
-
if (scope === StorageScope.Memory) {
|
|
1930
|
-
rollbackEntries.forEach(([key, record]) => {
|
|
1931
|
-
if (record.value === NOT_SET) {
|
|
1932
|
-
memoryStore.delete(key);
|
|
1933
|
-
} else {
|
|
1934
|
-
memoryStore.set(key, record.value);
|
|
1935
|
-
}
|
|
1936
|
-
notifyKeyListeners(memoryListeners, key);
|
|
1937
|
-
});
|
|
1938
|
-
} else {
|
|
1939
|
-
const groupedKeysToSet = new Map();
|
|
1940
|
-
const keysToRemove = [];
|
|
1941
|
-
rollbackEntries.forEach(([key, record]) => {
|
|
1942
|
-
if (record.kind === "biometric") {
|
|
1943
|
-
if (record.value === undefined) {
|
|
1944
|
-
WebStorage.deleteSecureBiometric(key);
|
|
1945
|
-
} else {
|
|
1946
|
-
WebStorage.setSecureBiometricWithLevel(key, record.value, record.level);
|
|
1947
|
-
}
|
|
1948
|
-
return;
|
|
1949
|
-
}
|
|
1950
|
-
if (record.kind !== "raw") {
|
|
1951
|
-
return;
|
|
1952
|
-
}
|
|
1953
|
-
if (record.value === undefined) {
|
|
1954
|
-
keysToRemove.push(key);
|
|
1955
|
-
} else {
|
|
1956
|
-
const accessControl = record.accessControl ?? secureDefaultAccessControl;
|
|
1957
|
-
const existingGroup = groupedKeysToSet.get(accessControl);
|
|
1958
|
-
const group = existingGroup ?? {
|
|
1959
|
-
keys: [],
|
|
1960
|
-
values: []
|
|
1961
|
-
};
|
|
1962
|
-
group.keys.push(key);
|
|
1963
|
-
group.values.push(record.value);
|
|
1964
|
-
if (!existingGroup) {
|
|
1965
|
-
groupedKeysToSet.set(accessControl, group);
|
|
1966
|
-
}
|
|
1967
|
-
}
|
|
1968
|
-
});
|
|
1969
|
-
if (scope === StorageScope.Disk) {
|
|
1970
|
-
flushDiskWrites();
|
|
1971
|
-
}
|
|
1972
|
-
if (scope === StorageScope.Secure) {
|
|
1973
|
-
flushSecureWrites();
|
|
1974
|
-
}
|
|
1975
|
-
groupedKeysToSet.forEach((group, accessControl) => {
|
|
1976
|
-
if (scope === StorageScope.Secure) {
|
|
1977
|
-
WebStorage.setSecureAccessControl(accessControl);
|
|
1978
|
-
}
|
|
1979
|
-
WebStorage.setBatch(group.keys, group.values, scope);
|
|
1980
|
-
group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
|
|
1981
|
-
});
|
|
1982
|
-
if (keysToRemove.length > 0) {
|
|
1983
|
-
WebStorage.removeBatch(keysToRemove, scope);
|
|
1984
|
-
keysToRemove.forEach(key => cacheRawValue(scope, key, undefined));
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
throw error;
|
|
1988
|
-
}
|
|
1989
|
-
});
|
|
1990
|
-
}
|
|
1991
|
-
export function createSecureAuthStorage(config, options) {
|
|
1992
|
-
const ns = options?.namespace ?? "auth";
|
|
1993
|
-
const result = {};
|
|
1994
|
-
for (const key of typedKeys(config)) {
|
|
1995
|
-
const itemConfig = config[key];
|
|
1996
|
-
const expirationConfig = itemConfig.ttlMs !== undefined ? {
|
|
1997
|
-
ttlMs: itemConfig.ttlMs
|
|
1998
|
-
} : undefined;
|
|
1999
|
-
result[key] = createStorageItem({
|
|
2000
|
-
key,
|
|
2001
|
-
scope: StorageScope.Secure,
|
|
2002
|
-
defaultValue: "",
|
|
2003
|
-
namespace: ns,
|
|
2004
|
-
...(itemConfig.biometric !== undefined ? {
|
|
2005
|
-
biometric: itemConfig.biometric
|
|
2006
|
-
} : {}),
|
|
2007
|
-
...(itemConfig.biometricLevel !== undefined ? {
|
|
2008
|
-
biometricLevel: itemConfig.biometricLevel
|
|
2009
|
-
} : {}),
|
|
2010
|
-
...(itemConfig.accessControl !== undefined ? {
|
|
2011
|
-
accessControl: itemConfig.accessControl
|
|
2012
|
-
} : {}),
|
|
2013
|
-
...(expirationConfig !== undefined ? {
|
|
2014
|
-
expiration: expirationConfig
|
|
2015
|
-
} : {})
|
|
2016
|
-
});
|
|
2017
|
-
}
|
|
2018
|
-
return result;
|
|
2019
|
-
}
|
|
2020
|
-
export function isKeychainLockedError(err) {
|
|
2021
|
-
return isLockedStorageErrorCode(getStorageErrorCode(err));
|
|
2022
|
-
}
|
|
2023
556
|
//# sourceMappingURL=index.web.js.map
|