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