react-native-nitro-storage 0.9.0 → 0.10.0
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/CHANGELOG.md +89 -29
- package/README.md +81 -24
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +5 -2
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +240 -34
- package/cpp/bindings/HybridStorage.cpp +106 -283
- package/cpp/bindings/HybridStorage.hpp +16 -9
- package/docs/api-reference.md +75 -19
- package/docs/benchmarks.md +6 -1
- package/docs/keychain-lifecycle-testing.md +36 -0
- package/docs/recipes.md +1 -2
- package/docs/secure-storage.md +45 -9
- package/ios/IOSStorageAdapterCpp.mm +391 -175
- package/lib/commonjs/capabilities.js +3 -1
- package/lib/commonjs/capabilities.js.map +1 -1
- package/lib/commonjs/core/durability.js +110 -43
- package/lib/commonjs/core/durability.js.map +1 -1
- package/lib/commonjs/index.js +15 -5
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +219 -97
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/internal.js +3 -11
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/commonjs/shared.js +62 -2
- package/lib/commonjs/shared.js.map +1 -1
- package/lib/commonjs/storage-core.js +852 -141
- package/lib/commonjs/storage-core.js.map +1 -1
- package/lib/commonjs/storage-runtime.js +5 -1
- package/lib/commonjs/storage-runtime.js.map +1 -1
- package/lib/commonjs/testing.js +13 -0
- package/lib/commonjs/testing.js.map +1 -1
- package/lib/module/capabilities.js +2 -1
- package/lib/module/capabilities.js.map +1 -1
- package/lib/module/core/durability.js +110 -43
- package/lib/module/core/durability.js.map +1 -1
- package/lib/module/index.js +12 -8
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +215 -99
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/internal.js +2 -9
- package/lib/module/internal.js.map +1 -1
- package/lib/module/shared.js +60 -2
- package/lib/module/shared.js.map +1 -1
- package/lib/module/storage-core.js +853 -142
- package/lib/module/storage-core.js.map +1 -1
- package/lib/module/storage-runtime.js +4 -1
- package/lib/module/storage-runtime.js.map +1 -1
- package/lib/module/testing.js +8 -1
- package/lib/module/testing.js.map +1 -1
- package/lib/typescript/capabilities.d.ts +2 -1
- package/lib/typescript/capabilities.d.ts.map +1 -1
- package/lib/typescript/core/durability.d.ts +7 -2
- package/lib/typescript/core/durability.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +3 -29
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts +0 -2
- package/lib/typescript/internal.d.ts.map +1 -1
- package/lib/typescript/shared.d.ts +19 -0
- package/lib/typescript/shared.d.ts.map +1 -1
- package/lib/typescript/storage-core.d.ts +15 -6
- package/lib/typescript/storage-core.d.ts.map +1 -1
- package/lib/typescript/storage-runtime.d.ts +2 -1
- package/lib/typescript/storage-runtime.d.ts.map +1 -1
- package/lib/typescript/testing.d.ts +1 -1
- package/lib/typescript/testing.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/capabilities.ts +3 -1
- package/src/core/durability.ts +139 -39
- package/src/index.ts +20 -10
- package/src/index.web.ts +328 -166
- package/src/internal.ts +0 -14
- package/src/shared.ts +85 -0
- package/src/storage-core.ts +1165 -202
- package/src/storage-runtime.ts +6 -0
- package/src/testing.ts +8 -1
|
@@ -19,6 +19,8 @@ function createStorageCore(buildAdapter) {
|
|
|
19
19
|
const itemGroups = new Map();
|
|
20
20
|
const registeredKeyCounts = new Map();
|
|
21
21
|
const memoryStore = new Map();
|
|
22
|
+
const memoryExpirationDeadlines = new Map();
|
|
23
|
+
const memoryItemsByKey = new Map();
|
|
22
24
|
const memoryListeners = new Map();
|
|
23
25
|
const scopedListeners = {
|
|
24
26
|
[_Storage.StorageScope.Disk]: new Map(),
|
|
@@ -53,15 +55,33 @@ function createStorageCore(buildAdapter) {
|
|
|
53
55
|
function getScopeRawCache(scope) {
|
|
54
56
|
return scopedRawCache[scope];
|
|
55
57
|
}
|
|
56
|
-
function
|
|
57
|
-
getScopeRawCache(scope)
|
|
58
|
+
function getCachedRawValueEntry(scope, key, create = false) {
|
|
59
|
+
const scopeCache = getScopeRawCache(scope);
|
|
60
|
+
const existing = scopeCache.get(key);
|
|
61
|
+
if (existing || !create) {
|
|
62
|
+
return existing;
|
|
63
|
+
}
|
|
64
|
+
const entry = new Map();
|
|
65
|
+
scopeCache.set(key, entry);
|
|
66
|
+
return entry;
|
|
67
|
+
}
|
|
68
|
+
function cacheRawValue(scope, key, value, representation = "plain") {
|
|
69
|
+
getCachedRawValueEntry(scope, key, true)?.set(representation, value);
|
|
70
|
+
}
|
|
71
|
+
function readCachedRawValue(scope, key, representation = "plain") {
|
|
72
|
+
return getCachedRawValueEntry(scope, key)?.get(representation);
|
|
58
73
|
}
|
|
59
|
-
function
|
|
60
|
-
|
|
74
|
+
function invalidateRawCache(scope, key) {
|
|
75
|
+
getScopeRawCache(scope).delete(key);
|
|
61
76
|
}
|
|
62
77
|
function clearScopeRawCache(scope) {
|
|
63
78
|
getScopeRawCache(scope).clear();
|
|
64
79
|
}
|
|
80
|
+
function invalidateMemoryItemCaches(key) {
|
|
81
|
+
memoryItemsByKey.get(key)?.forEach(item => {
|
|
82
|
+
item._invalidateParsedCacheOnly();
|
|
83
|
+
});
|
|
84
|
+
}
|
|
65
85
|
function addKeyListener(registry, key, listener) {
|
|
66
86
|
let listeners = registry.get(key);
|
|
67
87
|
if (!listeners) {
|
|
@@ -87,6 +107,13 @@ function createStorageCore(buildAdapter) {
|
|
|
87
107
|
}
|
|
88
108
|
return getRawValue(key, scope);
|
|
89
109
|
}
|
|
110
|
+
function getEventRawValueForRepresentation(scope, key, representation) {
|
|
111
|
+
if (representation === "plain") {
|
|
112
|
+
return getEventRawValue(scope, key);
|
|
113
|
+
}
|
|
114
|
+
const raw = adapter.backend.getSecureBiometric(key);
|
|
115
|
+
return raw === undefined ? undefined : (0, _internal.unescapeCollidingRawValue)(raw);
|
|
116
|
+
}
|
|
90
117
|
function shouldReadPreviousEventValues(scope) {
|
|
91
118
|
if (storageEvents.hasListeners(scope)) {
|
|
92
119
|
return true;
|
|
@@ -159,11 +186,14 @@ function createStorageCore(buildAdapter) {
|
|
|
159
186
|
function flushSecureWrites() {
|
|
160
187
|
durability.flushSecureWrites();
|
|
161
188
|
}
|
|
189
|
+
function runSecurePromotion(key, promotion) {
|
|
190
|
+
return durability.runSecurePromotion(key, promotion);
|
|
191
|
+
}
|
|
162
192
|
function scheduleDiskWrite(key, value) {
|
|
163
|
-
durability.scheduleDiskWrite(key, value);
|
|
193
|
+
return durability.scheduleDiskWrite(key, value);
|
|
164
194
|
}
|
|
165
195
|
function scheduleSecureWrite(key, value, accessControl) {
|
|
166
|
-
durability.scheduleSecureWrite(key, value, accessControl);
|
|
196
|
+
return durability.scheduleSecureWrite(key, value, accessControl);
|
|
167
197
|
}
|
|
168
198
|
function setDiskWritesAsyncMode(enabled) {
|
|
169
199
|
durability.setDiskWritesAsync(enabled);
|
|
@@ -184,6 +214,12 @@ function createStorageCore(buildAdapter) {
|
|
|
184
214
|
}
|
|
185
215
|
return adapter.backend.get(key, scope);
|
|
186
216
|
}
|
|
217
|
+
function getStoredRawValueForRepresentation(key, scope, representation) {
|
|
218
|
+
if (representation === "plain") {
|
|
219
|
+
return getStoredRawValue(key, scope);
|
|
220
|
+
}
|
|
221
|
+
return adapter.backend.getSecureBiometric(key);
|
|
222
|
+
}
|
|
187
223
|
function getRawValue(key, scope) {
|
|
188
224
|
(0, _internal.assertValidScope)(scope);
|
|
189
225
|
if (scope === _Storage.StorageScope.Memory) {
|
|
@@ -222,6 +258,7 @@ function createStorageCore(buildAdapter) {
|
|
|
222
258
|
clearPendingDiskWrite(key);
|
|
223
259
|
}
|
|
224
260
|
if (scope === _Storage.StorageScope.Secure) {
|
|
261
|
+
invalidateRawCache(scope, key);
|
|
225
262
|
flushSecureWrites();
|
|
226
263
|
clearPendingSecureWrite(key);
|
|
227
264
|
if (adapter.applyAccessControlOnSecureRawWrite) {
|
|
@@ -252,6 +289,7 @@ function createStorageCore(buildAdapter) {
|
|
|
252
289
|
clearPendingDiskWrite(key);
|
|
253
290
|
}
|
|
254
291
|
if (scope === _Storage.StorageScope.Secure) {
|
|
292
|
+
invalidateRawCache(scope, key);
|
|
255
293
|
flushSecureWrites();
|
|
256
294
|
clearPendingSecureWrite(key);
|
|
257
295
|
}
|
|
@@ -271,6 +309,7 @@ function createStorageCore(buildAdapter) {
|
|
|
271
309
|
getScopedListeners,
|
|
272
310
|
cacheRawValue,
|
|
273
311
|
readCachedRawValue,
|
|
312
|
+
invalidateRawCache,
|
|
274
313
|
clearScopeRawCache,
|
|
275
314
|
clearPendingDiskWrite,
|
|
276
315
|
clearPendingSecureWrite,
|
|
@@ -324,6 +363,11 @@ function createStorageCore(buildAdapter) {
|
|
|
324
363
|
return;
|
|
325
364
|
}
|
|
326
365
|
const previousValues = shouldReadPreviousEventValues(scope) ? adapter.backend.getBatch(removeKeys, scope).map(value => value === undefined ? undefined : (0, _internal.unescapeCollidingRawValue)(value)) : [];
|
|
366
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
367
|
+
removeKeys.forEach(key => {
|
|
368
|
+
invalidateRawCache(scope, key);
|
|
369
|
+
});
|
|
370
|
+
}
|
|
327
371
|
adapter.backend.removeBatch(removeKeys, scope);
|
|
328
372
|
removeKeys.forEach(key => {
|
|
329
373
|
cacheRawValue(scope, key, undefined);
|
|
@@ -391,6 +435,7 @@ function createStorageCore(buildAdapter) {
|
|
|
391
435
|
const previousValues = shouldReadPreviousEventValues(scope) ? storage.getAll(scope) : {};
|
|
392
436
|
if (scope === _Storage.StorageScope.Memory) {
|
|
393
437
|
memoryStore.clear();
|
|
438
|
+
memoryExpirationDeadlines.clear();
|
|
394
439
|
(0, _shared.notifyAllListeners)(memoryListeners);
|
|
395
440
|
emitBatchChange(scope, "clear", "memory", Object.keys(previousValues).map(key => (0, _shared.createKeyChange)(scope, key, previousValues[key], undefined, "clear", "memory")));
|
|
396
441
|
return;
|
|
@@ -494,6 +539,7 @@ function createStorageCore(buildAdapter) {
|
|
|
494
539
|
}
|
|
495
540
|
affectedKeys.forEach(key => {
|
|
496
541
|
memoryStore.delete(key);
|
|
542
|
+
memoryExpirationDeadlines.delete(key);
|
|
497
543
|
});
|
|
498
544
|
affectedKeys.forEach(key => {
|
|
499
545
|
(0, _shared.notifyKeyListeners)(memoryListeners, key);
|
|
@@ -524,7 +570,21 @@ function createStorageCore(buildAdapter) {
|
|
|
524
570
|
},
|
|
525
571
|
clearBiometric: () => {
|
|
526
572
|
measureOperation("storage:clearBiometric", _Storage.StorageScope.Secure, () => {
|
|
527
|
-
|
|
573
|
+
flushSecureWrites();
|
|
574
|
+
const readEventValues = shouldReadPreviousEventValues(_Storage.StorageScope.Secure);
|
|
575
|
+
const shouldEmitChanges = storageEvents.hasListeners(_Storage.StorageScope.Secure) || eventObserver !== undefined;
|
|
576
|
+
const biometricKeys = shouldEmitChanges ? adapter.backend.getAllKeys(_Storage.StorageScope.Secure).filter(key => adapter.backend.hasSecureBiometric(key)) : [];
|
|
577
|
+
const previousValues = readEventValues ? biometricKeys.map(key => {
|
|
578
|
+
const raw = adapter.backend.getSecureBiometric(key);
|
|
579
|
+
return raw === undefined ? undefined : (0, _internal.unescapeCollidingRawValue)(raw);
|
|
580
|
+
}) : [];
|
|
581
|
+
clearScopeRawCache(_Storage.StorageScope.Secure);
|
|
582
|
+
try {
|
|
583
|
+
adapter.backend.clearSecureBiometric();
|
|
584
|
+
} finally {
|
|
585
|
+
clearScopeRawCache(_Storage.StorageScope.Secure);
|
|
586
|
+
}
|
|
587
|
+
emitBatchChange(_Storage.StorageScope.Secure, "clear", adapter.changeSource, biometricKeys.map((key, index) => (0, _shared.createKeyChange)(_Storage.StorageScope.Secure, key, readEventValues ? previousValues[index] : undefined, undefined, "clear", adapter.changeSource)));
|
|
528
588
|
});
|
|
529
589
|
},
|
|
530
590
|
has: (key, scope) => {
|
|
@@ -749,6 +809,9 @@ function createStorageCore(buildAdapter) {
|
|
|
749
809
|
if (scope === _Storage.StorageScope.Secure) {
|
|
750
810
|
flushSecureWrites();
|
|
751
811
|
adapter.backend.setSecureAccessControl(secureDefaultAccessControl);
|
|
812
|
+
keys.forEach(key => {
|
|
813
|
+
invalidateRawCache(scope, key);
|
|
814
|
+
});
|
|
752
815
|
}
|
|
753
816
|
if (scope === _Storage.StorageScope.Disk) {
|
|
754
817
|
flushDiskWrites();
|
|
@@ -774,8 +837,9 @@ function createStorageCore(buildAdapter) {
|
|
|
774
837
|
const expiration = config.expiration;
|
|
775
838
|
const onExpired = config.onExpired;
|
|
776
839
|
const expirationTtlMs = expiration?.ttlMs;
|
|
777
|
-
const memoryExpiration = expiration && isMemory ?
|
|
840
|
+
const memoryExpiration = expiration && isMemory ? memoryExpirationDeadlines : null;
|
|
778
841
|
const readCache = !isMemory && config.readCache === true;
|
|
842
|
+
const rawCacheRepresentation = isBiometric ? "biometric" : "plain";
|
|
779
843
|
const coalesceDiskWrites = config.scope === _Storage.StorageScope.Disk && config.coalesceDiskWrites === true;
|
|
780
844
|
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
|
|
781
845
|
const defaultValue = config.defaultValue;
|
|
@@ -845,28 +909,40 @@ function createStorageCore(buildAdapter) {
|
|
|
845
909
|
const memoryStored = memoryStore.get(storageKey);
|
|
846
910
|
return typeof memoryStored === "string" ? (0, _internal.unescapeCollidingRawValue)(memoryStored) : memoryStored;
|
|
847
911
|
}
|
|
912
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
913
|
+
if (durability.hasPendingDiskWrite(storageKey)) {
|
|
914
|
+
return durability.readPendingDiskWrite(storageKey);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
|
|
918
|
+
if (durability.hasPendingSecureWrite(storageKey)) {
|
|
919
|
+
return durability.readPendingSecureWrite(storageKey);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
848
922
|
migrateRenamesIfNeeded();
|
|
849
923
|
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
return pending;
|
|
924
|
+
if (durability.hasPendingDiskWrite(storageKey)) {
|
|
925
|
+
return durability.readPendingDiskWrite(storageKey);
|
|
853
926
|
}
|
|
854
927
|
}
|
|
855
928
|
if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
return pending;
|
|
929
|
+
if (durability.hasPendingSecureWrite(storageKey)) {
|
|
930
|
+
return durability.readPendingSecureWrite(storageKey);
|
|
859
931
|
}
|
|
860
932
|
}
|
|
861
933
|
if (readCache) {
|
|
862
|
-
const
|
|
863
|
-
const
|
|
864
|
-
if (
|
|
865
|
-
return
|
|
934
|
+
const scope = resolveNonMemoryScope();
|
|
935
|
+
const cachedEntry = getCachedRawValueEntry(scope, storageKey);
|
|
936
|
+
if (cachedEntry?.has(rawCacheRepresentation)) {
|
|
937
|
+
return cachedEntry.get(rawCacheRepresentation);
|
|
866
938
|
}
|
|
867
939
|
}
|
|
868
940
|
if (isBiometric) {
|
|
869
|
-
|
|
941
|
+
const raw = readBackendRaw(() => adapter.backend.getSecureBiometric(storageKey));
|
|
942
|
+
if (readCache) {
|
|
943
|
+
cacheRawValue(resolveNonMemoryScope(), storageKey, raw, rawCacheRepresentation);
|
|
944
|
+
}
|
|
945
|
+
return raw;
|
|
870
946
|
}
|
|
871
947
|
const raw = readBackendRaw(() => adapter.backend.get(storageKey, config.scope));
|
|
872
948
|
cacheRawValue(resolveNonMemoryScope(), storageKey, raw);
|
|
@@ -878,7 +954,8 @@ function createStorageCore(buildAdapter) {
|
|
|
878
954
|
} catch (error) {
|
|
879
955
|
onReadError?.(error);
|
|
880
956
|
if (fallbackToCacheOnReadError) {
|
|
881
|
-
const
|
|
957
|
+
const scope = resolveNonMemoryScope();
|
|
958
|
+
const cached = readCachedRawValue(scope, storageKey, rawCacheRepresentation);
|
|
882
959
|
if (cached !== undefined) {
|
|
883
960
|
return cached;
|
|
884
961
|
}
|
|
@@ -887,26 +964,375 @@ function createStorageCore(buildAdapter) {
|
|
|
887
964
|
throw error;
|
|
888
965
|
}
|
|
889
966
|
};
|
|
890
|
-
|
|
967
|
+
function getRenameSourceKeys() {
|
|
968
|
+
return renameFromKeys.filter(key => key !== storageKey);
|
|
969
|
+
}
|
|
970
|
+
function getRenameSourceRaw(key) {
|
|
971
|
+
return isBiometric ? adapter.backend.getSecureBiometric(key) : adapter.backend.get(key, config.scope);
|
|
972
|
+
}
|
|
973
|
+
function invalidateRenameSourceCache(key) {
|
|
974
|
+
if (nonMemoryScope !== null) {
|
|
975
|
+
invalidateRawCache(nonMemoryScope, key);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
function removeRenameSource(key) {
|
|
979
|
+
invalidateRenameSourceCache(key);
|
|
980
|
+
if (isBiometric) {
|
|
981
|
+
adapter.backend.deleteSecureBiometric(key);
|
|
982
|
+
invalidateRenameSourceCache(key);
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
adapter.backend.remove(key, config.scope);
|
|
986
|
+
invalidateRenameSourceCache(key);
|
|
987
|
+
}
|
|
988
|
+
function restoreRenameSource(snapshot) {
|
|
989
|
+
invalidateRenameSourceCache(snapshot.key);
|
|
990
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
991
|
+
if (snapshot.biometricValue !== undefined) {
|
|
992
|
+
adapter.backend.setSecureBiometricWithLevel(snapshot.key, snapshot.biometricValue, resolvedBiometricLevel === _Storage.BiometricLevel.None ? _Storage.BiometricLevel.BiometryOnly : resolvedBiometricLevel);
|
|
993
|
+
} else {
|
|
994
|
+
adapter.backend.deleteSecureBiometric(snapshot.key);
|
|
995
|
+
}
|
|
996
|
+
if (snapshot.plainValue !== undefined) {
|
|
997
|
+
if (adapter.applyAccessControlOnSecureRawWrite) {
|
|
998
|
+
adapter.backend.setSecureAccessControl(secureAccessControl ?? secureDefaultAccessControl);
|
|
999
|
+
}
|
|
1000
|
+
adapter.backend.set(snapshot.key, snapshot.plainValue, config.scope);
|
|
1001
|
+
} else if (snapshot.biometricValue === undefined) {
|
|
1002
|
+
adapter.backend.remove(snapshot.key, config.scope);
|
|
1003
|
+
}
|
|
1004
|
+
invalidateRenameSourceCache(snapshot.key);
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
if (snapshot.plainValue !== undefined) {
|
|
1008
|
+
adapter.backend.set(snapshot.key, snapshot.plainValue, config.scope);
|
|
1009
|
+
} else {
|
|
1010
|
+
adapter.backend.remove(snapshot.key, config.scope);
|
|
1011
|
+
}
|
|
1012
|
+
invalidateRenameSourceCache(snapshot.key);
|
|
1013
|
+
}
|
|
1014
|
+
function readRenameSnapshots() {
|
|
1015
|
+
return getRenameSourceKeys().flatMap(key => {
|
|
1016
|
+
const plainValue = nonMemoryScope === _Storage.StorageScope.Secure ? adapter.backend.get(key, config.scope) : getRenameSourceRaw(key);
|
|
1017
|
+
const biometricValue = nonMemoryScope === _Storage.StorageScope.Secure ? adapter.backend.getSecureBiometric(key) : undefined;
|
|
1018
|
+
if (plainValue === undefined && biometricValue === undefined) {
|
|
1019
|
+
return [];
|
|
1020
|
+
}
|
|
1021
|
+
return [{
|
|
1022
|
+
key,
|
|
1023
|
+
...(plainValue === undefined ? {} : {
|
|
1024
|
+
plainValue
|
|
1025
|
+
}),
|
|
1026
|
+
...(biometricValue === undefined ? {} : {
|
|
1027
|
+
biometricValue
|
|
1028
|
+
})
|
|
1029
|
+
}];
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
function selectedRenameValue(snapshot) {
|
|
1033
|
+
return isBiometric ? snapshot.biometricValue : snapshot.plainValue;
|
|
1034
|
+
}
|
|
1035
|
+
function hasPendingCurrentWrite() {
|
|
1036
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1037
|
+
return durability.hasPendingDiskWrite(storageKey);
|
|
1038
|
+
}
|
|
1039
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
|
|
1040
|
+
return durability.hasPendingSecureWrite(storageKey);
|
|
1041
|
+
}
|
|
1042
|
+
return false;
|
|
1043
|
+
}
|
|
1044
|
+
function hasPendingRenameSourceWrite() {
|
|
1045
|
+
if (isBiometric || nonMemoryScope === null) {
|
|
1046
|
+
return false;
|
|
1047
|
+
}
|
|
1048
|
+
return getRenameSourceKeys().some(key => nonMemoryScope === _Storage.StorageScope.Disk ? durability.hasPendingDiskWrite(key) : durability.hasPendingSecureWrite(key));
|
|
1049
|
+
}
|
|
1050
|
+
function flushPendingRenameSourceWrites() {
|
|
1051
|
+
if (!hasPendingRenameSourceWrite()) {
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1055
|
+
flushDiskWrites();
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
flushSecureWrites();
|
|
1059
|
+
}
|
|
1060
|
+
function clearPendingCurrentWriteIf(write) {
|
|
1061
|
+
if (write === undefined) {
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk && "generation" in write) {
|
|
1065
|
+
durability.clearPendingDiskWriteIf(write);
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
|
|
1069
|
+
durability.clearPendingSecureWriteIf(write);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
function flushPendingCurrentWrite() {
|
|
1073
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1074
|
+
flushDiskWrites();
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
|
|
1078
|
+
flushSecureWrites();
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
function removeCurrentBackendValue() {
|
|
1082
|
+
invalidateRawCache(resolveNonMemoryScope(), storageKey);
|
|
1083
|
+
if (isBiometric) {
|
|
1084
|
+
adapter.backend.deleteSecureBiometric(storageKey);
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
adapter.backend.remove(storageKey, config.scope);
|
|
1088
|
+
}
|
|
1089
|
+
function scheduleRenameSourceCleanup() {
|
|
1090
|
+
const sourceKeys = getRenameSourceKeys();
|
|
1091
|
+
sourceKeys.forEach(invalidateRenameSourceCache);
|
|
1092
|
+
if (isBiometric) {
|
|
1093
|
+
sourceKeys.forEach(removeRenameSource);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk && (coalesceDiskWrites || isDiskWritesAsync())) {
|
|
1097
|
+
sourceKeys.forEach(key => {
|
|
1098
|
+
scheduleDiskWrite(key, undefined);
|
|
1099
|
+
});
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure && coalesceSecureWrites) {
|
|
1103
|
+
sourceKeys.forEach(key => {
|
|
1104
|
+
scheduleSecureWrite(key, undefined, secureAccessControl ?? secureDefaultAccessControl);
|
|
1105
|
+
});
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
sourceKeys.forEach(removeRenameSource);
|
|
1109
|
+
}
|
|
1110
|
+
let atomicMutationDepth = 0;
|
|
1111
|
+
function getItemStateKeys() {
|
|
1112
|
+
return Array.from(new Set([storageKey, ...getRenameSourceKeys()]));
|
|
1113
|
+
}
|
|
1114
|
+
function captureItemState() {
|
|
1115
|
+
const records = getItemStateKeys().map(key => {
|
|
1116
|
+
let pending;
|
|
1117
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1118
|
+
if (durability.hasPendingDiskWrite(key)) {
|
|
1119
|
+
pending = {
|
|
1120
|
+
value: durability.readPendingDiskWrite(key)
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
} else if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric && durability.hasPendingSecureWrite(key)) {
|
|
1124
|
+
const accessControl = durability.readPendingSecureAccessControl(key);
|
|
1125
|
+
pending = {
|
|
1126
|
+
value: durability.readPendingSecureWrite(key),
|
|
1127
|
+
...(accessControl === undefined ? {} : {
|
|
1128
|
+
accessControl
|
|
1129
|
+
})
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
return {
|
|
1133
|
+
key,
|
|
1134
|
+
plainValue: nonMemoryScope === null ? undefined : adapter.backend.get(key, config.scope),
|
|
1135
|
+
biometricValue: nonMemoryScope === _Storage.StorageScope.Secure ? adapter.backend.getSecureBiometric(key) : undefined,
|
|
1136
|
+
...(pending === undefined ? {} : {
|
|
1137
|
+
pending
|
|
1138
|
+
})
|
|
1139
|
+
};
|
|
1140
|
+
});
|
|
1141
|
+
return {
|
|
1142
|
+
records,
|
|
1143
|
+
renamesMigrated
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
function clearPendingItemWrite(key) {
|
|
1147
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1148
|
+
durability.clearPendingDiskWrite(key);
|
|
1149
|
+
} else if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
|
|
1150
|
+
durability.clearPendingSecureWrite(key);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
function restoreItemState(snapshot) {
|
|
1154
|
+
const rollbackErrors = [];
|
|
1155
|
+
const recordsByKey = new Map(snapshot.records.map(record => [record.key, record]));
|
|
1156
|
+
snapshot.records.forEach(({
|
|
1157
|
+
key
|
|
1158
|
+
}) => {
|
|
1159
|
+
clearPendingItemWrite(key);
|
|
1160
|
+
if (nonMemoryScope !== null) {
|
|
1161
|
+
invalidateRawCache(nonMemoryScope, key);
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
const biometricRecords = snapshot.records.filter(({
|
|
1165
|
+
biometricValue
|
|
1166
|
+
}) => biometricValue !== undefined);
|
|
1167
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
1168
|
+
biometricRecords.forEach(({
|
|
1169
|
+
key,
|
|
1170
|
+
biometricValue
|
|
1171
|
+
}) => {
|
|
1172
|
+
try {
|
|
1173
|
+
adapter.backend.setSecureBiometricWithLevel(key, biometricValue, resolvedBiometricLevel === _Storage.BiometricLevel.None ? _Storage.BiometricLevel.BiometryOnly : resolvedBiometricLevel);
|
|
1174
|
+
cacheRawValue(_Storage.StorageScope.Secure, key, biometricValue, "biometric");
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
rollbackErrors.push({
|
|
1177
|
+
label: "rollback biometric",
|
|
1178
|
+
error
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
const plainSets = new Map();
|
|
1184
|
+
const plainRemoves = [];
|
|
1185
|
+
snapshot.records.forEach(({
|
|
1186
|
+
key,
|
|
1187
|
+
plainValue,
|
|
1188
|
+
biometricValue
|
|
1189
|
+
}) => {
|
|
1190
|
+
if (plainValue === undefined) {
|
|
1191
|
+
if (biometricValue === undefined) {
|
|
1192
|
+
plainRemoves.push(key);
|
|
1193
|
+
}
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
const accessControl = secureAccessControl ?? secureDefaultAccessControl;
|
|
1197
|
+
const group = plainSets.get(accessControl) ?? {
|
|
1198
|
+
keys: [],
|
|
1199
|
+
values: []
|
|
1200
|
+
};
|
|
1201
|
+
group.keys.push(key);
|
|
1202
|
+
group.values.push(plainValue);
|
|
1203
|
+
plainSets.set(accessControl, group);
|
|
1204
|
+
});
|
|
1205
|
+
plainSets.forEach((group, accessControl) => {
|
|
1206
|
+
try {
|
|
1207
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
1208
|
+
adapter.backend.setSecureAccessControl(accessControl);
|
|
1209
|
+
}
|
|
1210
|
+
adapter.backend.setBatch(group.keys, group.values, config.scope);
|
|
1211
|
+
group.keys.forEach((key, index) => {
|
|
1212
|
+
cacheRawValue(resolveNonMemoryScope(), key, group.values[index], "plain");
|
|
1213
|
+
});
|
|
1214
|
+
} catch (error) {
|
|
1215
|
+
rollbackErrors.push({
|
|
1216
|
+
label: "rollback plain set",
|
|
1217
|
+
error
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
if (plainRemoves.length > 0) {
|
|
1222
|
+
try {
|
|
1223
|
+
adapter.backend.removeBatch(plainRemoves, config.scope);
|
|
1224
|
+
plainRemoves.forEach(key => {
|
|
1225
|
+
cacheRawValue(resolveNonMemoryScope(), key, undefined, "plain");
|
|
1226
|
+
});
|
|
1227
|
+
} catch (error) {
|
|
1228
|
+
rollbackErrors.push({
|
|
1229
|
+
label: "rollback plain remove",
|
|
1230
|
+
error
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
1235
|
+
snapshot.records.forEach(({
|
|
1236
|
+
key,
|
|
1237
|
+
biometricValue
|
|
1238
|
+
}) => {
|
|
1239
|
+
if (biometricValue !== undefined) {
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
try {
|
|
1243
|
+
adapter.backend.deleteSecureBiometric(key);
|
|
1244
|
+
cacheRawValue(_Storage.StorageScope.Secure, key, undefined, "biometric");
|
|
1245
|
+
} catch (error) {
|
|
1246
|
+
rollbackErrors.push({
|
|
1247
|
+
label: "rollback biometric remove",
|
|
1248
|
+
error
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
snapshot.records.forEach(({
|
|
1254
|
+
key,
|
|
1255
|
+
pending
|
|
1256
|
+
}) => {
|
|
1257
|
+
if (pending === undefined || nonMemoryScope === null) {
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1261
|
+
scheduleDiskWrite(key, pending.value);
|
|
1262
|
+
} else if (!isBiometric) {
|
|
1263
|
+
scheduleSecureWrite(key, pending.value, pending.accessControl ?? secureAccessControl ?? secureDefaultAccessControl);
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
recordsByKey.forEach(({
|
|
1267
|
+
key
|
|
1268
|
+
}) => {
|
|
1269
|
+
if (nonMemoryScope !== null) {
|
|
1270
|
+
invalidateRawCache(nonMemoryScope, key);
|
|
1271
|
+
}
|
|
1272
|
+
});
|
|
1273
|
+
renamesMigrated = snapshot.renamesMigrated;
|
|
1274
|
+
invalidateParsedCache();
|
|
1275
|
+
return rollbackErrors;
|
|
1276
|
+
}
|
|
1277
|
+
function runAtomicItemMutation(mutation) {
|
|
1278
|
+
if (isMemory || renameFromKeys.length === 0 || atomicMutationDepth > 0) {
|
|
1279
|
+
mutation();
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
const snapshot = captureItemState();
|
|
1283
|
+
atomicMutationDepth += 1;
|
|
1284
|
+
try {
|
|
1285
|
+
mutation();
|
|
1286
|
+
} catch (primaryError) {
|
|
1287
|
+
const rollbackErrors = restoreItemState(snapshot);
|
|
1288
|
+
if (rollbackErrors.length > 0) {
|
|
1289
|
+
throw (0, _shared.createStorageCompositeError)("item mutation rollback", primaryError, rollbackErrors);
|
|
1290
|
+
}
|
|
1291
|
+
throw primaryError;
|
|
1292
|
+
} finally {
|
|
1293
|
+
atomicMutationDepth -= 1;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
const writeStoredRaw = (rawValue, options = {}) => {
|
|
1297
|
+
const cleanupRenameSources = options.cleanupRenameSources !== false;
|
|
891
1298
|
const oldValue = undefined;
|
|
892
1299
|
if (isBiometric) {
|
|
893
|
-
|
|
1300
|
+
invalidateRawCache(_Storage.StorageScope.Secure, storageKey);
|
|
1301
|
+
runSecurePromotion(storageKey, () => {
|
|
1302
|
+
try {
|
|
1303
|
+
adapter.backend.setSecureBiometricWithLevel(storageKey, rawValue, resolvedBiometricLevel);
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
throw (0, _shared.normalizeStorageError)(error);
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
if (cleanupRenameSources) {
|
|
1309
|
+
scheduleRenameSourceCleanup();
|
|
1310
|
+
}
|
|
894
1311
|
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", adapter.changeSource);
|
|
895
|
-
return;
|
|
1312
|
+
return undefined;
|
|
1313
|
+
}
|
|
1314
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
1315
|
+
invalidateRawCache(_Storage.StorageScope.Secure, storageKey);
|
|
896
1316
|
}
|
|
897
1317
|
cacheRawValue(resolveNonMemoryScope(), storageKey, rawValue);
|
|
898
1318
|
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
899
1319
|
if (coalesceDiskWrites || isDiskWritesAsync()) {
|
|
900
|
-
scheduleDiskWrite(storageKey, rawValue);
|
|
1320
|
+
const pendingWrite = scheduleDiskWrite(storageKey, rawValue);
|
|
1321
|
+
if (cleanupRenameSources) {
|
|
1322
|
+
scheduleRenameSourceCleanup();
|
|
1323
|
+
}
|
|
901
1324
|
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", adapter.changeSource);
|
|
902
|
-
return;
|
|
1325
|
+
return pendingWrite;
|
|
903
1326
|
}
|
|
904
1327
|
clearPendingDiskWrite(storageKey);
|
|
905
1328
|
}
|
|
906
1329
|
if (coalesceSecureWrites) {
|
|
907
|
-
scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
|
|
1330
|
+
const pendingWrite = scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
|
|
1331
|
+
if (cleanupRenameSources) {
|
|
1332
|
+
scheduleRenameSourceCleanup();
|
|
1333
|
+
}
|
|
908
1334
|
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", adapter.changeSource);
|
|
909
|
-
return;
|
|
1335
|
+
return pendingWrite;
|
|
910
1336
|
}
|
|
911
1337
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
912
1338
|
clearPendingSecureWrite(storageKey);
|
|
@@ -915,41 +1341,102 @@ function createStorageCore(buildAdapter) {
|
|
|
915
1341
|
}
|
|
916
1342
|
}
|
|
917
1343
|
adapter.backend.set(storageKey, rawValue, config.scope);
|
|
1344
|
+
if (cleanupRenameSources) {
|
|
1345
|
+
scheduleRenameSourceCleanup();
|
|
1346
|
+
}
|
|
918
1347
|
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", adapter.changeSource);
|
|
1348
|
+
return undefined;
|
|
919
1349
|
};
|
|
920
1350
|
const migrateRenamesIfNeeded = () => {
|
|
921
1351
|
if (renamesMigrated) {
|
|
922
1352
|
return;
|
|
923
1353
|
}
|
|
924
1354
|
try {
|
|
1355
|
+
flushPendingRenameSourceWrites();
|
|
925
1356
|
const hasCurrent = isBiometric ? adapter.backend.hasSecureBiometric(storageKey) : adapter.backend.has(storageKey, config.scope);
|
|
1357
|
+
const snapshots = readRenameSnapshots();
|
|
926
1358
|
if (hasCurrent) {
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1359
|
+
try {
|
|
1360
|
+
snapshots.forEach(({
|
|
1361
|
+
key
|
|
1362
|
+
}) => {
|
|
1363
|
+
removeRenameSource(key);
|
|
1364
|
+
});
|
|
1365
|
+
} catch (primaryError) {
|
|
1366
|
+
const rollbackErrors = [];
|
|
1367
|
+
snapshots.forEach(snapshot => {
|
|
1368
|
+
try {
|
|
1369
|
+
restoreRenameSource(snapshot);
|
|
1370
|
+
} catch (error) {
|
|
1371
|
+
rollbackErrors.push({
|
|
1372
|
+
label: "rollback rename source",
|
|
1373
|
+
error
|
|
1374
|
+
});
|
|
931
1375
|
}
|
|
932
|
-
}
|
|
933
|
-
|
|
1376
|
+
});
|
|
1377
|
+
if (rollbackErrors.length > 0) {
|
|
1378
|
+
throw (0, _shared.createStorageCompositeError)("rename cleanup", primaryError, rollbackErrors);
|
|
934
1379
|
}
|
|
1380
|
+
throw primaryError;
|
|
935
1381
|
}
|
|
936
1382
|
renamesMigrated = true;
|
|
937
1383
|
return;
|
|
938
1384
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1385
|
+
const snapshot = snapshots.find(candidate => selectedRenameValue(candidate) !== undefined);
|
|
1386
|
+
if (snapshot === undefined) {
|
|
1387
|
+
renamesMigrated = true;
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
let writeAttempted = false;
|
|
1391
|
+
let pendingCurrentWrite;
|
|
1392
|
+
try {
|
|
1393
|
+
writeAttempted = true;
|
|
1394
|
+
pendingCurrentWrite = writeStoredRaw(selectedRenameValue(snapshot), {
|
|
1395
|
+
cleanupRenameSources: false
|
|
1396
|
+
});
|
|
1397
|
+
if (hasPendingCurrentWrite()) {
|
|
1398
|
+
flushPendingCurrentWrite();
|
|
943
1399
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1400
|
+
snapshots.forEach(({
|
|
1401
|
+
key
|
|
1402
|
+
}) => {
|
|
1403
|
+
removeRenameSource(key);
|
|
1404
|
+
});
|
|
1405
|
+
renamesMigrated = true;
|
|
1406
|
+
} catch (primaryError) {
|
|
1407
|
+
const rollbackErrors = [];
|
|
1408
|
+
clearPendingCurrentWriteIf(pendingCurrentWrite);
|
|
1409
|
+
if (writeAttempted) {
|
|
1410
|
+
try {
|
|
1411
|
+
removeCurrentBackendValue();
|
|
1412
|
+
} catch (error) {
|
|
1413
|
+
rollbackErrors.push({
|
|
1414
|
+
label: "rollback current value",
|
|
1415
|
+
error
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
949
1418
|
}
|
|
950
|
-
|
|
1419
|
+
snapshots.forEach(renameSnapshot => {
|
|
1420
|
+
try {
|
|
1421
|
+
restoreRenameSource(renameSnapshot);
|
|
1422
|
+
} catch (error) {
|
|
1423
|
+
rollbackErrors.push({
|
|
1424
|
+
label: "rollback rename source",
|
|
1425
|
+
error
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1429
|
+
invalidateRawCache(resolveNonMemoryScope(), storageKey);
|
|
1430
|
+
snapshots.forEach(({
|
|
1431
|
+
key
|
|
1432
|
+
}) => {
|
|
1433
|
+
invalidateRenameSourceCache(key);
|
|
1434
|
+
});
|
|
1435
|
+
if (rollbackErrors.length > 0) {
|
|
1436
|
+
throw (0, _shared.createStorageCompositeError)("rename migration", primaryError, rollbackErrors);
|
|
1437
|
+
}
|
|
1438
|
+
throw primaryError;
|
|
951
1439
|
}
|
|
952
|
-
renamesMigrated = true;
|
|
953
1440
|
} catch (error) {
|
|
954
1441
|
if ((0, _shared.isKeychainLockedError)(error)) {
|
|
955
1442
|
onReadError?.(error);
|
|
@@ -959,16 +1446,22 @@ function createStorageCore(buildAdapter) {
|
|
|
959
1446
|
}
|
|
960
1447
|
};
|
|
961
1448
|
const removeStoredRaw = (operation = "remove") => {
|
|
962
|
-
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1449
|
+
const oldValue = isBiometric ? getEventRawValueForRepresentation(config.scope, storageKey, "biometric") : getEventRawValue(config.scope, storageKey);
|
|
963
1450
|
if (isBiometric) {
|
|
1451
|
+
invalidateRawCache(_Storage.StorageScope.Secure, storageKey);
|
|
1452
|
+
scheduleRenameSourceCleanup();
|
|
964
1453
|
adapter.backend.deleteSecureBiometric(storageKey);
|
|
965
1454
|
emitKeyChange(config.scope, storageKey, oldValue, undefined, operation, adapter.changeSource);
|
|
966
1455
|
return;
|
|
967
1456
|
}
|
|
1457
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
1458
|
+
invalidateRawCache(_Storage.StorageScope.Secure, storageKey);
|
|
1459
|
+
}
|
|
968
1460
|
cacheRawValue(resolveNonMemoryScope(), storageKey, undefined);
|
|
969
1461
|
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
970
1462
|
if (coalesceDiskWrites || isDiskWritesAsync()) {
|
|
971
1463
|
scheduleDiskWrite(storageKey, undefined);
|
|
1464
|
+
scheduleRenameSourceCleanup();
|
|
972
1465
|
emitKeyChange(config.scope, storageKey, oldValue, undefined, operation, adapter.changeSource);
|
|
973
1466
|
return;
|
|
974
1467
|
}
|
|
@@ -976,12 +1469,14 @@ function createStorageCore(buildAdapter) {
|
|
|
976
1469
|
}
|
|
977
1470
|
if (coalesceSecureWrites) {
|
|
978
1471
|
scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
|
|
1472
|
+
scheduleRenameSourceCleanup();
|
|
979
1473
|
emitKeyChange(config.scope, storageKey, oldValue, undefined, operation, adapter.changeSource);
|
|
980
1474
|
return;
|
|
981
1475
|
}
|
|
982
1476
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
983
1477
|
clearPendingSecureWrite(storageKey);
|
|
984
1478
|
}
|
|
1479
|
+
scheduleRenameSourceCleanup();
|
|
985
1480
|
adapter.backend.remove(storageKey, config.scope);
|
|
986
1481
|
emitKeyChange(config.scope, storageKey, oldValue, undefined, operation, adapter.changeSource);
|
|
987
1482
|
};
|
|
@@ -990,6 +1485,8 @@ function createStorageCore(buildAdapter) {
|
|
|
990
1485
|
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
991
1486
|
if (memoryExpiration) {
|
|
992
1487
|
memoryExpiration.set(storageKey, Date.now() + (expirationTtlMs ?? 0));
|
|
1488
|
+
} else {
|
|
1489
|
+
memoryExpirationDeadlines.delete(storageKey);
|
|
993
1490
|
}
|
|
994
1491
|
const storedValue = typeof value === "string" ? (0, _internal.escapeCollidingRawValue)(value) : value;
|
|
995
1492
|
memoryStore.set(storageKey, storedValue);
|
|
@@ -1038,7 +1535,9 @@ function createStorageCore(buildAdapter) {
|
|
|
1038
1535
|
if (lastExpiresAt > Date.now()) {
|
|
1039
1536
|
return lastValue;
|
|
1040
1537
|
}
|
|
1041
|
-
|
|
1538
|
+
runAtomicItemMutation(() => {
|
|
1539
|
+
removeStoredRaw("expire");
|
|
1540
|
+
});
|
|
1042
1541
|
invalidateParsedCache();
|
|
1043
1542
|
onExpired?.(storageKey);
|
|
1044
1543
|
lastValue = ensureValidatedValue(defaultValue, false);
|
|
@@ -1076,7 +1575,9 @@ function createStorageCore(buildAdapter) {
|
|
|
1076
1575
|
if ((0, _internal.isStoredEnvelope)(parsed)) {
|
|
1077
1576
|
envelopeExpiresAt = parsed.expiresAt;
|
|
1078
1577
|
if (parsed.expiresAt <= Date.now()) {
|
|
1079
|
-
|
|
1578
|
+
runAtomicItemMutation(() => {
|
|
1579
|
+
removeStoredRaw("expire");
|
|
1580
|
+
});
|
|
1080
1581
|
invalidateParsedCache();
|
|
1081
1582
|
onExpired?.(storageKey);
|
|
1082
1583
|
lastValue = ensureValidatedValue(defaultValue, false);
|
|
@@ -1113,36 +1614,45 @@ function createStorageCore(buildAdapter) {
|
|
|
1113
1614
|
}));
|
|
1114
1615
|
const set = valueOrFn => {
|
|
1115
1616
|
measureOperation("item:set", config.scope, () => {
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1617
|
+
runAtomicItemMutation(() => {
|
|
1618
|
+
const newValue = (0, _shared.isUpdater)(valueOrFn) ? valueOrFn(getInternal()) : valueOrFn;
|
|
1619
|
+
if (validate && !validate(newValue)) {
|
|
1620
|
+
throw new Error(`Validation failed for key "${storageKey}" in scope "${_Storage.StorageScope[config.scope]}".`);
|
|
1621
|
+
}
|
|
1622
|
+
invalidateParsedCache();
|
|
1623
|
+
writeValueWithoutValidation(newValue);
|
|
1624
|
+
});
|
|
1122
1625
|
});
|
|
1123
1626
|
};
|
|
1124
1627
|
const setIfVersion = (version, valueOrFn) => measureOperation("item:setIfVersion", config.scope, () => {
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1628
|
+
let didSet = false;
|
|
1629
|
+
runAtomicItemMutation(() => {
|
|
1630
|
+
const currentVersion = getCurrentVersion();
|
|
1631
|
+
if (currentVersion !== version) {
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
set(valueOrFn);
|
|
1635
|
+
didSet = true;
|
|
1636
|
+
});
|
|
1637
|
+
return didSet;
|
|
1131
1638
|
});
|
|
1132
1639
|
const deleteItem = () => {
|
|
1133
1640
|
measureOperation("item:delete", config.scope, () => {
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
memoryExpiration
|
|
1641
|
+
runAtomicItemMutation(() => {
|
|
1642
|
+
invalidateParsedCache();
|
|
1643
|
+
if (isMemory) {
|
|
1644
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1645
|
+
if (memoryExpiration) {
|
|
1646
|
+
memoryExpiration.delete(storageKey);
|
|
1647
|
+
}
|
|
1648
|
+
memoryExpirationDeadlines.delete(storageKey);
|
|
1649
|
+
memoryStore.delete(storageKey);
|
|
1650
|
+
(0, _shared.notifyKeyListeners)(memoryListeners, storageKey);
|
|
1651
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "memory");
|
|
1652
|
+
return;
|
|
1139
1653
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "memory");
|
|
1143
|
-
return;
|
|
1144
|
-
}
|
|
1145
|
-
removeStoredRaw();
|
|
1654
|
+
removeStoredRaw();
|
|
1655
|
+
});
|
|
1146
1656
|
});
|
|
1147
1657
|
};
|
|
1148
1658
|
const merge = partial => {
|
|
@@ -1235,14 +1745,18 @@ function createStorageCore(buildAdapter) {
|
|
|
1235
1745
|
invalidateParsedCache();
|
|
1236
1746
|
},
|
|
1237
1747
|
_deleteMemoryEntry: () => {
|
|
1238
|
-
|
|
1239
|
-
memoryExpiration.delete(storageKey);
|
|
1240
|
-
}
|
|
1748
|
+
memoryExpirationDeadlines.delete(storageKey);
|
|
1241
1749
|
memoryStore.delete(storageKey);
|
|
1242
1750
|
invalidateParsedCache();
|
|
1243
1751
|
},
|
|
1244
1752
|
_hasValidation: validate !== undefined,
|
|
1245
1753
|
_hasExpiration: expiration !== undefined,
|
|
1754
|
+
_hasRenameFrom: renameFromKeys.length > 0,
|
|
1755
|
+
_renameFromKeys: renameFromKeys,
|
|
1756
|
+
_getRenameMigrationState: () => renamesMigrated,
|
|
1757
|
+
_setRenameMigrationState: migrated => {
|
|
1758
|
+
renamesMigrated = migrated;
|
|
1759
|
+
},
|
|
1246
1760
|
_readCacheEnabled: readCache,
|
|
1247
1761
|
_isBiometric: isBiometric,
|
|
1248
1762
|
_biometricLevel: resolvedBiometricLevel,
|
|
@@ -1264,6 +1778,14 @@ function createStorageCore(buildAdapter) {
|
|
|
1264
1778
|
}
|
|
1265
1779
|
groupSet.add(storageItem);
|
|
1266
1780
|
}
|
|
1781
|
+
if (isMemory) {
|
|
1782
|
+
let items = memoryItemsByKey.get(storageKey);
|
|
1783
|
+
if (!items) {
|
|
1784
|
+
items = new Set();
|
|
1785
|
+
memoryItemsByKey.set(storageKey, items);
|
|
1786
|
+
}
|
|
1787
|
+
items.add(storageItem);
|
|
1788
|
+
}
|
|
1267
1789
|
const registryKey = `${config.scope}:${storageKey}`;
|
|
1268
1790
|
registeredKeyCounts.set(registryKey, (registeredKeyCounts.get(registryKey) ?? 0) + 1);
|
|
1269
1791
|
return storageItem;
|
|
@@ -1283,24 +1805,21 @@ function createStorageCore(buildAdapter) {
|
|
|
1283
1805
|
const keyIndexes = [];
|
|
1284
1806
|
items.forEach((item, index) => {
|
|
1285
1807
|
if (scope === _Storage.StorageScope.Disk) {
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
rawValues[index] = pending;
|
|
1808
|
+
if (durability.hasPendingDiskWrite(item.key)) {
|
|
1809
|
+
rawValues[index] = durability.readPendingDiskWrite(item.key);
|
|
1289
1810
|
return;
|
|
1290
1811
|
}
|
|
1291
1812
|
}
|
|
1292
1813
|
if (scope === _Storage.StorageScope.Secure) {
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
rawValues[index] = pending;
|
|
1814
|
+
if (durability.hasPendingSecureWrite(item.key)) {
|
|
1815
|
+
rawValues[index] = durability.readPendingSecureWrite(item.key);
|
|
1296
1816
|
return;
|
|
1297
1817
|
}
|
|
1298
1818
|
}
|
|
1299
1819
|
if (item._readCacheEnabled === true) {
|
|
1300
|
-
const
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
rawValues[index] = cached;
|
|
1820
|
+
const cachedEntry = getCachedRawValueEntry(scope, item.key);
|
|
1821
|
+
if (cachedEntry?.has("plain")) {
|
|
1822
|
+
rawValues[index] = cachedEntry.get("plain");
|
|
1304
1823
|
return;
|
|
1305
1824
|
}
|
|
1306
1825
|
}
|
|
@@ -1395,6 +1914,10 @@ function createStorageCore(buildAdapter) {
|
|
|
1395
1914
|
const keys = secureEntries.map(({
|
|
1396
1915
|
item
|
|
1397
1916
|
}) => item.key);
|
|
1917
|
+
keys.forEach(key => {
|
|
1918
|
+
invalidateRawCache(scope, key);
|
|
1919
|
+
});
|
|
1920
|
+
const serializedValues = [];
|
|
1398
1921
|
const oldValues = shouldReadPreviousEventValues(scope) ? adapter.backend.getBatch(keys, scope).map(value => value === undefined ? undefined : (0, _internal.unescapeCollidingRawValue)(value)) : [];
|
|
1399
1922
|
const groupedByAccessControl = new Map();
|
|
1400
1923
|
secureEntries.forEach(({
|
|
@@ -1402,6 +1925,8 @@ function createStorageCore(buildAdapter) {
|
|
|
1402
1925
|
value,
|
|
1403
1926
|
internal
|
|
1404
1927
|
}) => {
|
|
1928
|
+
const serialized = item.serialize(value);
|
|
1929
|
+
serializedValues.push(serialized);
|
|
1405
1930
|
const accessControl = internal._secureAccessControl ?? secureDefaultAccessControl;
|
|
1406
1931
|
const existingGroup = groupedByAccessControl.get(accessControl);
|
|
1407
1932
|
const group = existingGroup ?? {
|
|
@@ -1409,7 +1934,7 @@ function createStorageCore(buildAdapter) {
|
|
|
1409
1934
|
values: []
|
|
1410
1935
|
};
|
|
1411
1936
|
group.keys.push(item.key);
|
|
1412
|
-
group.values.push(
|
|
1937
|
+
group.values.push(serialized);
|
|
1413
1938
|
if (!existingGroup) {
|
|
1414
1939
|
groupedByAccessControl.set(accessControl, group);
|
|
1415
1940
|
}
|
|
@@ -1421,10 +1946,8 @@ function createStorageCore(buildAdapter) {
|
|
|
1421
1946
|
cacheRawValue(scope, key, group.values[index]);
|
|
1422
1947
|
});
|
|
1423
1948
|
});
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
value
|
|
1427
|
-
}, index) => (0, _shared.createKeyChange)(scope, item.key, oldValues[index], item.serialize(value), "setBatch", adapter.changeSource)));
|
|
1949
|
+
const willEmitChanges = storageEvents.hasListeners(scope) || eventObserver !== undefined;
|
|
1950
|
+
emitBatchChange(scope, "setBatch", adapter.changeSource, willEmitChanges ? keys.map((key, index) => (0, _shared.createKeyChange)(scope, key, oldValues[index], serializedValues[index], "setBatch", adapter.changeSource)) : []);
|
|
1428
1951
|
return;
|
|
1429
1952
|
}
|
|
1430
1953
|
flushDiskWrites();
|
|
@@ -1464,6 +1987,12 @@ function createStorageCore(buildAdapter) {
|
|
|
1464
1987
|
emitBatchChange(scope, "removeBatch", "memory", changes);
|
|
1465
1988
|
return;
|
|
1466
1989
|
}
|
|
1990
|
+
if (items.some(item => asInternal(item)._hasRenameFrom)) {
|
|
1991
|
+
items.forEach(item => {
|
|
1992
|
+
asInternal(item).delete();
|
|
1993
|
+
});
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1467
1996
|
const keys = items.map(item => item.key);
|
|
1468
1997
|
if (scope === _Storage.StorageScope.Disk) {
|
|
1469
1998
|
flushDiskWrites();
|
|
@@ -1471,7 +2000,25 @@ function createStorageCore(buildAdapter) {
|
|
|
1471
2000
|
if (scope === _Storage.StorageScope.Secure) {
|
|
1472
2001
|
flushSecureWrites();
|
|
1473
2002
|
}
|
|
1474
|
-
const oldValues = shouldReadPreviousEventValues(scope) ?
|
|
2003
|
+
const oldValues = shouldReadPreviousEventValues(scope) ? (() => {
|
|
2004
|
+
const plainItems = items.filter(item => asInternal(item)._isBiometric !== true);
|
|
2005
|
+
const plainValues = plainItems.length === 0 ? [] : adapter.backend.getBatch(plainItems.map(item => item.key), scope).map(value => value === undefined ? undefined : (0, _internal.unescapeCollidingRawValue)(value));
|
|
2006
|
+
let plainIndex = 0;
|
|
2007
|
+
return items.map(item => {
|
|
2008
|
+
const internal = asInternal(item);
|
|
2009
|
+
if (internal._isBiometric === true) {
|
|
2010
|
+
return getEventRawValueForRepresentation(scope, item.key, "biometric");
|
|
2011
|
+
}
|
|
2012
|
+
const value = plainValues[plainIndex];
|
|
2013
|
+
plainIndex += 1;
|
|
2014
|
+
return value;
|
|
2015
|
+
});
|
|
2016
|
+
})() : [];
|
|
2017
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
2018
|
+
keys.forEach(key => {
|
|
2019
|
+
invalidateRawCache(scope, key);
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
1475
2022
|
adapter.backend.removeBatch(keys, scope);
|
|
1476
2023
|
keys.forEach(key => {
|
|
1477
2024
|
cacheRawValue(scope, key, undefined);
|
|
@@ -1519,33 +2066,74 @@ function createStorageCore(buildAdapter) {
|
|
|
1519
2066
|
}
|
|
1520
2067
|
const NOT_SET = Symbol();
|
|
1521
2068
|
const rollback = new Map();
|
|
1522
|
-
const
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
2069
|
+
const itemRenameStates = new Map();
|
|
2070
|
+
const rememberRollback = (key, item, includeOtherSecureRepresentation = false) => {
|
|
2071
|
+
const internal = item ? item : undefined;
|
|
2072
|
+
const representation = scope === _Storage.StorageScope.Secure && internal?._isBiometric === true ? "biometric" : "plain";
|
|
2073
|
+
const rememberRepresentation = representationToRemember => {
|
|
2074
|
+
const identity = JSON.stringify([scope, key, representationToRemember]);
|
|
2075
|
+
if (rollback.has(identity)) {
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
2079
|
+
const expiresAt = memoryExpirationDeadlines.get(key);
|
|
2080
|
+
rollback.set(identity, {
|
|
2081
|
+
key,
|
|
2082
|
+
representation: representationToRemember,
|
|
2083
|
+
record: {
|
|
2084
|
+
kind: "memory",
|
|
2085
|
+
value: memoryStore.has(key) ? memoryStore.get(key) : NOT_SET,
|
|
2086
|
+
...(expiresAt === undefined ? {} : {
|
|
2087
|
+
expiresAt
|
|
2088
|
+
})
|
|
2089
|
+
}
|
|
2090
|
+
});
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
2093
|
+
if (representationToRemember === "biometric") {
|
|
2094
|
+
rollback.set(identity, {
|
|
2095
|
+
key,
|
|
2096
|
+
representation: representationToRemember,
|
|
2097
|
+
record: {
|
|
2098
|
+
kind: "biometric",
|
|
2099
|
+
value: getStoredRawValueForRepresentation(key, scope, representationToRemember),
|
|
2100
|
+
level: internal?._biometricLevel !== undefined && internal._biometricLevel !== _Storage.BiometricLevel.None ? internal._biometricLevel : _Storage.BiometricLevel.BiometryOnly
|
|
2101
|
+
}
|
|
1538
2102
|
});
|
|
1539
2103
|
return;
|
|
1540
2104
|
}
|
|
1541
|
-
rollback.set(
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
2105
|
+
rollback.set(identity, {
|
|
2106
|
+
key,
|
|
2107
|
+
representation: representationToRemember,
|
|
2108
|
+
record: {
|
|
2109
|
+
kind: "raw",
|
|
2110
|
+
value: getStoredRawValueForRepresentation(key, scope, representationToRemember),
|
|
2111
|
+
...(scope === _Storage.StorageScope.Secure && internal?._secureAccessControl !== undefined ? {
|
|
2112
|
+
accessControl: internal._secureAccessControl
|
|
2113
|
+
} : {})
|
|
2114
|
+
}
|
|
1547
2115
|
});
|
|
2116
|
+
};
|
|
2117
|
+
if (representation === "biometric" || includeOtherSecureRepresentation) {
|
|
2118
|
+
rememberRepresentation("plain");
|
|
1548
2119
|
}
|
|
2120
|
+
rememberRepresentation(representation);
|
|
2121
|
+
if (includeOtherSecureRepresentation) {
|
|
2122
|
+
rememberRepresentation("biometric");
|
|
2123
|
+
}
|
|
2124
|
+
};
|
|
2125
|
+
const rememberItemRollback = item => {
|
|
2126
|
+
const internal = item;
|
|
2127
|
+
const includeOtherSecureRepresentation = scope === _Storage.StorageScope.Secure && (internal._isBiometric === true || internal._hasRenameFrom === true);
|
|
2128
|
+
rememberRollback(item.key, item, includeOtherSecureRepresentation);
|
|
2129
|
+
if (internal._getRenameMigrationState) {
|
|
2130
|
+
if (!itemRenameStates.has(internal)) {
|
|
2131
|
+
itemRenameStates.set(internal, internal._getRenameMigrationState());
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
(internal._renameFromKeys ?? EMPTY_KEYS).forEach(aliasKey => {
|
|
2135
|
+
rememberRollback(aliasKey, item, true);
|
|
2136
|
+
});
|
|
1549
2137
|
};
|
|
1550
2138
|
const tx = {
|
|
1551
2139
|
scope,
|
|
@@ -1555,49 +2143,85 @@ function createStorageCore(buildAdapter) {
|
|
|
1555
2143
|
setRawValue(key, value, scope);
|
|
1556
2144
|
},
|
|
1557
2145
|
removeRaw: key => {
|
|
1558
|
-
rememberRollback(key);
|
|
2146
|
+
rememberRollback(key, undefined, scope === _Storage.StorageScope.Secure);
|
|
1559
2147
|
removeRawValue(key, scope);
|
|
1560
2148
|
},
|
|
1561
2149
|
getItem: item => {
|
|
1562
2150
|
(0, _internal.assertBatchScope)([item], scope);
|
|
2151
|
+
rememberItemRollback(item);
|
|
1563
2152
|
return item.get();
|
|
1564
2153
|
},
|
|
1565
2154
|
setItem: (item, value) => {
|
|
1566
2155
|
(0, _internal.assertBatchScope)([item], scope);
|
|
1567
|
-
|
|
2156
|
+
rememberItemRollback(item);
|
|
1568
2157
|
item.set(value);
|
|
1569
2158
|
},
|
|
1570
2159
|
removeItem: item => {
|
|
1571
2160
|
(0, _internal.assertBatchScope)([item], scope);
|
|
1572
|
-
|
|
2161
|
+
rememberItemRollback(item);
|
|
1573
2162
|
item.delete();
|
|
1574
2163
|
}
|
|
1575
2164
|
};
|
|
1576
2165
|
try {
|
|
1577
2166
|
return transaction(tx);
|
|
1578
2167
|
} catch (error) {
|
|
1579
|
-
|
|
2168
|
+
itemRenameStates.forEach((migrated, item) => {
|
|
2169
|
+
item._setRenameMigrationState(migrated);
|
|
2170
|
+
});
|
|
2171
|
+
const rollbackEntries = Array.from(rollback.values()).reverse();
|
|
1580
2172
|
const rollbackSource = scope === _Storage.StorageScope.Memory ? "memory" : adapter.changeSource;
|
|
1581
|
-
const
|
|
2173
|
+
const rollbackErrors = [];
|
|
2174
|
+
const readRollbackEventValue = entry => {
|
|
2175
|
+
try {
|
|
2176
|
+
return getEventRawValueForRepresentation(scope, entry.key, entry.representation);
|
|
2177
|
+
} catch (readError) {
|
|
2178
|
+
rollbackErrors.push({
|
|
2179
|
+
label: `rollback read ${entry.representation}:${entry.key}`,
|
|
2180
|
+
error: readError
|
|
2181
|
+
});
|
|
2182
|
+
return undefined;
|
|
2183
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
const preRollbackValues = rollbackEntries.map(readRollbackEventValue);
|
|
1582
2186
|
if (scope === _Storage.StorageScope.Memory) {
|
|
1583
|
-
rollbackEntries.forEach(
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
2187
|
+
rollbackEntries.forEach(entry => {
|
|
2188
|
+
try {
|
|
2189
|
+
const record = entry.record;
|
|
2190
|
+
if (record.kind !== "memory") {
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
if (record.value === NOT_SET) {
|
|
2194
|
+
memoryStore.delete(entry.key);
|
|
2195
|
+
} else {
|
|
2196
|
+
memoryStore.set(entry.key, record.value);
|
|
2197
|
+
}
|
|
2198
|
+
if (record.expiresAt === undefined) {
|
|
2199
|
+
memoryExpirationDeadlines.delete(entry.key);
|
|
2200
|
+
} else {
|
|
2201
|
+
memoryExpirationDeadlines.set(entry.key, record.expiresAt);
|
|
2202
|
+
}
|
|
2203
|
+
invalidateMemoryItemCaches(entry.key);
|
|
2204
|
+
(0, _shared.notifyKeyListeners)(memoryListeners, entry.key);
|
|
2205
|
+
} catch (rollbackError) {
|
|
2206
|
+
rollbackErrors.push({
|
|
2207
|
+
label: `rollback ${entry.representation}:${entry.key}`,
|
|
2208
|
+
error: rollbackError
|
|
2209
|
+
});
|
|
1588
2210
|
}
|
|
1589
|
-
(0, _shared.notifyKeyListeners)(memoryListeners, key);
|
|
1590
2211
|
});
|
|
1591
2212
|
} else {
|
|
1592
2213
|
const groupedKeysToSet = new Map();
|
|
1593
2214
|
const keysToRemove = [];
|
|
1594
|
-
|
|
2215
|
+
const biometricEntries = [];
|
|
2216
|
+
const biometricValues = new Map();
|
|
2217
|
+
rollbackEntries.forEach(entry => {
|
|
2218
|
+
const {
|
|
2219
|
+
key,
|
|
2220
|
+
record
|
|
2221
|
+
} = entry;
|
|
1595
2222
|
if (record.kind === "biometric") {
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
} else {
|
|
1599
|
-
adapter.backend.setSecureBiometricWithLevel(key, record.value, record.level);
|
|
1600
|
-
}
|
|
2223
|
+
biometricEntries.push(entry);
|
|
2224
|
+
biometricValues.set(key, record.value);
|
|
1601
2225
|
return;
|
|
1602
2226
|
}
|
|
1603
2227
|
if (record.kind !== "raw") {
|
|
@@ -1620,29 +2244,108 @@ function createStorageCore(buildAdapter) {
|
|
|
1620
2244
|
}
|
|
1621
2245
|
});
|
|
1622
2246
|
if (scope === _Storage.StorageScope.Disk) {
|
|
1623
|
-
|
|
2247
|
+
try {
|
|
2248
|
+
flushDiskWrites();
|
|
2249
|
+
} catch (flushError) {
|
|
2250
|
+
rollbackErrors.push({
|
|
2251
|
+
label: "rollback flush disk",
|
|
2252
|
+
error: flushError
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
1624
2255
|
}
|
|
1625
2256
|
if (scope === _Storage.StorageScope.Secure) {
|
|
1626
|
-
|
|
2257
|
+
try {
|
|
2258
|
+
flushSecureWrites();
|
|
2259
|
+
} catch (flushError) {
|
|
2260
|
+
rollbackErrors.push({
|
|
2261
|
+
label: "rollback flush secure",
|
|
2262
|
+
error: flushError
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
1627
2265
|
}
|
|
2266
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
2267
|
+
rollbackEntries.forEach(entry => {
|
|
2268
|
+
invalidateRawCache(scope, entry.key);
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
biometricEntries.forEach(entry => {
|
|
2272
|
+
const {
|
|
2273
|
+
key,
|
|
2274
|
+
record
|
|
2275
|
+
} = entry;
|
|
2276
|
+
if (record.kind !== "biometric" || record.value === undefined) {
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
try {
|
|
2280
|
+
adapter.backend.setSecureBiometricWithLevel(key, record.value, record.level);
|
|
2281
|
+
cacheRawValue(_Storage.StorageScope.Secure, key, record.value, "biometric");
|
|
2282
|
+
} catch (rollbackError) {
|
|
2283
|
+
rollbackErrors.push({
|
|
2284
|
+
label: "rollback biometric",
|
|
2285
|
+
error: rollbackError
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2288
|
+
});
|
|
1628
2289
|
groupedKeysToSet.forEach((group, accessControl) => {
|
|
1629
|
-
|
|
1630
|
-
|
|
2290
|
+
try {
|
|
2291
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
2292
|
+
adapter.backend.setSecureAccessControl(accessControl);
|
|
2293
|
+
}
|
|
2294
|
+
adapter.backend.setBatch(group.keys, group.values, scope);
|
|
2295
|
+
group.keys.forEach((key, index) => {
|
|
2296
|
+
cacheRawValue(scope, key, group.values[index], "plain");
|
|
2297
|
+
});
|
|
2298
|
+
} catch (rollbackError) {
|
|
2299
|
+
rollbackErrors.push({
|
|
2300
|
+
label: "rollback plain setBatch",
|
|
2301
|
+
error: rollbackError
|
|
2302
|
+
});
|
|
1631
2303
|
}
|
|
1632
|
-
adapter.backend.setBatch(group.keys, group.values, scope);
|
|
1633
|
-
group.keys.forEach((key, index) => {
|
|
1634
|
-
cacheRawValue(scope, key, group.values[index]);
|
|
1635
|
-
});
|
|
1636
2304
|
});
|
|
1637
2305
|
if (keysToRemove.length > 0) {
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
2306
|
+
const keysToRemoveWithoutBiometric = keysToRemove.filter(key => biometricValues.get(key) === undefined);
|
|
2307
|
+
try {
|
|
2308
|
+
if (keysToRemoveWithoutBiometric.length > 0) {
|
|
2309
|
+
adapter.backend.removeBatch(keysToRemoveWithoutBiometric, scope);
|
|
2310
|
+
}
|
|
2311
|
+
keysToRemove.forEach(key => {
|
|
2312
|
+
cacheRawValue(scope, key, undefined, "plain");
|
|
2313
|
+
});
|
|
2314
|
+
} catch (rollbackError) {
|
|
2315
|
+
rollbackErrors.push({
|
|
2316
|
+
label: "rollback plain removeBatch",
|
|
2317
|
+
error: rollbackError
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
1642
2320
|
}
|
|
2321
|
+
biometricEntries.forEach(entry => {
|
|
2322
|
+
const {
|
|
2323
|
+
key,
|
|
2324
|
+
record
|
|
2325
|
+
} = entry;
|
|
2326
|
+
if (record.kind !== "biometric") {
|
|
2327
|
+
return;
|
|
2328
|
+
}
|
|
2329
|
+
if (record.value !== undefined) {
|
|
2330
|
+
return;
|
|
2331
|
+
}
|
|
2332
|
+
invalidateRawCache(_Storage.StorageScope.Secure, key);
|
|
2333
|
+
try {
|
|
2334
|
+
adapter.backend.deleteSecureBiometric(key);
|
|
2335
|
+
cacheRawValue(_Storage.StorageScope.Secure, key, record.value, "biometric");
|
|
2336
|
+
} catch (rollbackError) {
|
|
2337
|
+
rollbackErrors.push({
|
|
2338
|
+
label: "rollback biometric",
|
|
2339
|
+
error: rollbackError
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
});
|
|
1643
2343
|
}
|
|
1644
2344
|
if (rollbackEntries.length > 0) {
|
|
1645
|
-
emitBatchChange(scope, "rollback", rollbackSource, rollbackEntries.map((
|
|
2345
|
+
emitBatchChange(scope, "rollback", rollbackSource, rollbackEntries.map((entry, index) => (0, _shared.createKeyChange)(scope, entry.key, preRollbackValues[index], readRollbackEventValue(entry), "rollback", rollbackSource)));
|
|
2346
|
+
}
|
|
2347
|
+
if (rollbackErrors.length > 0) {
|
|
2348
|
+
throw (0, _shared.createStorageCompositeError)("transaction rollback", error, rollbackErrors);
|
|
1646
2349
|
}
|
|
1647
2350
|
throw error;
|
|
1648
2351
|
}
|
|
@@ -1750,8 +2453,16 @@ function createStorageCore(buildAdapter) {
|
|
|
1750
2453
|
add(id);
|
|
1751
2454
|
return true;
|
|
1752
2455
|
};
|
|
2456
|
+
const getTyped = () => {
|
|
2457
|
+
const typed = {};
|
|
2458
|
+
for (const id of Object.keys(item.get())) {
|
|
2459
|
+
typed[id] = true;
|
|
2460
|
+
}
|
|
2461
|
+
return typed;
|
|
2462
|
+
};
|
|
1753
2463
|
return {
|
|
1754
2464
|
get: item.get,
|
|
2465
|
+
getTyped,
|
|
1755
2466
|
has,
|
|
1756
2467
|
add,
|
|
1757
2468
|
delete: deleteId,
|