react-native-nitro-storage 0.3.2 → 0.4.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/README.md +192 -30
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +22 -2
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +3 -0
- package/android/src/main/cpp/cpp-adapter.cpp +3 -1
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +54 -5
- package/cpp/bindings/HybridStorage.cpp +167 -22
- package/cpp/bindings/HybridStorage.hpp +12 -1
- package/cpp/core/NativeStorageAdapter.hpp +3 -0
- package/ios/IOSStorageAdapterCpp.hpp +16 -0
- package/ios/IOSStorageAdapterCpp.mm +135 -11
- package/lib/commonjs/index.js +522 -275
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +614 -270
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/indexeddb-backend.js +130 -0
- package/lib/commonjs/indexeddb-backend.js.map +1 -0
- package/lib/commonjs/internal.js +25 -0
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/module/index.js +516 -277
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +608 -272
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/indexeddb-backend.js +126 -0
- package/lib/module/indexeddb-backend.js.map +1 -0
- package/lib/module/internal.js +24 -0
- package/lib/module/internal.js.map +1 -1
- package/lib/typescript/Storage.nitro.d.ts +2 -0
- package/lib/typescript/Storage.nitro.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +40 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +42 -1
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/indexeddb-backend.d.ts +29 -0
- package/lib/typescript/indexeddb-backend.d.ts.map +1 -0
- package/lib/typescript/internal.d.ts +1 -0
- package/lib/typescript/internal.d.ts.map +1 -1
- package/nitrogen/generated/android/NitroStorageOnLoad.cpp +22 -17
- package/nitrogen/generated/android/NitroStorageOnLoad.hpp +13 -4
- package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +2 -0
- package/package.json +7 -3
- package/src/Storage.nitro.ts +2 -0
- package/src/index.ts +671 -296
- package/src/index.web.ts +776 -288
- package/src/indexeddb-backend.ts +143 -0
- package/src/internal.ts +28 -0
package/lib/module/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { NitroModules } from "react-native-nitro-modules";
|
|
4
|
-
import { StorageScope, AccessControl } from "./Storage.types";
|
|
5
|
-
import { MIGRATION_VERSION_KEY, isStoredEnvelope, assertBatchScope, assertValidScope, decodeNativeBatchValue, serializeWithPrimitiveFastPath, deserializeWithPrimitiveFastPath, prefixKey, isNamespaced } from "./internal";
|
|
4
|
+
import { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
|
|
5
|
+
import { MIGRATION_VERSION_KEY, isStoredEnvelope, assertBatchScope, assertValidScope, decodeNativeBatchValue, serializeWithPrimitiveFastPath, deserializeWithPrimitiveFastPath, toVersionToken, prefixKey, isNamespaced } from "./internal";
|
|
6
6
|
export { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
|
|
7
7
|
export { migrateFromMMKV } from "./migration";
|
|
8
8
|
function asInternal(item) {
|
|
@@ -33,6 +33,36 @@ const scopedRawCache = new Map([[StorageScope.Disk, new Map()], [StorageScope.Se
|
|
|
33
33
|
const pendingSecureWrites = new Map();
|
|
34
34
|
let secureFlushScheduled = false;
|
|
35
35
|
let secureDefaultAccessControl = AccessControl.WhenUnlocked;
|
|
36
|
+
let metricsObserver;
|
|
37
|
+
const metricsCounters = new Map();
|
|
38
|
+
function recordMetric(operation, scope, durationMs, keysCount = 1) {
|
|
39
|
+
const existing = metricsCounters.get(operation);
|
|
40
|
+
if (!existing) {
|
|
41
|
+
metricsCounters.set(operation, {
|
|
42
|
+
count: 1,
|
|
43
|
+
totalDurationMs: durationMs,
|
|
44
|
+
maxDurationMs: durationMs
|
|
45
|
+
});
|
|
46
|
+
} else {
|
|
47
|
+
existing.count += 1;
|
|
48
|
+
existing.totalDurationMs += durationMs;
|
|
49
|
+
existing.maxDurationMs = Math.max(existing.maxDurationMs, durationMs);
|
|
50
|
+
}
|
|
51
|
+
metricsObserver?.({
|
|
52
|
+
operation,
|
|
53
|
+
scope,
|
|
54
|
+
durationMs,
|
|
55
|
+
keysCount
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function measureOperation(operation, scope, fn, keysCount = 1) {
|
|
59
|
+
const start = Date.now();
|
|
60
|
+
try {
|
|
61
|
+
return fn();
|
|
62
|
+
} finally {
|
|
63
|
+
recordMetric(operation, scope, Date.now() - start, keysCount);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
36
66
|
function getScopedListeners(scope) {
|
|
37
67
|
return scopedListeners.get(scope);
|
|
38
68
|
}
|
|
@@ -93,34 +123,47 @@ function flushSecureWrites() {
|
|
|
93
123
|
}
|
|
94
124
|
const writes = Array.from(pendingSecureWrites.values());
|
|
95
125
|
pendingSecureWrites.clear();
|
|
96
|
-
const
|
|
97
|
-
const valuesToSet = [];
|
|
126
|
+
const groupedSetWrites = new Map();
|
|
98
127
|
const keysToRemove = [];
|
|
99
128
|
writes.forEach(({
|
|
100
129
|
key,
|
|
101
|
-
value
|
|
130
|
+
value,
|
|
131
|
+
accessControl
|
|
102
132
|
}) => {
|
|
103
133
|
if (value === undefined) {
|
|
104
134
|
keysToRemove.push(key);
|
|
105
135
|
} else {
|
|
106
|
-
|
|
107
|
-
|
|
136
|
+
const resolvedAccessControl = accessControl ?? secureDefaultAccessControl;
|
|
137
|
+
const existingGroup = groupedSetWrites.get(resolvedAccessControl);
|
|
138
|
+
const group = existingGroup ?? {
|
|
139
|
+
keys: [],
|
|
140
|
+
values: []
|
|
141
|
+
};
|
|
142
|
+
group.keys.push(key);
|
|
143
|
+
group.values.push(value);
|
|
144
|
+
if (!existingGroup) {
|
|
145
|
+
groupedSetWrites.set(resolvedAccessControl, group);
|
|
146
|
+
}
|
|
108
147
|
}
|
|
109
148
|
});
|
|
110
149
|
const storageModule = getStorageModule();
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
storageModule.setBatch(
|
|
114
|
-
}
|
|
150
|
+
groupedSetWrites.forEach((group, accessControl) => {
|
|
151
|
+
storageModule.setSecureAccessControl(accessControl);
|
|
152
|
+
storageModule.setBatch(group.keys, group.values, StorageScope.Secure);
|
|
153
|
+
});
|
|
115
154
|
if (keysToRemove.length > 0) {
|
|
116
155
|
storageModule.removeBatch(keysToRemove, StorageScope.Secure);
|
|
117
156
|
}
|
|
118
157
|
}
|
|
119
|
-
function scheduleSecureWrite(key, value) {
|
|
120
|
-
|
|
158
|
+
function scheduleSecureWrite(key, value, accessControl) {
|
|
159
|
+
const pendingWrite = {
|
|
121
160
|
key,
|
|
122
161
|
value
|
|
123
|
-
}
|
|
162
|
+
};
|
|
163
|
+
if (accessControl !== undefined) {
|
|
164
|
+
pendingWrite.accessControl = accessControl;
|
|
165
|
+
}
|
|
166
|
+
pendingSecureWrites.set(key, pendingWrite);
|
|
124
167
|
if (secureFlushScheduled) {
|
|
125
168
|
return;
|
|
126
169
|
}
|
|
@@ -214,97 +257,201 @@ function writeMigrationVersion(scope, version) {
|
|
|
214
257
|
}
|
|
215
258
|
export const storage = {
|
|
216
259
|
clear: scope => {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
260
|
+
measureOperation("storage:clear", scope, () => {
|
|
261
|
+
if (scope === StorageScope.Memory) {
|
|
262
|
+
memoryStore.clear();
|
|
263
|
+
notifyAllListeners(memoryListeners);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (scope === StorageScope.Secure) {
|
|
267
|
+
flushSecureWrites();
|
|
268
|
+
pendingSecureWrites.clear();
|
|
269
|
+
}
|
|
270
|
+
clearScopeRawCache(scope);
|
|
271
|
+
getStorageModule().clear(scope);
|
|
272
|
+
});
|
|
228
273
|
},
|
|
229
274
|
clearAll: () => {
|
|
230
|
-
storage
|
|
231
|
-
|
|
232
|
-
|
|
275
|
+
measureOperation("storage:clearAll", StorageScope.Memory, () => {
|
|
276
|
+
storage.clear(StorageScope.Memory);
|
|
277
|
+
storage.clear(StorageScope.Disk);
|
|
278
|
+
storage.clear(StorageScope.Secure);
|
|
279
|
+
}, 3);
|
|
233
280
|
},
|
|
234
281
|
clearNamespace: (namespace, scope) => {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
282
|
+
measureOperation("storage:clearNamespace", scope, () => {
|
|
283
|
+
assertValidScope(scope);
|
|
284
|
+
if (scope === StorageScope.Memory) {
|
|
285
|
+
for (const key of memoryStore.keys()) {
|
|
286
|
+
if (isNamespaced(key, namespace)) {
|
|
287
|
+
memoryStore.delete(key);
|
|
288
|
+
}
|
|
240
289
|
}
|
|
290
|
+
notifyAllListeners(memoryListeners);
|
|
291
|
+
return;
|
|
241
292
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
clearScopeRawCache(scope);
|
|
250
|
-
getStorageModule().removeByPrefix(keyPrefix, scope);
|
|
293
|
+
const keyPrefix = prefixKey(namespace, "");
|
|
294
|
+
if (scope === StorageScope.Secure) {
|
|
295
|
+
flushSecureWrites();
|
|
296
|
+
}
|
|
297
|
+
clearScopeRawCache(scope);
|
|
298
|
+
getStorageModule().removeByPrefix(keyPrefix, scope);
|
|
299
|
+
});
|
|
251
300
|
},
|
|
252
301
|
clearBiometric: () => {
|
|
253
|
-
|
|
302
|
+
measureOperation("storage:clearBiometric", StorageScope.Secure, () => {
|
|
303
|
+
getStorageModule().clearSecureBiometric();
|
|
304
|
+
});
|
|
254
305
|
},
|
|
255
306
|
has: (key, scope) => {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
307
|
+
return measureOperation("storage:has", scope, () => {
|
|
308
|
+
assertValidScope(scope);
|
|
309
|
+
if (scope === StorageScope.Memory) {
|
|
310
|
+
return memoryStore.has(key);
|
|
311
|
+
}
|
|
312
|
+
return getStorageModule().has(key, scope);
|
|
313
|
+
});
|
|
261
314
|
},
|
|
262
315
|
getAllKeys: scope => {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
316
|
+
return measureOperation("storage:getAllKeys", scope, () => {
|
|
317
|
+
assertValidScope(scope);
|
|
318
|
+
if (scope === StorageScope.Memory) {
|
|
319
|
+
return Array.from(memoryStore.keys());
|
|
320
|
+
}
|
|
321
|
+
return getStorageModule().getAllKeys(scope);
|
|
322
|
+
});
|
|
323
|
+
},
|
|
324
|
+
getKeysByPrefix: (prefix, scope) => {
|
|
325
|
+
return measureOperation("storage:getKeysByPrefix", scope, () => {
|
|
326
|
+
assertValidScope(scope);
|
|
327
|
+
if (scope === StorageScope.Memory) {
|
|
328
|
+
return Array.from(memoryStore.keys()).filter(key => key.startsWith(prefix));
|
|
329
|
+
}
|
|
330
|
+
return getStorageModule().getKeysByPrefix(prefix, scope);
|
|
331
|
+
});
|
|
332
|
+
},
|
|
333
|
+
getByPrefix: (prefix, scope) => {
|
|
334
|
+
return measureOperation("storage:getByPrefix", scope, () => {
|
|
335
|
+
const result = {};
|
|
336
|
+
const keys = storage.getKeysByPrefix(prefix, scope);
|
|
337
|
+
if (keys.length === 0) {
|
|
338
|
+
return result;
|
|
339
|
+
}
|
|
340
|
+
if (scope === StorageScope.Memory) {
|
|
341
|
+
keys.forEach(key => {
|
|
342
|
+
const value = memoryStore.get(key);
|
|
343
|
+
if (typeof value === "string") {
|
|
344
|
+
result[key] = value;
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
const values = getStorageModule().getBatch(keys, scope);
|
|
350
|
+
keys.forEach((key, idx) => {
|
|
351
|
+
const value = decodeNativeBatchValue(values[idx]);
|
|
352
|
+
if (value !== undefined) {
|
|
353
|
+
result[key] = value;
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
return result;
|
|
357
|
+
});
|
|
268
358
|
},
|
|
269
359
|
getAll: scope => {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
360
|
+
return measureOperation("storage:getAll", scope, () => {
|
|
361
|
+
assertValidScope(scope);
|
|
362
|
+
const result = {};
|
|
363
|
+
if (scope === StorageScope.Memory) {
|
|
364
|
+
memoryStore.forEach((value, key) => {
|
|
365
|
+
if (typeof value === "string") result[key] = value;
|
|
366
|
+
});
|
|
367
|
+
return result;
|
|
368
|
+
}
|
|
369
|
+
const keys = getStorageModule().getAllKeys(scope);
|
|
370
|
+
if (keys.length === 0) return result;
|
|
371
|
+
const values = getStorageModule().getBatch(keys, scope);
|
|
372
|
+
keys.forEach((key, idx) => {
|
|
373
|
+
const val = decodeNativeBatchValue(values[idx]);
|
|
374
|
+
if (val !== undefined) result[key] = val;
|
|
275
375
|
});
|
|
276
376
|
return result;
|
|
277
|
-
}
|
|
278
|
-
const keys = getStorageModule().getAllKeys(scope);
|
|
279
|
-
if (keys.length === 0) return result;
|
|
280
|
-
const values = getStorageModule().getBatch(keys, scope);
|
|
281
|
-
keys.forEach((key, idx) => {
|
|
282
|
-
const val = decodeNativeBatchValue(values[idx]);
|
|
283
|
-
if (val !== undefined) result[key] = val;
|
|
284
377
|
});
|
|
285
|
-
return result;
|
|
286
378
|
},
|
|
287
379
|
size: scope => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
380
|
+
return measureOperation("storage:size", scope, () => {
|
|
381
|
+
assertValidScope(scope);
|
|
382
|
+
if (scope === StorageScope.Memory) {
|
|
383
|
+
return memoryStore.size;
|
|
384
|
+
}
|
|
385
|
+
return getStorageModule().size(scope);
|
|
386
|
+
});
|
|
293
387
|
},
|
|
294
388
|
setAccessControl: level => {
|
|
295
|
-
|
|
296
|
-
|
|
389
|
+
measureOperation("storage:setAccessControl", StorageScope.Secure, () => {
|
|
390
|
+
secureDefaultAccessControl = level;
|
|
391
|
+
getStorageModule().setSecureAccessControl(level);
|
|
392
|
+
});
|
|
297
393
|
},
|
|
298
394
|
setSecureWritesAsync: enabled => {
|
|
299
|
-
|
|
395
|
+
measureOperation("storage:setSecureWritesAsync", StorageScope.Secure, () => {
|
|
396
|
+
getStorageModule().setSecureWritesAsync(enabled);
|
|
397
|
+
});
|
|
300
398
|
},
|
|
301
399
|
flushSecureWrites: () => {
|
|
302
|
-
flushSecureWrites()
|
|
400
|
+
measureOperation("storage:flushSecureWrites", StorageScope.Secure, () => {
|
|
401
|
+
flushSecureWrites();
|
|
402
|
+
});
|
|
303
403
|
},
|
|
304
404
|
setKeychainAccessGroup: group => {
|
|
305
|
-
|
|
405
|
+
measureOperation("storage:setKeychainAccessGroup", StorageScope.Secure, () => {
|
|
406
|
+
getStorageModule().setKeychainAccessGroup(group);
|
|
407
|
+
});
|
|
408
|
+
},
|
|
409
|
+
setMetricsObserver: observer => {
|
|
410
|
+
metricsObserver = observer;
|
|
411
|
+
},
|
|
412
|
+
getMetricsSnapshot: () => {
|
|
413
|
+
const snapshot = {};
|
|
414
|
+
metricsCounters.forEach((value, key) => {
|
|
415
|
+
snapshot[key] = {
|
|
416
|
+
count: value.count,
|
|
417
|
+
totalDurationMs: value.totalDurationMs,
|
|
418
|
+
avgDurationMs: value.count === 0 ? 0 : value.totalDurationMs / value.count,
|
|
419
|
+
maxDurationMs: value.maxDurationMs
|
|
420
|
+
};
|
|
421
|
+
});
|
|
422
|
+
return snapshot;
|
|
423
|
+
},
|
|
424
|
+
resetMetrics: () => {
|
|
425
|
+
metricsCounters.clear();
|
|
426
|
+
},
|
|
427
|
+
import: (data, scope) => {
|
|
428
|
+
measureOperation("storage:import", scope, () => {
|
|
429
|
+
assertValidScope(scope);
|
|
430
|
+
const keys = Object.keys(data);
|
|
431
|
+
if (keys.length === 0) return;
|
|
432
|
+
const values = keys.map(k => data[k]);
|
|
433
|
+
if (scope === StorageScope.Memory) {
|
|
434
|
+
keys.forEach((key, index) => {
|
|
435
|
+
memoryStore.set(key, values[index]);
|
|
436
|
+
});
|
|
437
|
+
keys.forEach(key => notifyKeyListeners(memoryListeners, key));
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (scope === StorageScope.Secure) {
|
|
441
|
+
flushSecureWrites();
|
|
442
|
+
getStorageModule().setSecureAccessControl(secureDefaultAccessControl);
|
|
443
|
+
}
|
|
444
|
+
getStorageModule().setBatch(keys, values, scope);
|
|
445
|
+
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
446
|
+
}, Object.keys(data).length);
|
|
306
447
|
}
|
|
307
448
|
};
|
|
449
|
+
export function setWebSecureStorageBackend(_backend) {
|
|
450
|
+
// Native platforms do not use web secure backends.
|
|
451
|
+
}
|
|
452
|
+
export function getWebSecureStorageBackend() {
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
308
455
|
function canUseRawBatchPath(item) {
|
|
309
456
|
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
|
|
310
457
|
}
|
|
@@ -322,7 +469,8 @@ export function createStorageItem(config) {
|
|
|
322
469
|
const serialize = config.serialize ?? defaultSerialize;
|
|
323
470
|
const deserialize = config.deserialize ?? defaultDeserialize;
|
|
324
471
|
const isMemory = config.scope === StorageScope.Memory;
|
|
325
|
-
const
|
|
472
|
+
const resolvedBiometricLevel = config.scope === StorageScope.Secure ? config.biometricLevel ?? (config.biometric === true ? BiometricLevel.BiometryOnly : BiometricLevel.None) : BiometricLevel.None;
|
|
473
|
+
const isBiometric = resolvedBiometricLevel !== BiometricLevel.None;
|
|
326
474
|
const secureAccessControl = config.accessControl;
|
|
327
475
|
const validate = config.validate;
|
|
328
476
|
const onValidationError = config.onValidationError;
|
|
@@ -331,7 +479,7 @@ export function createStorageItem(config) {
|
|
|
331
479
|
const expirationTtlMs = expiration?.ttlMs;
|
|
332
480
|
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
333
481
|
const readCache = !isMemory && config.readCache === true;
|
|
334
|
-
const coalesceSecureWrites = config.scope === StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric
|
|
482
|
+
const coalesceSecureWrites = config.scope === StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
|
|
335
483
|
const defaultValue = config.defaultValue;
|
|
336
484
|
const nonMemoryScope = config.scope === StorageScope.Disk ? StorageScope.Disk : config.scope === StorageScope.Secure ? StorageScope.Secure : null;
|
|
337
485
|
if (expiration && expiration.ttlMs <= 0) {
|
|
@@ -395,12 +543,12 @@ export function createStorageItem(config) {
|
|
|
395
543
|
};
|
|
396
544
|
const writeStoredRaw = rawValue => {
|
|
397
545
|
if (isBiometric) {
|
|
398
|
-
getStorageModule().
|
|
546
|
+
getStorageModule().setSecureBiometricWithLevel(storageKey, rawValue, resolvedBiometricLevel);
|
|
399
547
|
return;
|
|
400
548
|
}
|
|
401
549
|
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
402
550
|
if (coalesceSecureWrites) {
|
|
403
|
-
scheduleSecureWrite(storageKey, rawValue);
|
|
551
|
+
scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
|
|
404
552
|
return;
|
|
405
553
|
}
|
|
406
554
|
if (nonMemoryScope === StorageScope.Secure) {
|
|
@@ -416,7 +564,7 @@ export function createStorageItem(config) {
|
|
|
416
564
|
}
|
|
417
565
|
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
418
566
|
if (coalesceSecureWrites) {
|
|
419
|
-
scheduleSecureWrite(storageKey, undefined);
|
|
567
|
+
scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
|
|
420
568
|
return;
|
|
421
569
|
}
|
|
422
570
|
if (nonMemoryScope === StorageScope.Secure) {
|
|
@@ -464,7 +612,7 @@ export function createStorageItem(config) {
|
|
|
464
612
|
}
|
|
465
613
|
return resolved;
|
|
466
614
|
};
|
|
467
|
-
const
|
|
615
|
+
const getInternal = () => {
|
|
468
616
|
const raw = readStoredRaw();
|
|
469
617
|
if (!memoryExpiration && raw === lastRaw && hasLastValue) {
|
|
470
618
|
if (!expiration || lastExpiresAt === null) {
|
|
@@ -479,6 +627,7 @@ export function createStorageItem(config) {
|
|
|
479
627
|
onExpired?.(storageKey);
|
|
480
628
|
lastValue = ensureValidatedValue(defaultValue, false);
|
|
481
629
|
hasLastValue = true;
|
|
630
|
+
listeners.forEach(cb => cb());
|
|
482
631
|
return lastValue;
|
|
483
632
|
}
|
|
484
633
|
}
|
|
@@ -514,6 +663,7 @@ export function createStorageItem(config) {
|
|
|
514
663
|
onExpired?.(storageKey);
|
|
515
664
|
lastValue = ensureValidatedValue(defaultValue, false);
|
|
516
665
|
hasLastValue = true;
|
|
666
|
+
listeners.forEach(cb => cb());
|
|
517
667
|
return lastValue;
|
|
518
668
|
}
|
|
519
669
|
deserializableRaw = parsed.payload;
|
|
@@ -529,31 +679,52 @@ export function createStorageItem(config) {
|
|
|
529
679
|
hasLastValue = true;
|
|
530
680
|
return lastValue;
|
|
531
681
|
};
|
|
682
|
+
const getCurrentVersion = () => {
|
|
683
|
+
const raw = readStoredRaw();
|
|
684
|
+
return toVersionToken(raw);
|
|
685
|
+
};
|
|
686
|
+
const get = () => measureOperation("item:get", config.scope, () => getInternal());
|
|
687
|
+
const getWithVersion = () => measureOperation("item:getWithVersion", config.scope, () => ({
|
|
688
|
+
value: getInternal(),
|
|
689
|
+
version: getCurrentVersion()
|
|
690
|
+
}));
|
|
532
691
|
const set = valueOrFn => {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
692
|
+
measureOperation("item:set", config.scope, () => {
|
|
693
|
+
const newValue = isUpdater(valueOrFn) ? valueOrFn(getInternal()) : valueOrFn;
|
|
694
|
+
invalidateParsedCache();
|
|
695
|
+
if (validate && !validate(newValue)) {
|
|
696
|
+
throw new Error(`Validation failed for key "${storageKey}" in scope "${StorageScope[config.scope]}".`);
|
|
697
|
+
}
|
|
698
|
+
writeValueWithoutValidation(newValue);
|
|
699
|
+
});
|
|
539
700
|
};
|
|
701
|
+
const setIfVersion = (version, valueOrFn) => measureOperation("item:setIfVersion", config.scope, () => {
|
|
702
|
+
const currentVersion = getCurrentVersion();
|
|
703
|
+
if (currentVersion !== version) {
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
set(valueOrFn);
|
|
707
|
+
return true;
|
|
708
|
+
});
|
|
540
709
|
const deleteItem = () => {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if (
|
|
544
|
-
memoryExpiration
|
|
710
|
+
measureOperation("item:delete", config.scope, () => {
|
|
711
|
+
invalidateParsedCache();
|
|
712
|
+
if (isMemory) {
|
|
713
|
+
if (memoryExpiration) {
|
|
714
|
+
memoryExpiration.delete(storageKey);
|
|
715
|
+
}
|
|
716
|
+
memoryStore.delete(storageKey);
|
|
717
|
+
notifyKeyListeners(memoryListeners, storageKey);
|
|
718
|
+
return;
|
|
545
719
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
removeStoredRaw();
|
|
720
|
+
removeStoredRaw();
|
|
721
|
+
});
|
|
551
722
|
};
|
|
552
|
-
const hasItem = () => {
|
|
723
|
+
const hasItem = () => measureOperation("item:has", config.scope, () => {
|
|
553
724
|
if (isMemory) return memoryStore.has(storageKey);
|
|
554
725
|
if (isBiometric) return getStorageModule().hasSecureBiometric(storageKey);
|
|
555
726
|
return getStorageModule().has(storageKey, config.scope);
|
|
556
|
-
};
|
|
727
|
+
});
|
|
557
728
|
const subscribe = callback => {
|
|
558
729
|
ensureSubscription();
|
|
559
730
|
listeners.add(callback);
|
|
@@ -570,7 +741,9 @@ export function createStorageItem(config) {
|
|
|
570
741
|
};
|
|
571
742
|
const storageItem = {
|
|
572
743
|
get,
|
|
744
|
+
getWithVersion,
|
|
573
745
|
set,
|
|
746
|
+
setIfVersion,
|
|
574
747
|
delete: deleteItem,
|
|
575
748
|
has: hasItem,
|
|
576
749
|
subscribe,
|
|
@@ -580,10 +753,14 @@ export function createStorageItem(config) {
|
|
|
580
753
|
invalidateParsedCache();
|
|
581
754
|
listeners.forEach(listener => listener());
|
|
582
755
|
},
|
|
756
|
+
_invalidateParsedCacheOnly: () => {
|
|
757
|
+
invalidateParsedCache();
|
|
758
|
+
},
|
|
583
759
|
_hasValidation: validate !== undefined,
|
|
584
760
|
_hasExpiration: expiration !== undefined,
|
|
585
761
|
_readCacheEnabled: readCache,
|
|
586
762
|
_isBiometric: isBiometric,
|
|
763
|
+
_defaultValue: defaultValue,
|
|
587
764
|
...(secureAccessControl !== undefined ? {
|
|
588
765
|
_secureAccessControl: secureAccessControl
|
|
589
766
|
} : {}),
|
|
@@ -593,137 +770,166 @@ export function createStorageItem(config) {
|
|
|
593
770
|
return storageItem;
|
|
594
771
|
}
|
|
595
772
|
export { useStorage, useStorageSelector, useSetStorage } from "./storage-hooks";
|
|
773
|
+
export { createIndexedDBBackend } from "./indexeddb-backend";
|
|
596
774
|
export function getBatch(items, scope) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
775
|
+
return measureOperation("batch:get", scope, () => {
|
|
776
|
+
assertBatchScope(items, scope);
|
|
777
|
+
if (scope === StorageScope.Memory) {
|
|
778
|
+
return items.map(item => item.get());
|
|
779
|
+
}
|
|
780
|
+
const useRawBatchPath = items.every(item => scope === StorageScope.Secure ? canUseSecureRawBatchPath(item) : canUseRawBatchPath(item));
|
|
781
|
+
if (!useRawBatchPath) {
|
|
782
|
+
return items.map(item => item.get());
|
|
783
|
+
}
|
|
784
|
+
const rawValues = new Array(items.length);
|
|
785
|
+
const keysToFetch = [];
|
|
786
|
+
const keyIndexes = [];
|
|
787
|
+
items.forEach((item, index) => {
|
|
788
|
+
if (scope === StorageScope.Secure) {
|
|
789
|
+
if (hasPendingSecureWrite(item.key)) {
|
|
790
|
+
rawValues[index] = readPendingSecureWrite(item.key);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
614
793
|
}
|
|
794
|
+
if (item._readCacheEnabled === true) {
|
|
795
|
+
if (hasCachedRawValue(scope, item.key)) {
|
|
796
|
+
rawValues[index] = readCachedRawValue(scope, item.key);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
keysToFetch.push(item.key);
|
|
801
|
+
keyIndexes.push(index);
|
|
802
|
+
});
|
|
803
|
+
if (keysToFetch.length > 0) {
|
|
804
|
+
const fetchedValues = getStorageModule().getBatch(keysToFetch, scope).map(value => decodeNativeBatchValue(value));
|
|
805
|
+
fetchedValues.forEach((value, index) => {
|
|
806
|
+
const key = keysToFetch[index];
|
|
807
|
+
const targetIndex = keyIndexes[index];
|
|
808
|
+
if (key === undefined || targetIndex === undefined) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
rawValues[targetIndex] = value;
|
|
812
|
+
cacheRawValue(scope, key, value);
|
|
813
|
+
});
|
|
615
814
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
815
|
+
return items.map((item, index) => {
|
|
816
|
+
const raw = rawValues[index];
|
|
817
|
+
if (raw === undefined) {
|
|
818
|
+
return asInternal(item)._defaultValue;
|
|
819
|
+
}
|
|
820
|
+
return item.deserialize(raw);
|
|
821
|
+
});
|
|
822
|
+
}, items.length);
|
|
823
|
+
}
|
|
824
|
+
export function setBatch(items, scope) {
|
|
825
|
+
measureOperation("batch:set", scope, () => {
|
|
826
|
+
assertBatchScope(items.map(batchEntry => batchEntry.item), scope);
|
|
827
|
+
if (scope === StorageScope.Memory) {
|
|
828
|
+
// Determine if any item needs per-item handling (validation or TTL)
|
|
829
|
+
const needsIndividualSets = items.some(({
|
|
830
|
+
item
|
|
831
|
+
}) => {
|
|
832
|
+
const internal = asInternal(item);
|
|
833
|
+
return internal._hasValidation || internal._hasExpiration;
|
|
834
|
+
});
|
|
835
|
+
if (needsIndividualSets) {
|
|
836
|
+
// Fall back to individual sets to preserve validation and TTL semantics
|
|
837
|
+
items.forEach(({
|
|
838
|
+
item,
|
|
839
|
+
value
|
|
840
|
+
}) => item.set(value));
|
|
619
841
|
return;
|
|
620
842
|
}
|
|
843
|
+
|
|
844
|
+
// Atomic write: update all values in memoryStore, invalidate caches, then batch-notify
|
|
845
|
+
items.forEach(({
|
|
846
|
+
item,
|
|
847
|
+
value
|
|
848
|
+
}) => {
|
|
849
|
+
memoryStore.set(item.key, value);
|
|
850
|
+
asInternal(item)._invalidateParsedCacheOnly();
|
|
851
|
+
});
|
|
852
|
+
items.forEach(({
|
|
853
|
+
item
|
|
854
|
+
}) => notifyKeyListeners(memoryListeners, item.key));
|
|
855
|
+
return;
|
|
621
856
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
857
|
+
if (scope === StorageScope.Secure) {
|
|
858
|
+
const secureEntries = items.map(({
|
|
859
|
+
item,
|
|
860
|
+
value
|
|
861
|
+
}) => ({
|
|
862
|
+
item,
|
|
863
|
+
value,
|
|
864
|
+
internal: asInternal(item)
|
|
865
|
+
}));
|
|
866
|
+
const canUseSecureBatchPath = secureEntries.every(({
|
|
867
|
+
internal
|
|
868
|
+
}) => canUseSecureRawBatchPath(internal));
|
|
869
|
+
if (!canUseSecureBatchPath) {
|
|
870
|
+
items.forEach(({
|
|
871
|
+
item,
|
|
872
|
+
value
|
|
873
|
+
}) => item.set(value));
|
|
631
874
|
return;
|
|
632
875
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
876
|
+
flushSecureWrites();
|
|
877
|
+
const storageModule = getStorageModule();
|
|
878
|
+
const groupedByAccessControl = new Map();
|
|
879
|
+
secureEntries.forEach(({
|
|
880
|
+
item,
|
|
881
|
+
value,
|
|
882
|
+
internal
|
|
883
|
+
}) => {
|
|
884
|
+
const accessControl = internal._secureAccessControl ?? secureDefaultAccessControl;
|
|
885
|
+
const existingGroup = groupedByAccessControl.get(accessControl);
|
|
886
|
+
const group = existingGroup ?? {
|
|
887
|
+
keys: [],
|
|
888
|
+
values: []
|
|
889
|
+
};
|
|
890
|
+
group.keys.push(item.key);
|
|
891
|
+
group.values.push(item.serialize(value));
|
|
892
|
+
if (!existingGroup) {
|
|
893
|
+
groupedByAccessControl.set(accessControl, group);
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
groupedByAccessControl.forEach((group, accessControl) => {
|
|
897
|
+
storageModule.setSecureAccessControl(accessControl);
|
|
898
|
+
storageModule.setBatch(group.keys, group.values, scope);
|
|
899
|
+
group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
|
|
900
|
+
});
|
|
901
|
+
return;
|
|
641
902
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
assertBatchScope(items.map(batchEntry => batchEntry.item), scope);
|
|
647
|
-
if (scope === StorageScope.Memory) {
|
|
648
|
-
items.forEach(({
|
|
649
|
-
item,
|
|
650
|
-
value
|
|
651
|
-
}) => item.set(value));
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
if (scope === StorageScope.Secure) {
|
|
655
|
-
const secureEntries = items.map(({
|
|
656
|
-
item,
|
|
657
|
-
value
|
|
658
|
-
}) => ({
|
|
659
|
-
item,
|
|
660
|
-
value,
|
|
661
|
-
internal: asInternal(item)
|
|
662
|
-
}));
|
|
663
|
-
const canUseSecureBatchPath = secureEntries.every(({
|
|
664
|
-
internal
|
|
665
|
-
}) => canUseSecureRawBatchPath(internal));
|
|
666
|
-
if (!canUseSecureBatchPath) {
|
|
903
|
+
const useRawBatchPath = items.every(({
|
|
904
|
+
item
|
|
905
|
+
}) => canUseRawBatchPath(asInternal(item)));
|
|
906
|
+
if (!useRawBatchPath) {
|
|
667
907
|
items.forEach(({
|
|
668
908
|
item,
|
|
669
909
|
value
|
|
670
910
|
}) => item.set(value));
|
|
671
911
|
return;
|
|
672
912
|
}
|
|
673
|
-
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
value,
|
|
679
|
-
internal
|
|
680
|
-
}) => {
|
|
681
|
-
const accessControl = internal._secureAccessControl ?? secureDefaultAccessControl;
|
|
682
|
-
const existingGroup = groupedByAccessControl.get(accessControl);
|
|
683
|
-
const group = existingGroup ?? {
|
|
684
|
-
keys: [],
|
|
685
|
-
values: []
|
|
686
|
-
};
|
|
687
|
-
group.keys.push(item.key);
|
|
688
|
-
group.values.push(item.serialize(value));
|
|
689
|
-
if (!existingGroup) {
|
|
690
|
-
groupedByAccessControl.set(accessControl, group);
|
|
691
|
-
}
|
|
692
|
-
});
|
|
693
|
-
groupedByAccessControl.forEach((group, accessControl) => {
|
|
694
|
-
storageModule.setSecureAccessControl(accessControl);
|
|
695
|
-
storageModule.setBatch(group.keys, group.values, scope);
|
|
696
|
-
group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
|
|
697
|
-
});
|
|
698
|
-
return;
|
|
699
|
-
}
|
|
700
|
-
const useRawBatchPath = items.every(({
|
|
701
|
-
item
|
|
702
|
-
}) => canUseRawBatchPath(asInternal(item)));
|
|
703
|
-
if (!useRawBatchPath) {
|
|
704
|
-
items.forEach(({
|
|
705
|
-
item,
|
|
706
|
-
value
|
|
707
|
-
}) => item.set(value));
|
|
708
|
-
return;
|
|
709
|
-
}
|
|
710
|
-
const keys = items.map(entry => entry.item.key);
|
|
711
|
-
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
712
|
-
getStorageModule().setBatch(keys, values, scope);
|
|
713
|
-
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
913
|
+
const keys = items.map(entry => entry.item.key);
|
|
914
|
+
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
915
|
+
getStorageModule().setBatch(keys, values, scope);
|
|
916
|
+
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
917
|
+
}, items.length);
|
|
714
918
|
}
|
|
715
919
|
export function removeBatch(items, scope) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
920
|
+
measureOperation("batch:remove", scope, () => {
|
|
921
|
+
assertBatchScope(items, scope);
|
|
922
|
+
if (scope === StorageScope.Memory) {
|
|
923
|
+
items.forEach(item => item.delete());
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
const keys = items.map(item => item.key);
|
|
927
|
+
if (scope === StorageScope.Secure) {
|
|
928
|
+
flushSecureWrites();
|
|
929
|
+
}
|
|
930
|
+
getStorageModule().removeBatch(keys, scope);
|
|
931
|
+
keys.forEach(key => cacheRawValue(scope, key, undefined));
|
|
932
|
+
}, items.length);
|
|
727
933
|
}
|
|
728
934
|
export function registerMigration(version, migration) {
|
|
729
935
|
if (!Number.isInteger(version) || version <= 0) {
|
|
@@ -735,77 +941,107 @@ export function registerMigration(version, migration) {
|
|
|
735
941
|
registeredMigrations.set(version, migration);
|
|
736
942
|
}
|
|
737
943
|
export function migrateToLatest(scope = StorageScope.Disk) {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
944
|
+
return measureOperation("migration:run", scope, () => {
|
|
945
|
+
assertValidScope(scope);
|
|
946
|
+
const currentVersion = readMigrationVersion(scope);
|
|
947
|
+
const versions = Array.from(registeredMigrations.keys()).filter(version => version > currentVersion).sort((a, b) => a - b);
|
|
948
|
+
let appliedVersion = currentVersion;
|
|
949
|
+
const context = {
|
|
950
|
+
scope,
|
|
951
|
+
getRaw: key => getRawValue(key, scope),
|
|
952
|
+
setRaw: (key, value) => setRawValue(key, value, scope),
|
|
953
|
+
removeRaw: key => removeRawValue(key, scope)
|
|
954
|
+
};
|
|
955
|
+
versions.forEach(version => {
|
|
956
|
+
const migration = registeredMigrations.get(version);
|
|
957
|
+
if (!migration) {
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
migration(context);
|
|
961
|
+
writeMigrationVersion(scope, version);
|
|
962
|
+
appliedVersion = version;
|
|
963
|
+
});
|
|
964
|
+
return appliedVersion;
|
|
756
965
|
});
|
|
757
|
-
return appliedVersion;
|
|
758
966
|
}
|
|
759
967
|
export function runTransaction(scope, transaction) {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
const rollback = new Map();
|
|
765
|
-
const rememberRollback = key => {
|
|
766
|
-
if (rollback.has(key)) {
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
rollback.set(key, getRawValue(key, scope));
|
|
770
|
-
};
|
|
771
|
-
const tx = {
|
|
772
|
-
scope,
|
|
773
|
-
getRaw: key => getRawValue(key, scope),
|
|
774
|
-
setRaw: (key, value) => {
|
|
775
|
-
rememberRollback(key);
|
|
776
|
-
setRawValue(key, value, scope);
|
|
777
|
-
},
|
|
778
|
-
removeRaw: key => {
|
|
779
|
-
rememberRollback(key);
|
|
780
|
-
removeRawValue(key, scope);
|
|
781
|
-
},
|
|
782
|
-
getItem: item => {
|
|
783
|
-
assertBatchScope([item], scope);
|
|
784
|
-
return item.get();
|
|
785
|
-
},
|
|
786
|
-
setItem: (item, value) => {
|
|
787
|
-
assertBatchScope([item], scope);
|
|
788
|
-
rememberRollback(item.key);
|
|
789
|
-
item.set(value);
|
|
790
|
-
},
|
|
791
|
-
removeItem: item => {
|
|
792
|
-
assertBatchScope([item], scope);
|
|
793
|
-
rememberRollback(item.key);
|
|
794
|
-
item.delete();
|
|
968
|
+
return measureOperation("transaction:run", scope, () => {
|
|
969
|
+
assertValidScope(scope);
|
|
970
|
+
if (scope === StorageScope.Secure) {
|
|
971
|
+
flushSecureWrites();
|
|
795
972
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
973
|
+
const rollback = new Map();
|
|
974
|
+
const rememberRollback = key => {
|
|
975
|
+
if (rollback.has(key)) {
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
rollback.set(key, getRawValue(key, scope));
|
|
979
|
+
};
|
|
980
|
+
const tx = {
|
|
981
|
+
scope,
|
|
982
|
+
getRaw: key => getRawValue(key, scope),
|
|
983
|
+
setRaw: (key, value) => {
|
|
984
|
+
rememberRollback(key);
|
|
985
|
+
setRawValue(key, value, scope);
|
|
986
|
+
},
|
|
987
|
+
removeRaw: key => {
|
|
988
|
+
rememberRollback(key);
|
|
802
989
|
removeRawValue(key, scope);
|
|
990
|
+
},
|
|
991
|
+
getItem: item => {
|
|
992
|
+
assertBatchScope([item], scope);
|
|
993
|
+
return item.get();
|
|
994
|
+
},
|
|
995
|
+
setItem: (item, value) => {
|
|
996
|
+
assertBatchScope([item], scope);
|
|
997
|
+
rememberRollback(item.key);
|
|
998
|
+
item.set(value);
|
|
999
|
+
},
|
|
1000
|
+
removeItem: item => {
|
|
1001
|
+
assertBatchScope([item], scope);
|
|
1002
|
+
rememberRollback(item.key);
|
|
1003
|
+
item.delete();
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
try {
|
|
1007
|
+
return transaction(tx);
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
const rollbackEntries = Array.from(rollback.entries()).reverse();
|
|
1010
|
+
if (scope === StorageScope.Memory) {
|
|
1011
|
+
rollbackEntries.forEach(([key, previousValue]) => {
|
|
1012
|
+
if (previousValue === undefined) {
|
|
1013
|
+
removeRawValue(key, scope);
|
|
1014
|
+
} else {
|
|
1015
|
+
setRawValue(key, previousValue, scope);
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
803
1018
|
} else {
|
|
804
|
-
|
|
1019
|
+
const keysToSet = [];
|
|
1020
|
+
const valuesToSet = [];
|
|
1021
|
+
const keysToRemove = [];
|
|
1022
|
+
rollbackEntries.forEach(([key, previousValue]) => {
|
|
1023
|
+
if (previousValue === undefined) {
|
|
1024
|
+
keysToRemove.push(key);
|
|
1025
|
+
} else {
|
|
1026
|
+
keysToSet.push(key);
|
|
1027
|
+
valuesToSet.push(previousValue);
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
if (scope === StorageScope.Secure) {
|
|
1031
|
+
flushSecureWrites();
|
|
1032
|
+
}
|
|
1033
|
+
if (keysToSet.length > 0) {
|
|
1034
|
+
getStorageModule().setBatch(keysToSet, valuesToSet, scope);
|
|
1035
|
+
keysToSet.forEach((key, index) => cacheRawValue(scope, key, valuesToSet[index]));
|
|
1036
|
+
}
|
|
1037
|
+
if (keysToRemove.length > 0) {
|
|
1038
|
+
getStorageModule().removeBatch(keysToRemove, scope);
|
|
1039
|
+
keysToRemove.forEach(key => cacheRawValue(scope, key, undefined));
|
|
1040
|
+
}
|
|
805
1041
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
}
|
|
1042
|
+
throw error;
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
809
1045
|
}
|
|
810
1046
|
export function createSecureAuthStorage(config, options) {
|
|
811
1047
|
const ns = options?.namespace ?? "auth";
|
|
@@ -823,6 +1059,9 @@ export function createSecureAuthStorage(config, options) {
|
|
|
823
1059
|
...(itemConfig.biometric !== undefined ? {
|
|
824
1060
|
biometric: itemConfig.biometric
|
|
825
1061
|
} : {}),
|
|
1062
|
+
...(itemConfig.biometricLevel !== undefined ? {
|
|
1063
|
+
biometricLevel: itemConfig.biometricLevel
|
|
1064
|
+
} : {}),
|
|
826
1065
|
...(itemConfig.accessControl !== undefined ? {
|
|
827
1066
|
accessControl: itemConfig.accessControl
|
|
828
1067
|
} : {}),
|