react-native-nitro-storage 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +334 -34
- package/android/CMakeLists.txt +2 -0
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +26 -2
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +4 -0
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +90 -18
- package/cpp/bindings/HybridStorage.cpp +214 -23
- package/cpp/bindings/HybridStorage.hpp +31 -3
- package/cpp/core/NativeStorageAdapter.hpp +4 -0
- package/ios/IOSStorageAdapterCpp.hpp +17 -0
- package/ios/IOSStorageAdapterCpp.mm +140 -10
- package/lib/commonjs/index.js +555 -288
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +750 -309
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/internal.js +25 -0
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/commonjs/storage-hooks.js +36 -0
- package/lib/commonjs/storage-hooks.js.map +1 -0
- package/lib/module/index.js +537 -287
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +732 -308
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/internal.js +24 -0
- package/lib/module/internal.js.map +1 -1
- package/lib/module/storage-hooks.js +30 -0
- package/lib/module/storage-hooks.js.map +1 -0
- package/lib/typescript/Storage.nitro.d.ts +4 -0
- package/lib/typescript/Storage.nitro.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +41 -4
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +45 -4
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts +1 -0
- package/lib/typescript/internal.d.ts.map +1 -1
- package/lib/typescript/storage-hooks.d.ts +10 -0
- package/lib/typescript/storage-hooks.d.ts.map +1 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +4 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +4 -0
- package/package.json +5 -3
- package/src/Storage.nitro.ts +4 -0
- package/src/index.ts +704 -324
- package/src/index.web.ts +929 -346
- package/src/internal.ts +28 -0
- package/src/storage-hooks.ts +48 -0
package/lib/commonjs/index.js
CHANGED
|
@@ -24,6 +24,7 @@ Object.defineProperty(exports, "StorageScope", {
|
|
|
24
24
|
exports.createSecureAuthStorage = createSecureAuthStorage;
|
|
25
25
|
exports.createStorageItem = createStorageItem;
|
|
26
26
|
exports.getBatch = getBatch;
|
|
27
|
+
exports.getWebSecureStorageBackend = getWebSecureStorageBackend;
|
|
27
28
|
Object.defineProperty(exports, "migrateFromMMKV", {
|
|
28
29
|
enumerable: true,
|
|
29
30
|
get: function () {
|
|
@@ -35,18 +36,40 @@ exports.registerMigration = registerMigration;
|
|
|
35
36
|
exports.removeBatch = removeBatch;
|
|
36
37
|
exports.runTransaction = runTransaction;
|
|
37
38
|
exports.setBatch = setBatch;
|
|
39
|
+
exports.setWebSecureStorageBackend = setWebSecureStorageBackend;
|
|
38
40
|
exports.storage = void 0;
|
|
39
|
-
exports
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
Object.defineProperty(exports, "useSetStorage", {
|
|
42
|
+
enumerable: true,
|
|
43
|
+
get: function () {
|
|
44
|
+
return _storageHooks.useSetStorage;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
Object.defineProperty(exports, "useStorage", {
|
|
48
|
+
enumerable: true,
|
|
49
|
+
get: function () {
|
|
50
|
+
return _storageHooks.useStorage;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
Object.defineProperty(exports, "useStorageSelector", {
|
|
54
|
+
enumerable: true,
|
|
55
|
+
get: function () {
|
|
56
|
+
return _storageHooks.useStorageSelector;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
43
59
|
var _reactNativeNitroModules = require("react-native-nitro-modules");
|
|
44
60
|
var _Storage = require("./Storage.types");
|
|
45
61
|
var _internal = require("./internal");
|
|
46
62
|
var _migration = require("./migration");
|
|
63
|
+
var _storageHooks = require("./storage-hooks");
|
|
47
64
|
function asInternal(item) {
|
|
48
65
|
return item;
|
|
49
66
|
}
|
|
67
|
+
function isUpdater(valueOrFn) {
|
|
68
|
+
return typeof valueOrFn === "function";
|
|
69
|
+
}
|
|
70
|
+
function typedKeys(record) {
|
|
71
|
+
return Object.keys(record);
|
|
72
|
+
}
|
|
50
73
|
const registeredMigrations = new Map();
|
|
51
74
|
const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
|
|
52
75
|
Promise.resolve().then(task);
|
|
@@ -66,6 +89,36 @@ const scopedRawCache = new Map([[_Storage.StorageScope.Disk, new Map()], [_Stora
|
|
|
66
89
|
const pendingSecureWrites = new Map();
|
|
67
90
|
let secureFlushScheduled = false;
|
|
68
91
|
let secureDefaultAccessControl = _Storage.AccessControl.WhenUnlocked;
|
|
92
|
+
let metricsObserver;
|
|
93
|
+
const metricsCounters = new Map();
|
|
94
|
+
function recordMetric(operation, scope, durationMs, keysCount = 1) {
|
|
95
|
+
const existing = metricsCounters.get(operation);
|
|
96
|
+
if (!existing) {
|
|
97
|
+
metricsCounters.set(operation, {
|
|
98
|
+
count: 1,
|
|
99
|
+
totalDurationMs: durationMs,
|
|
100
|
+
maxDurationMs: durationMs
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
existing.count += 1;
|
|
104
|
+
existing.totalDurationMs += durationMs;
|
|
105
|
+
existing.maxDurationMs = Math.max(existing.maxDurationMs, durationMs);
|
|
106
|
+
}
|
|
107
|
+
metricsObserver?.({
|
|
108
|
+
operation,
|
|
109
|
+
scope,
|
|
110
|
+
durationMs,
|
|
111
|
+
keysCount
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function measureOperation(operation, scope, fn, keysCount = 1) {
|
|
115
|
+
const start = Date.now();
|
|
116
|
+
try {
|
|
117
|
+
return fn();
|
|
118
|
+
} finally {
|
|
119
|
+
recordMetric(operation, scope, Date.now() - start, keysCount);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
69
122
|
function getScopedListeners(scope) {
|
|
70
123
|
return scopedListeners.get(scope);
|
|
71
124
|
}
|
|
@@ -126,34 +179,47 @@ function flushSecureWrites() {
|
|
|
126
179
|
}
|
|
127
180
|
const writes = Array.from(pendingSecureWrites.values());
|
|
128
181
|
pendingSecureWrites.clear();
|
|
129
|
-
const
|
|
130
|
-
const valuesToSet = [];
|
|
182
|
+
const groupedSetWrites = new Map();
|
|
131
183
|
const keysToRemove = [];
|
|
132
184
|
writes.forEach(({
|
|
133
185
|
key,
|
|
134
|
-
value
|
|
186
|
+
value,
|
|
187
|
+
accessControl
|
|
135
188
|
}) => {
|
|
136
189
|
if (value === undefined) {
|
|
137
190
|
keysToRemove.push(key);
|
|
138
191
|
} else {
|
|
139
|
-
|
|
140
|
-
|
|
192
|
+
const resolvedAccessControl = accessControl ?? secureDefaultAccessControl;
|
|
193
|
+
const existingGroup = groupedSetWrites.get(resolvedAccessControl);
|
|
194
|
+
const group = existingGroup ?? {
|
|
195
|
+
keys: [],
|
|
196
|
+
values: []
|
|
197
|
+
};
|
|
198
|
+
group.keys.push(key);
|
|
199
|
+
group.values.push(value);
|
|
200
|
+
if (!existingGroup) {
|
|
201
|
+
groupedSetWrites.set(resolvedAccessControl, group);
|
|
202
|
+
}
|
|
141
203
|
}
|
|
142
204
|
});
|
|
143
205
|
const storageModule = getStorageModule();
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
storageModule.setBatch(
|
|
147
|
-
}
|
|
206
|
+
groupedSetWrites.forEach((group, accessControl) => {
|
|
207
|
+
storageModule.setSecureAccessControl(accessControl);
|
|
208
|
+
storageModule.setBatch(group.keys, group.values, _Storage.StorageScope.Secure);
|
|
209
|
+
});
|
|
148
210
|
if (keysToRemove.length > 0) {
|
|
149
211
|
storageModule.removeBatch(keysToRemove, _Storage.StorageScope.Secure);
|
|
150
212
|
}
|
|
151
213
|
}
|
|
152
|
-
function scheduleSecureWrite(key, value) {
|
|
153
|
-
|
|
214
|
+
function scheduleSecureWrite(key, value, accessControl) {
|
|
215
|
+
const pendingWrite = {
|
|
154
216
|
key,
|
|
155
217
|
value
|
|
156
|
-
}
|
|
218
|
+
};
|
|
219
|
+
if (accessControl !== undefined) {
|
|
220
|
+
pendingWrite.accessControl = accessControl;
|
|
221
|
+
}
|
|
222
|
+
pendingSecureWrites.set(key, pendingWrite);
|
|
157
223
|
if (secureFlushScheduled) {
|
|
158
224
|
return;
|
|
159
225
|
}
|
|
@@ -247,103 +313,186 @@ function writeMigrationVersion(scope, version) {
|
|
|
247
313
|
}
|
|
248
314
|
const storage = exports.storage = {
|
|
249
315
|
clear: scope => {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
316
|
+
measureOperation("storage:clear", scope, () => {
|
|
317
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
318
|
+
memoryStore.clear();
|
|
319
|
+
notifyAllListeners(memoryListeners);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
323
|
+
flushSecureWrites();
|
|
324
|
+
pendingSecureWrites.clear();
|
|
325
|
+
}
|
|
326
|
+
clearScopeRawCache(scope);
|
|
327
|
+
getStorageModule().clear(scope);
|
|
328
|
+
});
|
|
264
329
|
},
|
|
265
330
|
clearAll: () => {
|
|
266
|
-
storage
|
|
267
|
-
|
|
268
|
-
|
|
331
|
+
measureOperation("storage:clearAll", _Storage.StorageScope.Memory, () => {
|
|
332
|
+
storage.clear(_Storage.StorageScope.Memory);
|
|
333
|
+
storage.clear(_Storage.StorageScope.Disk);
|
|
334
|
+
storage.clear(_Storage.StorageScope.Secure);
|
|
335
|
+
}, 3);
|
|
269
336
|
},
|
|
270
337
|
clearNamespace: (namespace, scope) => {
|
|
271
|
-
(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
338
|
+
measureOperation("storage:clearNamespace", scope, () => {
|
|
339
|
+
(0, _internal.assertValidScope)(scope);
|
|
340
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
341
|
+
for (const key of memoryStore.keys()) {
|
|
342
|
+
if ((0, _internal.isNamespaced)(key, namespace)) {
|
|
343
|
+
memoryStore.delete(key);
|
|
344
|
+
}
|
|
276
345
|
}
|
|
346
|
+
notifyAllListeners(memoryListeners);
|
|
347
|
+
return;
|
|
277
348
|
}
|
|
278
|
-
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
if (scope === _Storage.StorageScope.Secure) {
|
|
282
|
-
flushSecureWrites();
|
|
283
|
-
}
|
|
284
|
-
const keys = getStorageModule().getAllKeys(scope);
|
|
285
|
-
const namespacedKeys = keys.filter(k => (0, _internal.isNamespaced)(k, namespace));
|
|
286
|
-
if (namespacedKeys.length > 0) {
|
|
287
|
-
getStorageModule().removeBatch(namespacedKeys, scope);
|
|
288
|
-
namespacedKeys.forEach(k => cacheRawValue(scope, k, undefined));
|
|
349
|
+
const keyPrefix = (0, _internal.prefixKey)(namespace, "");
|
|
289
350
|
if (scope === _Storage.StorageScope.Secure) {
|
|
290
|
-
|
|
351
|
+
flushSecureWrites();
|
|
291
352
|
}
|
|
292
|
-
|
|
353
|
+
clearScopeRawCache(scope);
|
|
354
|
+
getStorageModule().removeByPrefix(keyPrefix, scope);
|
|
355
|
+
});
|
|
293
356
|
},
|
|
294
357
|
clearBiometric: () => {
|
|
295
|
-
|
|
358
|
+
measureOperation("storage:clearBiometric", _Storage.StorageScope.Secure, () => {
|
|
359
|
+
getStorageModule().clearSecureBiometric();
|
|
360
|
+
});
|
|
296
361
|
},
|
|
297
362
|
has: (key, scope) => {
|
|
298
|
-
(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
363
|
+
return measureOperation("storage:has", scope, () => {
|
|
364
|
+
(0, _internal.assertValidScope)(scope);
|
|
365
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
366
|
+
return memoryStore.has(key);
|
|
367
|
+
}
|
|
368
|
+
return getStorageModule().has(key, scope);
|
|
369
|
+
});
|
|
303
370
|
},
|
|
304
371
|
getAllKeys: scope => {
|
|
305
|
-
(
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
372
|
+
return measureOperation("storage:getAllKeys", scope, () => {
|
|
373
|
+
(0, _internal.assertValidScope)(scope);
|
|
374
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
375
|
+
return Array.from(memoryStore.keys());
|
|
376
|
+
}
|
|
377
|
+
return getStorageModule().getAllKeys(scope);
|
|
378
|
+
});
|
|
379
|
+
},
|
|
380
|
+
getKeysByPrefix: (prefix, scope) => {
|
|
381
|
+
return measureOperation("storage:getKeysByPrefix", scope, () => {
|
|
382
|
+
(0, _internal.assertValidScope)(scope);
|
|
383
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
384
|
+
return Array.from(memoryStore.keys()).filter(key => key.startsWith(prefix));
|
|
385
|
+
}
|
|
386
|
+
return getStorageModule().getKeysByPrefix(prefix, scope);
|
|
387
|
+
});
|
|
388
|
+
},
|
|
389
|
+
getByPrefix: (prefix, scope) => {
|
|
390
|
+
return measureOperation("storage:getByPrefix", scope, () => {
|
|
391
|
+
const result = {};
|
|
392
|
+
const keys = storage.getKeysByPrefix(prefix, scope);
|
|
393
|
+
if (keys.length === 0) {
|
|
394
|
+
return result;
|
|
395
|
+
}
|
|
396
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
397
|
+
keys.forEach(key => {
|
|
398
|
+
const value = memoryStore.get(key);
|
|
399
|
+
if (typeof value === "string") {
|
|
400
|
+
result[key] = value;
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
return result;
|
|
404
|
+
}
|
|
405
|
+
const values = getStorageModule().getBatch(keys, scope);
|
|
406
|
+
keys.forEach((key, idx) => {
|
|
407
|
+
const value = (0, _internal.decodeNativeBatchValue)(values[idx]);
|
|
408
|
+
if (value !== undefined) {
|
|
409
|
+
result[key] = value;
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
return result;
|
|
413
|
+
});
|
|
310
414
|
},
|
|
311
415
|
getAll: scope => {
|
|
312
|
-
(
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
416
|
+
return measureOperation("storage:getAll", scope, () => {
|
|
417
|
+
(0, _internal.assertValidScope)(scope);
|
|
418
|
+
const result = {};
|
|
419
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
420
|
+
memoryStore.forEach((value, key) => {
|
|
421
|
+
if (typeof value === "string") result[key] = value;
|
|
422
|
+
});
|
|
423
|
+
return result;
|
|
424
|
+
}
|
|
425
|
+
const keys = getStorageModule().getAllKeys(scope);
|
|
426
|
+
if (keys.length === 0) return result;
|
|
427
|
+
const values = getStorageModule().getBatch(keys, scope);
|
|
428
|
+
keys.forEach((key, idx) => {
|
|
429
|
+
const val = (0, _internal.decodeNativeBatchValue)(values[idx]);
|
|
430
|
+
if (val !== undefined) result[key] = val;
|
|
317
431
|
});
|
|
318
432
|
return result;
|
|
319
|
-
}
|
|
320
|
-
const keys = getStorageModule().getAllKeys(scope);
|
|
321
|
-
if (keys.length === 0) return result;
|
|
322
|
-
const values = getStorageModule().getBatch(keys, scope);
|
|
323
|
-
keys.forEach((key, idx) => {
|
|
324
|
-
const val = (0, _internal.decodeNativeBatchValue)(values[idx]);
|
|
325
|
-
if (val !== undefined) result[key] = val;
|
|
326
433
|
});
|
|
327
|
-
return result;
|
|
328
434
|
},
|
|
329
435
|
size: scope => {
|
|
330
|
-
(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
436
|
+
return measureOperation("storage:size", scope, () => {
|
|
437
|
+
(0, _internal.assertValidScope)(scope);
|
|
438
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
439
|
+
return memoryStore.size;
|
|
440
|
+
}
|
|
441
|
+
return getStorageModule().size(scope);
|
|
442
|
+
});
|
|
335
443
|
},
|
|
336
444
|
setAccessControl: level => {
|
|
337
|
-
|
|
338
|
-
|
|
445
|
+
measureOperation("storage:setAccessControl", _Storage.StorageScope.Secure, () => {
|
|
446
|
+
secureDefaultAccessControl = level;
|
|
447
|
+
getStorageModule().setSecureAccessControl(level);
|
|
448
|
+
});
|
|
449
|
+
},
|
|
450
|
+
setSecureWritesAsync: enabled => {
|
|
451
|
+
measureOperation("storage:setSecureWritesAsync", _Storage.StorageScope.Secure, () => {
|
|
452
|
+
getStorageModule().setSecureWritesAsync(enabled);
|
|
453
|
+
});
|
|
454
|
+
},
|
|
455
|
+
flushSecureWrites: () => {
|
|
456
|
+
measureOperation("storage:flushSecureWrites", _Storage.StorageScope.Secure, () => {
|
|
457
|
+
flushSecureWrites();
|
|
458
|
+
});
|
|
339
459
|
},
|
|
340
460
|
setKeychainAccessGroup: group => {
|
|
341
|
-
|
|
461
|
+
measureOperation("storage:setKeychainAccessGroup", _Storage.StorageScope.Secure, () => {
|
|
462
|
+
getStorageModule().setKeychainAccessGroup(group);
|
|
463
|
+
});
|
|
464
|
+
},
|
|
465
|
+
setMetricsObserver: observer => {
|
|
466
|
+
metricsObserver = observer;
|
|
467
|
+
},
|
|
468
|
+
getMetricsSnapshot: () => {
|
|
469
|
+
const snapshot = {};
|
|
470
|
+
metricsCounters.forEach((value, key) => {
|
|
471
|
+
snapshot[key] = {
|
|
472
|
+
count: value.count,
|
|
473
|
+
totalDurationMs: value.totalDurationMs,
|
|
474
|
+
avgDurationMs: value.count === 0 ? 0 : value.totalDurationMs / value.count,
|
|
475
|
+
maxDurationMs: value.maxDurationMs
|
|
476
|
+
};
|
|
477
|
+
});
|
|
478
|
+
return snapshot;
|
|
479
|
+
},
|
|
480
|
+
resetMetrics: () => {
|
|
481
|
+
metricsCounters.clear();
|
|
342
482
|
}
|
|
343
483
|
};
|
|
484
|
+
function setWebSecureStorageBackend(_backend) {
|
|
485
|
+
// Native platforms do not use web secure backends.
|
|
486
|
+
}
|
|
487
|
+
function getWebSecureStorageBackend() {
|
|
488
|
+
return undefined;
|
|
489
|
+
}
|
|
344
490
|
function canUseRawBatchPath(item) {
|
|
345
491
|
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
|
|
346
492
|
}
|
|
493
|
+
function canUseSecureRawBatchPath(item) {
|
|
494
|
+
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true;
|
|
495
|
+
}
|
|
347
496
|
function defaultSerialize(value) {
|
|
348
497
|
return (0, _internal.serializeWithPrimitiveFastPath)(value);
|
|
349
498
|
}
|
|
@@ -355,7 +504,8 @@ function createStorageItem(config) {
|
|
|
355
504
|
const serialize = config.serialize ?? defaultSerialize;
|
|
356
505
|
const deserialize = config.deserialize ?? defaultDeserialize;
|
|
357
506
|
const isMemory = config.scope === _Storage.StorageScope.Memory;
|
|
358
|
-
const
|
|
507
|
+
const resolvedBiometricLevel = config.scope === _Storage.StorageScope.Secure ? config.biometricLevel ?? (config.biometric === true ? _Storage.BiometricLevel.BiometryOnly : _Storage.BiometricLevel.None) : _Storage.BiometricLevel.None;
|
|
508
|
+
const isBiometric = resolvedBiometricLevel !== _Storage.BiometricLevel.None;
|
|
359
509
|
const secureAccessControl = config.accessControl;
|
|
360
510
|
const validate = config.validate;
|
|
361
511
|
const onValidationError = config.onValidationError;
|
|
@@ -364,7 +514,8 @@ function createStorageItem(config) {
|
|
|
364
514
|
const expirationTtlMs = expiration?.ttlMs;
|
|
365
515
|
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
366
516
|
const readCache = !isMemory && config.readCache === true;
|
|
367
|
-
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric
|
|
517
|
+
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
|
|
518
|
+
const defaultValue = config.defaultValue;
|
|
368
519
|
const nonMemoryScope = config.scope === _Storage.StorageScope.Disk ? _Storage.StorageScope.Disk : config.scope === _Storage.StorageScope.Secure ? _Storage.StorageScope.Secure : null;
|
|
369
520
|
if (expiration && expiration.ttlMs <= 0) {
|
|
370
521
|
throw new Error("expiration.ttlMs must be greater than 0.");
|
|
@@ -374,10 +525,12 @@ function createStorageItem(config) {
|
|
|
374
525
|
let lastRaw = undefined;
|
|
375
526
|
let lastValue;
|
|
376
527
|
let hasLastValue = false;
|
|
528
|
+
let lastExpiresAt = undefined;
|
|
377
529
|
const invalidateParsedCache = () => {
|
|
378
530
|
lastRaw = undefined;
|
|
379
531
|
lastValue = undefined;
|
|
380
532
|
hasLastValue = false;
|
|
533
|
+
lastExpiresAt = undefined;
|
|
381
534
|
};
|
|
382
535
|
const ensureSubscription = () => {
|
|
383
536
|
if (unsubscribe) {
|
|
@@ -425,12 +578,12 @@ function createStorageItem(config) {
|
|
|
425
578
|
};
|
|
426
579
|
const writeStoredRaw = rawValue => {
|
|
427
580
|
if (isBiometric) {
|
|
428
|
-
getStorageModule().
|
|
581
|
+
getStorageModule().setSecureBiometricWithLevel(storageKey, rawValue, resolvedBiometricLevel);
|
|
429
582
|
return;
|
|
430
583
|
}
|
|
431
584
|
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
432
585
|
if (coalesceSecureWrites) {
|
|
433
|
-
scheduleSecureWrite(storageKey, rawValue);
|
|
586
|
+
scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
|
|
434
587
|
return;
|
|
435
588
|
}
|
|
436
589
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
@@ -446,7 +599,7 @@ function createStorageItem(config) {
|
|
|
446
599
|
}
|
|
447
600
|
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
448
601
|
if (coalesceSecureWrites) {
|
|
449
|
-
scheduleSecureWrite(storageKey, undefined);
|
|
602
|
+
scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
|
|
450
603
|
return;
|
|
451
604
|
}
|
|
452
605
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
@@ -479,7 +632,7 @@ function createStorageItem(config) {
|
|
|
479
632
|
if (onValidationError) {
|
|
480
633
|
return onValidationError(invalidValue);
|
|
481
634
|
}
|
|
482
|
-
return
|
|
635
|
+
return defaultValue;
|
|
483
636
|
};
|
|
484
637
|
const ensureValidatedValue = (candidate, hadStoredValue) => {
|
|
485
638
|
if (!validate || validate(candidate)) {
|
|
@@ -487,40 +640,62 @@ function createStorageItem(config) {
|
|
|
487
640
|
}
|
|
488
641
|
const resolved = resolveInvalidValue(candidate);
|
|
489
642
|
if (validate && !validate(resolved)) {
|
|
490
|
-
return
|
|
643
|
+
return defaultValue;
|
|
491
644
|
}
|
|
492
645
|
if (hadStoredValue) {
|
|
493
646
|
writeValueWithoutValidation(resolved);
|
|
494
647
|
}
|
|
495
648
|
return resolved;
|
|
496
649
|
};
|
|
497
|
-
const
|
|
650
|
+
const getInternal = () => {
|
|
498
651
|
const raw = readStoredRaw();
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
652
|
+
if (!memoryExpiration && raw === lastRaw && hasLastValue) {
|
|
653
|
+
if (!expiration || lastExpiresAt === null) {
|
|
654
|
+
return lastValue;
|
|
655
|
+
}
|
|
656
|
+
if (typeof lastExpiresAt === "number") {
|
|
657
|
+
if (lastExpiresAt > Date.now()) {
|
|
658
|
+
return lastValue;
|
|
659
|
+
}
|
|
660
|
+
removeStoredRaw();
|
|
661
|
+
invalidateParsedCache();
|
|
662
|
+
onExpired?.(storageKey);
|
|
663
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
664
|
+
hasLastValue = true;
|
|
665
|
+
return lastValue;
|
|
666
|
+
}
|
|
502
667
|
}
|
|
503
668
|
lastRaw = raw;
|
|
504
669
|
if (raw === undefined) {
|
|
505
|
-
|
|
670
|
+
lastExpiresAt = undefined;
|
|
671
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
506
672
|
hasLastValue = true;
|
|
507
673
|
return lastValue;
|
|
508
674
|
}
|
|
509
675
|
if (isMemory) {
|
|
676
|
+
lastExpiresAt = undefined;
|
|
510
677
|
lastValue = ensureValidatedValue(raw, true);
|
|
511
678
|
hasLastValue = true;
|
|
512
679
|
return lastValue;
|
|
513
680
|
}
|
|
681
|
+
if (typeof raw !== "string") {
|
|
682
|
+
lastExpiresAt = undefined;
|
|
683
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
684
|
+
hasLastValue = true;
|
|
685
|
+
return lastValue;
|
|
686
|
+
}
|
|
514
687
|
let deserializableRaw = raw;
|
|
515
688
|
if (expiration) {
|
|
689
|
+
let envelopeExpiresAt = null;
|
|
516
690
|
try {
|
|
517
691
|
const parsed = JSON.parse(raw);
|
|
518
692
|
if ((0, _internal.isStoredEnvelope)(parsed)) {
|
|
693
|
+
envelopeExpiresAt = parsed.expiresAt;
|
|
519
694
|
if (parsed.expiresAt <= Date.now()) {
|
|
520
695
|
removeStoredRaw();
|
|
521
696
|
invalidateParsedCache();
|
|
522
697
|
onExpired?.(storageKey);
|
|
523
|
-
lastValue = ensureValidatedValue(
|
|
698
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
524
699
|
hasLastValue = true;
|
|
525
700
|
return lastValue;
|
|
526
701
|
}
|
|
@@ -529,37 +704,60 @@ function createStorageItem(config) {
|
|
|
529
704
|
} catch {
|
|
530
705
|
// Keep backward compatibility with legacy raw values.
|
|
531
706
|
}
|
|
707
|
+
lastExpiresAt = envelopeExpiresAt;
|
|
708
|
+
} else {
|
|
709
|
+
lastExpiresAt = undefined;
|
|
532
710
|
}
|
|
533
711
|
lastValue = ensureValidatedValue(deserialize(deserializableRaw), true);
|
|
534
712
|
hasLastValue = true;
|
|
535
713
|
return lastValue;
|
|
536
714
|
};
|
|
715
|
+
const getCurrentVersion = () => {
|
|
716
|
+
const raw = readStoredRaw();
|
|
717
|
+
return (0, _internal.toVersionToken)(raw);
|
|
718
|
+
};
|
|
719
|
+
const get = () => measureOperation("item:get", config.scope, () => getInternal());
|
|
720
|
+
const getWithVersion = () => measureOperation("item:getWithVersion", config.scope, () => ({
|
|
721
|
+
value: getInternal(),
|
|
722
|
+
version: getCurrentVersion()
|
|
723
|
+
}));
|
|
537
724
|
const set = valueOrFn => {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
725
|
+
measureOperation("item:set", config.scope, () => {
|
|
726
|
+
const newValue = isUpdater(valueOrFn) ? valueOrFn(getInternal()) : valueOrFn;
|
|
727
|
+
invalidateParsedCache();
|
|
728
|
+
if (validate && !validate(newValue)) {
|
|
729
|
+
throw new Error(`Validation failed for key "${storageKey}" in scope "${_Storage.StorageScope[config.scope]}".`);
|
|
730
|
+
}
|
|
731
|
+
writeValueWithoutValidation(newValue);
|
|
732
|
+
});
|
|
545
733
|
};
|
|
734
|
+
const setIfVersion = (version, valueOrFn) => measureOperation("item:setIfVersion", config.scope, () => {
|
|
735
|
+
const currentVersion = getCurrentVersion();
|
|
736
|
+
if (currentVersion !== version) {
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
set(valueOrFn);
|
|
740
|
+
return true;
|
|
741
|
+
});
|
|
546
742
|
const deleteItem = () => {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
if (
|
|
550
|
-
memoryExpiration
|
|
743
|
+
measureOperation("item:delete", config.scope, () => {
|
|
744
|
+
invalidateParsedCache();
|
|
745
|
+
if (isMemory) {
|
|
746
|
+
if (memoryExpiration) {
|
|
747
|
+
memoryExpiration.delete(storageKey);
|
|
748
|
+
}
|
|
749
|
+
memoryStore.delete(storageKey);
|
|
750
|
+
notifyKeyListeners(memoryListeners, storageKey);
|
|
751
|
+
return;
|
|
551
752
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
return;
|
|
555
|
-
}
|
|
556
|
-
removeStoredRaw();
|
|
753
|
+
removeStoredRaw();
|
|
754
|
+
});
|
|
557
755
|
};
|
|
558
|
-
const hasItem = () => {
|
|
756
|
+
const hasItem = () => measureOperation("item:has", config.scope, () => {
|
|
559
757
|
if (isMemory) return memoryStore.has(storageKey);
|
|
560
758
|
if (isBiometric) return getStorageModule().hasSecureBiometric(storageKey);
|
|
561
759
|
return getStorageModule().has(storageKey, config.scope);
|
|
562
|
-
};
|
|
760
|
+
});
|
|
563
761
|
const subscribe = callback => {
|
|
564
762
|
ensureSubscription();
|
|
565
763
|
listeners.add(callback);
|
|
@@ -576,7 +774,9 @@ function createStorageItem(config) {
|
|
|
576
774
|
};
|
|
577
775
|
const storageItem = {
|
|
578
776
|
get,
|
|
777
|
+
getWithVersion,
|
|
579
778
|
set,
|
|
779
|
+
setIfVersion,
|
|
580
780
|
delete: deleteItem,
|
|
581
781
|
has: hasItem,
|
|
582
782
|
subscribe,
|
|
@@ -590,124 +790,151 @@ function createStorageItem(config) {
|
|
|
590
790
|
_hasExpiration: expiration !== undefined,
|
|
591
791
|
_readCacheEnabled: readCache,
|
|
592
792
|
_isBiometric: isBiometric,
|
|
593
|
-
|
|
793
|
+
_defaultValue: defaultValue,
|
|
794
|
+
...(secureAccessControl !== undefined ? {
|
|
795
|
+
_secureAccessControl: secureAccessControl
|
|
796
|
+
} : {}),
|
|
594
797
|
scope: config.scope,
|
|
595
798
|
key: storageKey
|
|
596
799
|
};
|
|
597
800
|
return storageItem;
|
|
598
801
|
}
|
|
599
|
-
function useStorage(item) {
|
|
600
|
-
const value = (0, _react.useSyncExternalStore)(item.subscribe, item.get, item.get);
|
|
601
|
-
return [value, item.set];
|
|
602
|
-
}
|
|
603
|
-
function useStorageSelector(item, selector, isEqual = Object.is) {
|
|
604
|
-
const selectedRef = (0, _react.useRef)({
|
|
605
|
-
hasValue: false
|
|
606
|
-
});
|
|
607
|
-
const getSelectedSnapshot = () => {
|
|
608
|
-
const nextSelected = selector(item.get());
|
|
609
|
-
const current = selectedRef.current;
|
|
610
|
-
if (current.hasValue && isEqual(current.value, nextSelected)) {
|
|
611
|
-
return current.value;
|
|
612
|
-
}
|
|
613
|
-
selectedRef.current = {
|
|
614
|
-
hasValue: true,
|
|
615
|
-
value: nextSelected
|
|
616
|
-
};
|
|
617
|
-
return nextSelected;
|
|
618
|
-
};
|
|
619
|
-
const selectedValue = (0, _react.useSyncExternalStore)(item.subscribe, getSelectedSnapshot, getSelectedSnapshot);
|
|
620
|
-
return [selectedValue, item.set];
|
|
621
|
-
}
|
|
622
|
-
function useSetStorage(item) {
|
|
623
|
-
return item.set;
|
|
624
|
-
}
|
|
625
802
|
function getBatch(items, scope) {
|
|
626
|
-
(
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
const useRawBatchPath = items.every(item => canUseRawBatchPath(item));
|
|
631
|
-
if (!useRawBatchPath) {
|
|
632
|
-
return items.map(item => item.get());
|
|
633
|
-
}
|
|
634
|
-
const useBatchCache = items.every(item => item._readCacheEnabled === true);
|
|
635
|
-
const rawValues = new Array(items.length);
|
|
636
|
-
const keysToFetch = [];
|
|
637
|
-
const keyIndexes = [];
|
|
638
|
-
items.forEach((item, index) => {
|
|
639
|
-
if (scope === _Storage.StorageScope.Secure) {
|
|
640
|
-
if (hasPendingSecureWrite(item.key)) {
|
|
641
|
-
rawValues[index] = readPendingSecureWrite(item.key);
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
803
|
+
return measureOperation("batch:get", scope, () => {
|
|
804
|
+
(0, _internal.assertBatchScope)(items, scope);
|
|
805
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
806
|
+
return items.map(item => item.get());
|
|
644
807
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
808
|
+
const useRawBatchPath = items.every(item => scope === _Storage.StorageScope.Secure ? canUseSecureRawBatchPath(item) : canUseRawBatchPath(item));
|
|
809
|
+
if (!useRawBatchPath) {
|
|
810
|
+
return items.map(item => item.get());
|
|
650
811
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
812
|
+
const rawValues = new Array(items.length);
|
|
813
|
+
const keysToFetch = [];
|
|
814
|
+
const keyIndexes = [];
|
|
815
|
+
items.forEach((item, index) => {
|
|
816
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
817
|
+
if (hasPendingSecureWrite(item.key)) {
|
|
818
|
+
rawValues[index] = readPendingSecureWrite(item.key);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (item._readCacheEnabled === true) {
|
|
823
|
+
if (hasCachedRawValue(scope, item.key)) {
|
|
824
|
+
rawValues[index] = readCachedRawValue(scope, item.key);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
keysToFetch.push(item.key);
|
|
829
|
+
keyIndexes.push(index);
|
|
661
830
|
});
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
831
|
+
if (keysToFetch.length > 0) {
|
|
832
|
+
const fetchedValues = getStorageModule().getBatch(keysToFetch, scope).map(value => (0, _internal.decodeNativeBatchValue)(value));
|
|
833
|
+
fetchedValues.forEach((value, index) => {
|
|
834
|
+
const key = keysToFetch[index];
|
|
835
|
+
const targetIndex = keyIndexes[index];
|
|
836
|
+
if (key === undefined || targetIndex === undefined) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
rawValues[targetIndex] = value;
|
|
840
|
+
cacheRawValue(scope, key, value);
|
|
841
|
+
});
|
|
667
842
|
}
|
|
668
|
-
return
|
|
669
|
-
|
|
843
|
+
return items.map((item, index) => {
|
|
844
|
+
const raw = rawValues[index];
|
|
845
|
+
if (raw === undefined) {
|
|
846
|
+
return asInternal(item)._defaultValue;
|
|
847
|
+
}
|
|
848
|
+
return item.deserialize(raw);
|
|
849
|
+
});
|
|
850
|
+
}, items.length);
|
|
670
851
|
}
|
|
671
852
|
function setBatch(items, scope) {
|
|
672
|
-
(
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
853
|
+
measureOperation("batch:set", scope, () => {
|
|
854
|
+
(0, _internal.assertBatchScope)(items.map(batchEntry => batchEntry.item), scope);
|
|
855
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
856
|
+
items.forEach(({
|
|
857
|
+
item,
|
|
858
|
+
value
|
|
859
|
+
}) => item.set(value));
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
863
|
+
const secureEntries = items.map(({
|
|
864
|
+
item,
|
|
865
|
+
value
|
|
866
|
+
}) => ({
|
|
867
|
+
item,
|
|
868
|
+
value,
|
|
869
|
+
internal: asInternal(item)
|
|
870
|
+
}));
|
|
871
|
+
const canUseSecureBatchPath = secureEntries.every(({
|
|
872
|
+
internal
|
|
873
|
+
}) => canUseSecureRawBatchPath(internal));
|
|
874
|
+
if (!canUseSecureBatchPath) {
|
|
875
|
+
items.forEach(({
|
|
876
|
+
item,
|
|
877
|
+
value
|
|
878
|
+
}) => item.set(value));
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
flushSecureWrites();
|
|
882
|
+
const storageModule = getStorageModule();
|
|
883
|
+
const groupedByAccessControl = new Map();
|
|
884
|
+
secureEntries.forEach(({
|
|
885
|
+
item,
|
|
886
|
+
value,
|
|
887
|
+
internal
|
|
888
|
+
}) => {
|
|
889
|
+
const accessControl = internal._secureAccessControl ?? secureDefaultAccessControl;
|
|
890
|
+
const existingGroup = groupedByAccessControl.get(accessControl);
|
|
891
|
+
const group = existingGroup ?? {
|
|
892
|
+
keys: [],
|
|
893
|
+
values: []
|
|
894
|
+
};
|
|
895
|
+
group.keys.push(item.key);
|
|
896
|
+
group.values.push(item.serialize(value));
|
|
897
|
+
if (!existingGroup) {
|
|
898
|
+
groupedByAccessControl.set(accessControl, group);
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
groupedByAccessControl.forEach((group, accessControl) => {
|
|
902
|
+
storageModule.setSecureAccessControl(accessControl);
|
|
903
|
+
storageModule.setBatch(group.keys, group.values, scope);
|
|
904
|
+
group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
|
|
905
|
+
});
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
const useRawBatchPath = items.every(({
|
|
909
|
+
item
|
|
910
|
+
}) => canUseRawBatchPath(asInternal(item)));
|
|
911
|
+
if (!useRawBatchPath) {
|
|
912
|
+
items.forEach(({
|
|
913
|
+
item,
|
|
914
|
+
value
|
|
915
|
+
}) => item.set(value));
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
const keys = items.map(entry => entry.item.key);
|
|
919
|
+
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
920
|
+
getStorageModule().setBatch(keys, values, scope);
|
|
921
|
+
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
922
|
+
}, items.length);
|
|
698
923
|
}
|
|
699
924
|
function removeBatch(items, scope) {
|
|
700
|
-
(
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
925
|
+
measureOperation("batch:remove", scope, () => {
|
|
926
|
+
(0, _internal.assertBatchScope)(items, scope);
|
|
927
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
928
|
+
items.forEach(item => item.delete());
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
const keys = items.map(item => item.key);
|
|
932
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
933
|
+
flushSecureWrites();
|
|
934
|
+
}
|
|
935
|
+
getStorageModule().removeBatch(keys, scope);
|
|
936
|
+
keys.forEach(key => cacheRawValue(scope, key, undefined));
|
|
937
|
+
}, items.length);
|
|
711
938
|
}
|
|
712
939
|
function registerMigration(version, migration) {
|
|
713
940
|
if (!Number.isInteger(version) || version <= 0) {
|
|
@@ -719,93 +946,133 @@ function registerMigration(version, migration) {
|
|
|
719
946
|
registeredMigrations.set(version, migration);
|
|
720
947
|
}
|
|
721
948
|
function migrateToLatest(scope = _Storage.StorageScope.Disk) {
|
|
722
|
-
(
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
949
|
+
return measureOperation("migration:run", scope, () => {
|
|
950
|
+
(0, _internal.assertValidScope)(scope);
|
|
951
|
+
const currentVersion = readMigrationVersion(scope);
|
|
952
|
+
const versions = Array.from(registeredMigrations.keys()).filter(version => version > currentVersion).sort((a, b) => a - b);
|
|
953
|
+
let appliedVersion = currentVersion;
|
|
954
|
+
const context = {
|
|
955
|
+
scope,
|
|
956
|
+
getRaw: key => getRawValue(key, scope),
|
|
957
|
+
setRaw: (key, value) => setRawValue(key, value, scope),
|
|
958
|
+
removeRaw: key => removeRawValue(key, scope)
|
|
959
|
+
};
|
|
960
|
+
versions.forEach(version => {
|
|
961
|
+
const migration = registeredMigrations.get(version);
|
|
962
|
+
if (!migration) {
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
migration(context);
|
|
966
|
+
writeMigrationVersion(scope, version);
|
|
967
|
+
appliedVersion = version;
|
|
968
|
+
});
|
|
969
|
+
return appliedVersion;
|
|
740
970
|
});
|
|
741
|
-
return appliedVersion;
|
|
742
971
|
}
|
|
743
972
|
function runTransaction(scope, transaction) {
|
|
744
|
-
(
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
const rollback = new Map();
|
|
749
|
-
const rememberRollback = key => {
|
|
750
|
-
if (rollback.has(key)) {
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
rollback.set(key, getRawValue(key, scope));
|
|
754
|
-
};
|
|
755
|
-
const tx = {
|
|
756
|
-
scope,
|
|
757
|
-
getRaw: key => getRawValue(key, scope),
|
|
758
|
-
setRaw: (key, value) => {
|
|
759
|
-
rememberRollback(key);
|
|
760
|
-
setRawValue(key, value, scope);
|
|
761
|
-
},
|
|
762
|
-
removeRaw: key => {
|
|
763
|
-
rememberRollback(key);
|
|
764
|
-
removeRawValue(key, scope);
|
|
765
|
-
},
|
|
766
|
-
getItem: item => {
|
|
767
|
-
(0, _internal.assertBatchScope)([item], scope);
|
|
768
|
-
return item.get();
|
|
769
|
-
},
|
|
770
|
-
setItem: (item, value) => {
|
|
771
|
-
(0, _internal.assertBatchScope)([item], scope);
|
|
772
|
-
rememberRollback(item.key);
|
|
773
|
-
item.set(value);
|
|
774
|
-
},
|
|
775
|
-
removeItem: item => {
|
|
776
|
-
(0, _internal.assertBatchScope)([item], scope);
|
|
777
|
-
rememberRollback(item.key);
|
|
778
|
-
item.delete();
|
|
973
|
+
return measureOperation("transaction:run", scope, () => {
|
|
974
|
+
(0, _internal.assertValidScope)(scope);
|
|
975
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
976
|
+
flushSecureWrites();
|
|
779
977
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
978
|
+
const rollback = new Map();
|
|
979
|
+
const rememberRollback = key => {
|
|
980
|
+
if (rollback.has(key)) {
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
rollback.set(key, getRawValue(key, scope));
|
|
984
|
+
};
|
|
985
|
+
const tx = {
|
|
986
|
+
scope,
|
|
987
|
+
getRaw: key => getRawValue(key, scope),
|
|
988
|
+
setRaw: (key, value) => {
|
|
989
|
+
rememberRollback(key);
|
|
990
|
+
setRawValue(key, value, scope);
|
|
991
|
+
},
|
|
992
|
+
removeRaw: key => {
|
|
993
|
+
rememberRollback(key);
|
|
786
994
|
removeRawValue(key, scope);
|
|
995
|
+
},
|
|
996
|
+
getItem: item => {
|
|
997
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
998
|
+
return item.get();
|
|
999
|
+
},
|
|
1000
|
+
setItem: (item, value) => {
|
|
1001
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
1002
|
+
rememberRollback(item.key);
|
|
1003
|
+
item.set(value);
|
|
1004
|
+
},
|
|
1005
|
+
removeItem: item => {
|
|
1006
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
1007
|
+
rememberRollback(item.key);
|
|
1008
|
+
item.delete();
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
try {
|
|
1012
|
+
return transaction(tx);
|
|
1013
|
+
} catch (error) {
|
|
1014
|
+
const rollbackEntries = Array.from(rollback.entries()).reverse();
|
|
1015
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
1016
|
+
rollbackEntries.forEach(([key, previousValue]) => {
|
|
1017
|
+
if (previousValue === undefined) {
|
|
1018
|
+
removeRawValue(key, scope);
|
|
1019
|
+
} else {
|
|
1020
|
+
setRawValue(key, previousValue, scope);
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
787
1023
|
} else {
|
|
788
|
-
|
|
1024
|
+
const keysToSet = [];
|
|
1025
|
+
const valuesToSet = [];
|
|
1026
|
+
const keysToRemove = [];
|
|
1027
|
+
rollbackEntries.forEach(([key, previousValue]) => {
|
|
1028
|
+
if (previousValue === undefined) {
|
|
1029
|
+
keysToRemove.push(key);
|
|
1030
|
+
} else {
|
|
1031
|
+
keysToSet.push(key);
|
|
1032
|
+
valuesToSet.push(previousValue);
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
1036
|
+
flushSecureWrites();
|
|
1037
|
+
}
|
|
1038
|
+
if (keysToSet.length > 0) {
|
|
1039
|
+
getStorageModule().setBatch(keysToSet, valuesToSet, scope);
|
|
1040
|
+
keysToSet.forEach((key, index) => cacheRawValue(scope, key, valuesToSet[index]));
|
|
1041
|
+
}
|
|
1042
|
+
if (keysToRemove.length > 0) {
|
|
1043
|
+
getStorageModule().removeBatch(keysToRemove, scope);
|
|
1044
|
+
keysToRemove.forEach(key => cacheRawValue(scope, key, undefined));
|
|
1045
|
+
}
|
|
789
1046
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
}
|
|
1047
|
+
throw error;
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
793
1050
|
}
|
|
794
1051
|
function createSecureAuthStorage(config, options) {
|
|
795
1052
|
const ns = options?.namespace ?? "auth";
|
|
796
1053
|
const result = {};
|
|
797
|
-
for (const key of
|
|
1054
|
+
for (const key of typedKeys(config)) {
|
|
798
1055
|
const itemConfig = config[key];
|
|
1056
|
+
const expirationConfig = itemConfig.ttlMs !== undefined ? {
|
|
1057
|
+
ttlMs: itemConfig.ttlMs
|
|
1058
|
+
} : undefined;
|
|
799
1059
|
result[key] = createStorageItem({
|
|
800
1060
|
key,
|
|
801
1061
|
scope: _Storage.StorageScope.Secure,
|
|
802
1062
|
defaultValue: "",
|
|
803
1063
|
namespace: ns,
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1064
|
+
...(itemConfig.biometric !== undefined ? {
|
|
1065
|
+
biometric: itemConfig.biometric
|
|
1066
|
+
} : {}),
|
|
1067
|
+
...(itemConfig.biometricLevel !== undefined ? {
|
|
1068
|
+
biometricLevel: itemConfig.biometricLevel
|
|
1069
|
+
} : {}),
|
|
1070
|
+
...(itemConfig.accessControl !== undefined ? {
|
|
1071
|
+
accessControl: itemConfig.accessControl
|
|
1072
|
+
} : {}),
|
|
1073
|
+
...(expirationConfig !== undefined ? {
|
|
1074
|
+
expiration: expirationConfig
|
|
1075
|
+
} : {})
|
|
809
1076
|
});
|
|
810
1077
|
}
|
|
811
1078
|
return result;
|