react-native-nitro-storage 0.3.2 → 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 +141 -30
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +22 -2
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +3 -0
- 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 +466 -275
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +564 -270
- 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/module/index.js +466 -277
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +564 -272
- 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/typescript/Storage.nitro.d.ts +2 -0
- package/lib/typescript/Storage.nitro.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +38 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +40 -1
- 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/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +2 -0
- package/package.json +1 -1
- package/src/Storage.nitro.ts +2 -0
- package/src/index.ts +616 -296
- package/src/index.web.ts +728 -288
- package/src/internal.ts +28 -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,6 +36,7 @@ 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
41
|
Object.defineProperty(exports, "useSetStorage", {
|
|
40
42
|
enumerable: true,
|
|
@@ -87,6 +89,36 @@ const scopedRawCache = new Map([[_Storage.StorageScope.Disk, new Map()], [_Stora
|
|
|
87
89
|
const pendingSecureWrites = new Map();
|
|
88
90
|
let secureFlushScheduled = false;
|
|
89
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
|
+
}
|
|
90
122
|
function getScopedListeners(scope) {
|
|
91
123
|
return scopedListeners.get(scope);
|
|
92
124
|
}
|
|
@@ -147,34 +179,47 @@ function flushSecureWrites() {
|
|
|
147
179
|
}
|
|
148
180
|
const writes = Array.from(pendingSecureWrites.values());
|
|
149
181
|
pendingSecureWrites.clear();
|
|
150
|
-
const
|
|
151
|
-
const valuesToSet = [];
|
|
182
|
+
const groupedSetWrites = new Map();
|
|
152
183
|
const keysToRemove = [];
|
|
153
184
|
writes.forEach(({
|
|
154
185
|
key,
|
|
155
|
-
value
|
|
186
|
+
value,
|
|
187
|
+
accessControl
|
|
156
188
|
}) => {
|
|
157
189
|
if (value === undefined) {
|
|
158
190
|
keysToRemove.push(key);
|
|
159
191
|
} else {
|
|
160
|
-
|
|
161
|
-
|
|
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
|
+
}
|
|
162
203
|
}
|
|
163
204
|
});
|
|
164
205
|
const storageModule = getStorageModule();
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
storageModule.setBatch(
|
|
168
|
-
}
|
|
206
|
+
groupedSetWrites.forEach((group, accessControl) => {
|
|
207
|
+
storageModule.setSecureAccessControl(accessControl);
|
|
208
|
+
storageModule.setBatch(group.keys, group.values, _Storage.StorageScope.Secure);
|
|
209
|
+
});
|
|
169
210
|
if (keysToRemove.length > 0) {
|
|
170
211
|
storageModule.removeBatch(keysToRemove, _Storage.StorageScope.Secure);
|
|
171
212
|
}
|
|
172
213
|
}
|
|
173
|
-
function scheduleSecureWrite(key, value) {
|
|
174
|
-
|
|
214
|
+
function scheduleSecureWrite(key, value, accessControl) {
|
|
215
|
+
const pendingWrite = {
|
|
175
216
|
key,
|
|
176
217
|
value
|
|
177
|
-
}
|
|
218
|
+
};
|
|
219
|
+
if (accessControl !== undefined) {
|
|
220
|
+
pendingWrite.accessControl = accessControl;
|
|
221
|
+
}
|
|
222
|
+
pendingSecureWrites.set(key, pendingWrite);
|
|
178
223
|
if (secureFlushScheduled) {
|
|
179
224
|
return;
|
|
180
225
|
}
|
|
@@ -268,97 +313,180 @@ function writeMigrationVersion(scope, version) {
|
|
|
268
313
|
}
|
|
269
314
|
const storage = exports.storage = {
|
|
270
315
|
clear: scope => {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
+
});
|
|
282
329
|
},
|
|
283
330
|
clearAll: () => {
|
|
284
|
-
storage
|
|
285
|
-
|
|
286
|
-
|
|
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);
|
|
287
336
|
},
|
|
288
337
|
clearNamespace: (namespace, scope) => {
|
|
289
|
-
(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
+
}
|
|
294
345
|
}
|
|
346
|
+
notifyAllListeners(memoryListeners);
|
|
347
|
+
return;
|
|
295
348
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
clearScopeRawCache(scope);
|
|
304
|
-
getStorageModule().removeByPrefix(keyPrefix, scope);
|
|
349
|
+
const keyPrefix = (0, _internal.prefixKey)(namespace, "");
|
|
350
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
351
|
+
flushSecureWrites();
|
|
352
|
+
}
|
|
353
|
+
clearScopeRawCache(scope);
|
|
354
|
+
getStorageModule().removeByPrefix(keyPrefix, scope);
|
|
355
|
+
});
|
|
305
356
|
},
|
|
306
357
|
clearBiometric: () => {
|
|
307
|
-
|
|
358
|
+
measureOperation("storage:clearBiometric", _Storage.StorageScope.Secure, () => {
|
|
359
|
+
getStorageModule().clearSecureBiometric();
|
|
360
|
+
});
|
|
308
361
|
},
|
|
309
362
|
has: (key, scope) => {
|
|
310
|
-
(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
+
});
|
|
315
370
|
},
|
|
316
371
|
getAllKeys: scope => {
|
|
317
|
-
(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
+
});
|
|
322
414
|
},
|
|
323
415
|
getAll: scope => {
|
|
324
|
-
(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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;
|
|
329
431
|
});
|
|
330
432
|
return result;
|
|
331
|
-
}
|
|
332
|
-
const keys = getStorageModule().getAllKeys(scope);
|
|
333
|
-
if (keys.length === 0) return result;
|
|
334
|
-
const values = getStorageModule().getBatch(keys, scope);
|
|
335
|
-
keys.forEach((key, idx) => {
|
|
336
|
-
const val = (0, _internal.decodeNativeBatchValue)(values[idx]);
|
|
337
|
-
if (val !== undefined) result[key] = val;
|
|
338
433
|
});
|
|
339
|
-
return result;
|
|
340
434
|
},
|
|
341
435
|
size: scope => {
|
|
342
|
-
(
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
+
});
|
|
347
443
|
},
|
|
348
444
|
setAccessControl: level => {
|
|
349
|
-
|
|
350
|
-
|
|
445
|
+
measureOperation("storage:setAccessControl", _Storage.StorageScope.Secure, () => {
|
|
446
|
+
secureDefaultAccessControl = level;
|
|
447
|
+
getStorageModule().setSecureAccessControl(level);
|
|
448
|
+
});
|
|
351
449
|
},
|
|
352
450
|
setSecureWritesAsync: enabled => {
|
|
353
|
-
|
|
451
|
+
measureOperation("storage:setSecureWritesAsync", _Storage.StorageScope.Secure, () => {
|
|
452
|
+
getStorageModule().setSecureWritesAsync(enabled);
|
|
453
|
+
});
|
|
354
454
|
},
|
|
355
455
|
flushSecureWrites: () => {
|
|
356
|
-
flushSecureWrites()
|
|
456
|
+
measureOperation("storage:flushSecureWrites", _Storage.StorageScope.Secure, () => {
|
|
457
|
+
flushSecureWrites();
|
|
458
|
+
});
|
|
357
459
|
},
|
|
358
460
|
setKeychainAccessGroup: group => {
|
|
359
|
-
|
|
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();
|
|
360
482
|
}
|
|
361
483
|
};
|
|
484
|
+
function setWebSecureStorageBackend(_backend) {
|
|
485
|
+
// Native platforms do not use web secure backends.
|
|
486
|
+
}
|
|
487
|
+
function getWebSecureStorageBackend() {
|
|
488
|
+
return undefined;
|
|
489
|
+
}
|
|
362
490
|
function canUseRawBatchPath(item) {
|
|
363
491
|
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
|
|
364
492
|
}
|
|
@@ -376,7 +504,8 @@ function createStorageItem(config) {
|
|
|
376
504
|
const serialize = config.serialize ?? defaultSerialize;
|
|
377
505
|
const deserialize = config.deserialize ?? defaultDeserialize;
|
|
378
506
|
const isMemory = config.scope === _Storage.StorageScope.Memory;
|
|
379
|
-
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;
|
|
380
509
|
const secureAccessControl = config.accessControl;
|
|
381
510
|
const validate = config.validate;
|
|
382
511
|
const onValidationError = config.onValidationError;
|
|
@@ -385,7 +514,7 @@ function createStorageItem(config) {
|
|
|
385
514
|
const expirationTtlMs = expiration?.ttlMs;
|
|
386
515
|
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
387
516
|
const readCache = !isMemory && config.readCache === true;
|
|
388
|
-
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric
|
|
517
|
+
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
|
|
389
518
|
const defaultValue = config.defaultValue;
|
|
390
519
|
const nonMemoryScope = config.scope === _Storage.StorageScope.Disk ? _Storage.StorageScope.Disk : config.scope === _Storage.StorageScope.Secure ? _Storage.StorageScope.Secure : null;
|
|
391
520
|
if (expiration && expiration.ttlMs <= 0) {
|
|
@@ -449,12 +578,12 @@ function createStorageItem(config) {
|
|
|
449
578
|
};
|
|
450
579
|
const writeStoredRaw = rawValue => {
|
|
451
580
|
if (isBiometric) {
|
|
452
|
-
getStorageModule().
|
|
581
|
+
getStorageModule().setSecureBiometricWithLevel(storageKey, rawValue, resolvedBiometricLevel);
|
|
453
582
|
return;
|
|
454
583
|
}
|
|
455
584
|
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
456
585
|
if (coalesceSecureWrites) {
|
|
457
|
-
scheduleSecureWrite(storageKey, rawValue);
|
|
586
|
+
scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
|
|
458
587
|
return;
|
|
459
588
|
}
|
|
460
589
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
@@ -470,7 +599,7 @@ function createStorageItem(config) {
|
|
|
470
599
|
}
|
|
471
600
|
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
472
601
|
if (coalesceSecureWrites) {
|
|
473
|
-
scheduleSecureWrite(storageKey, undefined);
|
|
602
|
+
scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
|
|
474
603
|
return;
|
|
475
604
|
}
|
|
476
605
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
@@ -518,7 +647,7 @@ function createStorageItem(config) {
|
|
|
518
647
|
}
|
|
519
648
|
return resolved;
|
|
520
649
|
};
|
|
521
|
-
const
|
|
650
|
+
const getInternal = () => {
|
|
522
651
|
const raw = readStoredRaw();
|
|
523
652
|
if (!memoryExpiration && raw === lastRaw && hasLastValue) {
|
|
524
653
|
if (!expiration || lastExpiresAt === null) {
|
|
@@ -583,31 +712,52 @@ function createStorageItem(config) {
|
|
|
583
712
|
hasLastValue = true;
|
|
584
713
|
return lastValue;
|
|
585
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
|
+
}));
|
|
586
724
|
const set = valueOrFn => {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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
|
+
});
|
|
593
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
|
+
});
|
|
594
742
|
const deleteItem = () => {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
if (
|
|
598
|
-
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;
|
|
599
752
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
removeStoredRaw();
|
|
753
|
+
removeStoredRaw();
|
|
754
|
+
});
|
|
605
755
|
};
|
|
606
|
-
const hasItem = () => {
|
|
756
|
+
const hasItem = () => measureOperation("item:has", config.scope, () => {
|
|
607
757
|
if (isMemory) return memoryStore.has(storageKey);
|
|
608
758
|
if (isBiometric) return getStorageModule().hasSecureBiometric(storageKey);
|
|
609
759
|
return getStorageModule().has(storageKey, config.scope);
|
|
610
|
-
};
|
|
760
|
+
});
|
|
611
761
|
const subscribe = callback => {
|
|
612
762
|
ensureSubscription();
|
|
613
763
|
listeners.add(callback);
|
|
@@ -624,7 +774,9 @@ function createStorageItem(config) {
|
|
|
624
774
|
};
|
|
625
775
|
const storageItem = {
|
|
626
776
|
get,
|
|
777
|
+
getWithVersion,
|
|
627
778
|
set,
|
|
779
|
+
setIfVersion,
|
|
628
780
|
delete: deleteItem,
|
|
629
781
|
has: hasItem,
|
|
630
782
|
subscribe,
|
|
@@ -638,6 +790,7 @@ function createStorageItem(config) {
|
|
|
638
790
|
_hasExpiration: expiration !== undefined,
|
|
639
791
|
_readCacheEnabled: readCache,
|
|
640
792
|
_isBiometric: isBiometric,
|
|
793
|
+
_defaultValue: defaultValue,
|
|
641
794
|
...(secureAccessControl !== undefined ? {
|
|
642
795
|
_secureAccessControl: secureAccessControl
|
|
643
796
|
} : {}),
|
|
@@ -647,136 +800,141 @@ function createStorageItem(config) {
|
|
|
647
800
|
return storageItem;
|
|
648
801
|
}
|
|
649
802
|
function getBatch(items, scope) {
|
|
650
|
-
(
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
const useRawBatchPath = items.every(item => scope === _Storage.StorageScope.Secure ? canUseSecureRawBatchPath(item) : canUseRawBatchPath(item));
|
|
655
|
-
if (!useRawBatchPath) {
|
|
656
|
-
return items.map(item => item.get());
|
|
657
|
-
}
|
|
658
|
-
const useBatchCache = items.every(item => item._readCacheEnabled === true);
|
|
659
|
-
const rawValues = new Array(items.length);
|
|
660
|
-
const keysToFetch = [];
|
|
661
|
-
const keyIndexes = [];
|
|
662
|
-
items.forEach((item, index) => {
|
|
663
|
-
if (scope === _Storage.StorageScope.Secure) {
|
|
664
|
-
if (hasPendingSecureWrite(item.key)) {
|
|
665
|
-
rawValues[index] = readPendingSecureWrite(item.key);
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
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());
|
|
668
807
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
808
|
+
const useRawBatchPath = items.every(item => scope === _Storage.StorageScope.Secure ? canUseSecureRawBatchPath(item) : canUseRawBatchPath(item));
|
|
809
|
+
if (!useRawBatchPath) {
|
|
810
|
+
return items.map(item => item.get());
|
|
674
811
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
return;
|
|
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
|
+
}
|
|
685
821
|
}
|
|
686
|
-
|
|
687
|
-
|
|
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);
|
|
688
830
|
});
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
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
|
+
});
|
|
694
842
|
}
|
|
695
|
-
return
|
|
696
|
-
|
|
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);
|
|
697
851
|
}
|
|
698
852
|
function setBatch(items, scope) {
|
|
699
|
-
(
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
item,
|
|
703
|
-
value
|
|
704
|
-
}) => item.set(value));
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
if (scope === _Storage.StorageScope.Secure) {
|
|
708
|
-
const secureEntries = items.map(({
|
|
709
|
-
item,
|
|
710
|
-
value
|
|
711
|
-
}) => ({
|
|
712
|
-
item,
|
|
713
|
-
value,
|
|
714
|
-
internal: asInternal(item)
|
|
715
|
-
}));
|
|
716
|
-
const canUseSecureBatchPath = secureEntries.every(({
|
|
717
|
-
internal
|
|
718
|
-
}) => canUseSecureRawBatchPath(internal));
|
|
719
|
-
if (!canUseSecureBatchPath) {
|
|
853
|
+
measureOperation("batch:set", scope, () => {
|
|
854
|
+
(0, _internal.assertBatchScope)(items.map(batchEntry => batchEntry.item), scope);
|
|
855
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
720
856
|
items.forEach(({
|
|
721
857
|
item,
|
|
722
858
|
value
|
|
723
859
|
}) => item.set(value));
|
|
724
860
|
return;
|
|
725
861
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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;
|
|
744
880
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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);
|
|
767
923
|
}
|
|
768
924
|
function removeBatch(items, scope) {
|
|
769
|
-
(
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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);
|
|
780
938
|
}
|
|
781
939
|
function registerMigration(version, migration) {
|
|
782
940
|
if (!Number.isInteger(version) || version <= 0) {
|
|
@@ -788,77 +946,107 @@ function registerMigration(version, migration) {
|
|
|
788
946
|
registeredMigrations.set(version, migration);
|
|
789
947
|
}
|
|
790
948
|
function migrateToLatest(scope = _Storage.StorageScope.Disk) {
|
|
791
|
-
(
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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;
|
|
809
970
|
});
|
|
810
|
-
return appliedVersion;
|
|
811
971
|
}
|
|
812
972
|
function runTransaction(scope, transaction) {
|
|
813
|
-
(
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
const rollback = new Map();
|
|
818
|
-
const rememberRollback = key => {
|
|
819
|
-
if (rollback.has(key)) {
|
|
820
|
-
return;
|
|
821
|
-
}
|
|
822
|
-
rollback.set(key, getRawValue(key, scope));
|
|
823
|
-
};
|
|
824
|
-
const tx = {
|
|
825
|
-
scope,
|
|
826
|
-
getRaw: key => getRawValue(key, scope),
|
|
827
|
-
setRaw: (key, value) => {
|
|
828
|
-
rememberRollback(key);
|
|
829
|
-
setRawValue(key, value, scope);
|
|
830
|
-
},
|
|
831
|
-
removeRaw: key => {
|
|
832
|
-
rememberRollback(key);
|
|
833
|
-
removeRawValue(key, scope);
|
|
834
|
-
},
|
|
835
|
-
getItem: item => {
|
|
836
|
-
(0, _internal.assertBatchScope)([item], scope);
|
|
837
|
-
return item.get();
|
|
838
|
-
},
|
|
839
|
-
setItem: (item, value) => {
|
|
840
|
-
(0, _internal.assertBatchScope)([item], scope);
|
|
841
|
-
rememberRollback(item.key);
|
|
842
|
-
item.set(value);
|
|
843
|
-
},
|
|
844
|
-
removeItem: item => {
|
|
845
|
-
(0, _internal.assertBatchScope)([item], scope);
|
|
846
|
-
rememberRollback(item.key);
|
|
847
|
-
item.delete();
|
|
973
|
+
return measureOperation("transaction:run", scope, () => {
|
|
974
|
+
(0, _internal.assertValidScope)(scope);
|
|
975
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
976
|
+
flushSecureWrites();
|
|
848
977
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
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);
|
|
855
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
|
+
});
|
|
856
1023
|
} else {
|
|
857
|
-
|
|
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
|
+
}
|
|
858
1046
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
}
|
|
1047
|
+
throw error;
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
862
1050
|
}
|
|
863
1051
|
function createSecureAuthStorage(config, options) {
|
|
864
1052
|
const ns = options?.namespace ?? "auth";
|
|
@@ -876,6 +1064,9 @@ function createSecureAuthStorage(config, options) {
|
|
|
876
1064
|
...(itemConfig.biometric !== undefined ? {
|
|
877
1065
|
biometric: itemConfig.biometric
|
|
878
1066
|
} : {}),
|
|
1067
|
+
...(itemConfig.biometricLevel !== undefined ? {
|
|
1068
|
+
biometricLevel: itemConfig.biometricLevel
|
|
1069
|
+
} : {}),
|
|
879
1070
|
...(itemConfig.accessControl !== undefined ? {
|
|
880
1071
|
accessControl: itemConfig.accessControl
|
|
881
1072
|
} : {}),
|