react-native-nitro-storage 0.4.5 → 0.5.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 +254 -945
- package/SECURITY.md +26 -0
- package/docs/api-reference.md +281 -0
- package/docs/batch-transactions-migrations.md +200 -0
- package/docs/benchmarks.md +37 -0
- package/docs/mmkv-migration.md +80 -0
- package/docs/react-hooks.md +113 -0
- package/docs/recipes.md +302 -0
- package/docs/secure-storage.md +190 -0
- package/docs/web-backends.md +141 -0
- package/lib/commonjs/index.js +265 -14
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +220 -11
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/storage-events.js +117 -0
- package/lib/commonjs/storage-events.js.map +1 -0
- package/lib/commonjs/storage-runtime.js.map +1 -1
- package/lib/module/index.js +265 -14
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +220 -11
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/storage-events.js +112 -0
- package/lib/module/storage-events.js.map +1 -0
- package/lib/module/storage-runtime.js.map +1 -1
- package/lib/typescript/index.d.ts +19 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +19 -2
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/storage-events.d.ts +37 -0
- package/lib/typescript/storage-events.d.ts.map +1 -0
- package/lib/typescript/storage-runtime.d.ts +32 -0
- package/lib/typescript/storage-runtime.d.ts.map +1 -1
- package/package.json +25 -11
- package/src/index.ts +601 -14
- package/src/index.web.ts +535 -22
- package/src/storage-events.ts +184 -0
- package/src/storage-runtime.ts +35 -0
package/lib/commonjs/index.js
CHANGED
|
@@ -76,6 +76,7 @@ var _reactNativeNitroModules = require("react-native-nitro-modules");
|
|
|
76
76
|
var _Storage = require("./Storage.types");
|
|
77
77
|
var _internal = require("./internal");
|
|
78
78
|
var _storageRuntime = require("./storage-runtime");
|
|
79
|
+
var _storageEvents = require("./storage-events");
|
|
79
80
|
var _migration = require("./migration");
|
|
80
81
|
var _storageHooks = require("./storage-hooks");
|
|
81
82
|
var _indexeddbBackend = require("./indexeddb-backend");
|
|
@@ -111,8 +112,12 @@ let diskWritesAsync = false;
|
|
|
111
112
|
const pendingSecureWrites = new Map();
|
|
112
113
|
let secureFlushScheduled = false;
|
|
113
114
|
let secureDefaultAccessControl = _Storage.AccessControl.WhenUnlocked;
|
|
115
|
+
const suppressedNativeEvents = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
|
|
114
116
|
let metricsObserver;
|
|
117
|
+
let eventObserver;
|
|
115
118
|
const metricsCounters = new Map();
|
|
119
|
+
const storageEvents = new _storageEvents.StorageEventRegistry();
|
|
120
|
+
const nativeSecureBackend = "platform-secure-storage";
|
|
116
121
|
function recordMetric(operation, scope, durationMs, keysCount = 1) {
|
|
117
122
|
const existing = metricsCounters.get(operation);
|
|
118
123
|
if (!existing) {
|
|
@@ -162,6 +167,23 @@ function hasCachedRawValue(scope, key) {
|
|
|
162
167
|
function clearScopeRawCache(scope) {
|
|
163
168
|
getScopeRawCache(scope).clear();
|
|
164
169
|
}
|
|
170
|
+
function suppressNativeEvent(scope, key) {
|
|
171
|
+
const suppressedEvents = suppressedNativeEvents.get(scope);
|
|
172
|
+
suppressedEvents.set(key, (suppressedEvents.get(key) ?? 0) + 1);
|
|
173
|
+
}
|
|
174
|
+
function consumeSuppressedNativeEvent(scope, key) {
|
|
175
|
+
const suppressedEvents = suppressedNativeEvents.get(scope);
|
|
176
|
+
const count = suppressedEvents.get(key);
|
|
177
|
+
if (count === undefined) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
if (count <= 1) {
|
|
181
|
+
suppressedEvents.delete(key);
|
|
182
|
+
} else {
|
|
183
|
+
suppressedEvents.set(key, count - 1);
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
165
187
|
function notifyKeyListeners(registry, key) {
|
|
166
188
|
const listeners = registry.get(key);
|
|
167
189
|
if (listeners) {
|
|
@@ -195,6 +217,52 @@ function addKeyListener(registry, key, listener) {
|
|
|
195
217
|
}
|
|
196
218
|
};
|
|
197
219
|
}
|
|
220
|
+
function getEventRawValue(scope, key) {
|
|
221
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
222
|
+
const value = memoryStore.get(key);
|
|
223
|
+
return typeof value === "string" ? value : undefined;
|
|
224
|
+
}
|
|
225
|
+
return getRawValue(key, scope);
|
|
226
|
+
}
|
|
227
|
+
function createKeyChange(scope, key, oldValue, newValue, operation, source) {
|
|
228
|
+
return {
|
|
229
|
+
type: "key",
|
|
230
|
+
scope,
|
|
231
|
+
key,
|
|
232
|
+
oldValue,
|
|
233
|
+
newValue,
|
|
234
|
+
operation,
|
|
235
|
+
source
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function hasStorageChangeObservers(scope) {
|
|
239
|
+
return storageEvents.hasListeners(scope) || eventObserver !== undefined;
|
|
240
|
+
}
|
|
241
|
+
function emitKeyChange(scope, key, oldValue, newValue, operation, source) {
|
|
242
|
+
if (source === "native" && operation !== "external" && scope !== _Storage.StorageScope.Memory && scopedUnsubscribers.has(scope)) {
|
|
243
|
+
suppressNativeEvent(scope, key);
|
|
244
|
+
}
|
|
245
|
+
const event = createKeyChange(scope, key, oldValue, newValue, operation, source);
|
|
246
|
+
storageEvents.emitKey(event);
|
|
247
|
+
eventObserver?.(event);
|
|
248
|
+
}
|
|
249
|
+
function emitBatchChange(scope, operation, source, changes) {
|
|
250
|
+
if (changes.length === 0) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (source === "native" && operation !== "external" && scope !== _Storage.StorageScope.Memory && scopedUnsubscribers.has(scope)) {
|
|
254
|
+
changes.forEach(change => suppressNativeEvent(scope, change.key));
|
|
255
|
+
}
|
|
256
|
+
const event = {
|
|
257
|
+
type: "batch",
|
|
258
|
+
scope,
|
|
259
|
+
operation,
|
|
260
|
+
source,
|
|
261
|
+
changes
|
|
262
|
+
};
|
|
263
|
+
storageEvents.emitBatch(event);
|
|
264
|
+
eventObserver?.(event);
|
|
265
|
+
}
|
|
198
266
|
function readPendingSecureWrite(key) {
|
|
199
267
|
return pendingSecureWrites.get(key)?.value;
|
|
200
268
|
}
|
|
@@ -331,14 +399,19 @@ function ensureNativeScopeSubscription(scope) {
|
|
|
331
399
|
notifyAllListeners(getScopedListeners(scope));
|
|
332
400
|
return;
|
|
333
401
|
}
|
|
402
|
+
const oldValue = readCachedRawValue(scope, key);
|
|
334
403
|
cacheRawValue(scope, key, value);
|
|
335
404
|
notifyKeyListeners(getScopedListeners(scope), key);
|
|
405
|
+
if (consumeSuppressedNativeEvent(scope, key)) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
emitKeyChange(scope, key, oldValue, value, "external", "native");
|
|
336
409
|
});
|
|
337
|
-
scopedUnsubscribers.set(scope, unsubscribe);
|
|
410
|
+
scopedUnsubscribers.set(scope, typeof unsubscribe === "function" ? unsubscribe : () => {});
|
|
338
411
|
}
|
|
339
412
|
function maybeCleanupNativeScopeSubscription(scope) {
|
|
340
413
|
const listeners = getScopedListeners(scope);
|
|
341
|
-
if (listeners.size > 0) {
|
|
414
|
+
if (listeners.size > 0 || storageEvents.hasListeners(scope) || eventObserver !== undefined) {
|
|
342
415
|
return;
|
|
343
416
|
}
|
|
344
417
|
const unsubscribe = scopedUnsubscribers.get(scope);
|
|
@@ -364,15 +437,18 @@ function getRawValue(key, scope) {
|
|
|
364
437
|
}
|
|
365
438
|
function setRawValue(key, value, scope) {
|
|
366
439
|
(0, _internal.assertValidScope)(scope);
|
|
440
|
+
const oldValue = scope === _Storage.StorageScope.Memory ? getEventRawValue(scope, key) : undefined;
|
|
367
441
|
if (scope === _Storage.StorageScope.Memory) {
|
|
368
442
|
memoryStore.set(key, value);
|
|
369
443
|
notifyKeyListeners(memoryListeners, key);
|
|
444
|
+
emitKeyChange(scope, key, oldValue, value, "set", "memory");
|
|
370
445
|
return;
|
|
371
446
|
}
|
|
372
447
|
if (scope === _Storage.StorageScope.Disk) {
|
|
373
448
|
cacheRawValue(scope, key, value);
|
|
374
449
|
if (diskWritesAsync) {
|
|
375
450
|
scheduleDiskWrite(key, value);
|
|
451
|
+
emitKeyChange(scope, key, oldValue, value, "set", "native");
|
|
376
452
|
return;
|
|
377
453
|
}
|
|
378
454
|
flushDiskWrites();
|
|
@@ -385,18 +461,22 @@ function setRawValue(key, value, scope) {
|
|
|
385
461
|
}
|
|
386
462
|
getStorageModule().set(key, value, scope);
|
|
387
463
|
cacheRawValue(scope, key, value);
|
|
464
|
+
emitKeyChange(scope, key, oldValue, value, "set", "native");
|
|
388
465
|
}
|
|
389
466
|
function removeRawValue(key, scope) {
|
|
390
467
|
(0, _internal.assertValidScope)(scope);
|
|
468
|
+
const oldValue = getEventRawValue(scope, key);
|
|
391
469
|
if (scope === _Storage.StorageScope.Memory) {
|
|
392
470
|
memoryStore.delete(key);
|
|
393
471
|
notifyKeyListeners(memoryListeners, key);
|
|
472
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "memory");
|
|
394
473
|
return;
|
|
395
474
|
}
|
|
396
475
|
if (scope === _Storage.StorageScope.Disk) {
|
|
397
476
|
cacheRawValue(scope, key, undefined);
|
|
398
477
|
if (diskWritesAsync) {
|
|
399
478
|
scheduleDiskWrite(key, undefined);
|
|
479
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "native");
|
|
400
480
|
return;
|
|
401
481
|
}
|
|
402
482
|
flushDiskWrites();
|
|
@@ -408,6 +488,7 @@ function removeRawValue(key, scope) {
|
|
|
408
488
|
}
|
|
409
489
|
getStorageModule().remove(key, scope);
|
|
410
490
|
cacheRawValue(scope, key, undefined);
|
|
491
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "native");
|
|
411
492
|
}
|
|
412
493
|
function readMigrationVersion(scope) {
|
|
413
494
|
const raw = getRawValue(_internal.MIGRATION_VERSION_KEY, scope);
|
|
@@ -421,11 +502,62 @@ function writeMigrationVersion(scope, version) {
|
|
|
421
502
|
setRawValue(_internal.MIGRATION_VERSION_KEY, String(version), scope);
|
|
422
503
|
}
|
|
423
504
|
const storage = exports.storage = {
|
|
505
|
+
subscribe: (scope, listener) => {
|
|
506
|
+
(0, _internal.assertValidScope)(scope);
|
|
507
|
+
if (scope !== _Storage.StorageScope.Memory) {
|
|
508
|
+
ensureNativeScopeSubscription(scope);
|
|
509
|
+
const unsubscribe = storageEvents.subscribe(scope, listener);
|
|
510
|
+
return () => {
|
|
511
|
+
unsubscribe();
|
|
512
|
+
maybeCleanupNativeScopeSubscription(scope);
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
return storageEvents.subscribe(scope, listener);
|
|
516
|
+
},
|
|
517
|
+
subscribeKey: (scope, key, listener) => {
|
|
518
|
+
(0, _internal.assertValidScope)(scope);
|
|
519
|
+
if (scope !== _Storage.StorageScope.Memory) {
|
|
520
|
+
ensureNativeScopeSubscription(scope);
|
|
521
|
+
const unsubscribe = storageEvents.subscribeKey(scope, key, listener);
|
|
522
|
+
return () => {
|
|
523
|
+
unsubscribe();
|
|
524
|
+
maybeCleanupNativeScopeSubscription(scope);
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
return storageEvents.subscribeKey(scope, key, listener);
|
|
528
|
+
},
|
|
529
|
+
subscribePrefix: (scope, prefix, listener) => {
|
|
530
|
+
(0, _internal.assertValidScope)(scope);
|
|
531
|
+
if (scope !== _Storage.StorageScope.Memory) {
|
|
532
|
+
ensureNativeScopeSubscription(scope);
|
|
533
|
+
const unsubscribe = storageEvents.subscribePrefix(scope, prefix, listener);
|
|
534
|
+
return () => {
|
|
535
|
+
unsubscribe();
|
|
536
|
+
maybeCleanupNativeScopeSubscription(scope);
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
return storageEvents.subscribePrefix(scope, prefix, listener);
|
|
540
|
+
},
|
|
541
|
+
subscribeNamespace: (namespace, scope, listener) => {
|
|
542
|
+
return storage.subscribePrefix(scope, (0, _internal.prefixKey)(namespace, ""), listener);
|
|
543
|
+
},
|
|
544
|
+
setEventObserver: observer => {
|
|
545
|
+
eventObserver = observer;
|
|
546
|
+
if (observer) {
|
|
547
|
+
ensureNativeScopeSubscription(_Storage.StorageScope.Disk);
|
|
548
|
+
ensureNativeScopeSubscription(_Storage.StorageScope.Secure);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
maybeCleanupNativeScopeSubscription(_Storage.StorageScope.Disk);
|
|
552
|
+
maybeCleanupNativeScopeSubscription(_Storage.StorageScope.Secure);
|
|
553
|
+
},
|
|
424
554
|
clear: scope => {
|
|
425
555
|
measureOperation("storage:clear", scope, () => {
|
|
556
|
+
const previousValues = hasStorageChangeObservers(scope) ? storage.getAll(scope) : {};
|
|
426
557
|
if (scope === _Storage.StorageScope.Memory) {
|
|
427
558
|
memoryStore.clear();
|
|
428
559
|
notifyAllListeners(memoryListeners);
|
|
560
|
+
emitBatchChange(scope, "clear", "memory", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clear", "memory")));
|
|
429
561
|
return;
|
|
430
562
|
}
|
|
431
563
|
if (scope === _Storage.StorageScope.Disk) {
|
|
@@ -438,6 +570,7 @@ const storage = exports.storage = {
|
|
|
438
570
|
}
|
|
439
571
|
clearScopeRawCache(scope);
|
|
440
572
|
getStorageModule().clear(scope);
|
|
573
|
+
emitBatchChange(scope, "clear", "native", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clear", "native")));
|
|
441
574
|
});
|
|
442
575
|
},
|
|
443
576
|
clearAll: () => {
|
|
@@ -451,15 +584,26 @@ const storage = exports.storage = {
|
|
|
451
584
|
measureOperation("storage:clearNamespace", scope, () => {
|
|
452
585
|
(0, _internal.assertValidScope)(scope);
|
|
453
586
|
if (scope === _Storage.StorageScope.Memory) {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
587
|
+
const affectedKeys = Array.from(memoryStore.keys()).filter(key => (0, _internal.isNamespaced)(key, namespace));
|
|
588
|
+
const previousValues = affectedKeys.map(key => ({
|
|
589
|
+
key,
|
|
590
|
+
value: getEventRawValue(scope, key)
|
|
591
|
+
}));
|
|
592
|
+
if (affectedKeys.length === 0) {
|
|
593
|
+
return;
|
|
458
594
|
}
|
|
459
|
-
|
|
595
|
+
affectedKeys.forEach(key => {
|
|
596
|
+
memoryStore.delete(key);
|
|
597
|
+
});
|
|
598
|
+
affectedKeys.forEach(key => notifyKeyListeners(memoryListeners, key));
|
|
599
|
+
emitBatchChange(scope, "clearNamespace", "memory", previousValues.map(({
|
|
600
|
+
key,
|
|
601
|
+
value
|
|
602
|
+
}) => createKeyChange(scope, key, value, undefined, "clearNamespace", "memory")));
|
|
460
603
|
return;
|
|
461
604
|
}
|
|
462
605
|
const keyPrefix = (0, _internal.prefixKey)(namespace, "");
|
|
606
|
+
const previousValues = hasStorageChangeObservers(scope) ? storage.getByPrefix(keyPrefix, scope) : {};
|
|
463
607
|
if (scope === _Storage.StorageScope.Disk) {
|
|
464
608
|
flushDiskWrites();
|
|
465
609
|
}
|
|
@@ -473,6 +617,7 @@ const storage = exports.storage = {
|
|
|
473
617
|
}
|
|
474
618
|
}
|
|
475
619
|
getStorageModule().removeByPrefix(keyPrefix, scope);
|
|
620
|
+
emitBatchChange(scope, "clearNamespace", "native", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clearNamespace", "native")));
|
|
476
621
|
});
|
|
477
622
|
},
|
|
478
623
|
clearBiometric: () => {
|
|
@@ -522,7 +667,7 @@ const storage = exports.storage = {
|
|
|
522
667
|
if (scope === _Storage.StorageScope.Secure) {
|
|
523
668
|
flushSecureWrites();
|
|
524
669
|
}
|
|
525
|
-
return getStorageModule().getKeysByPrefix(prefix, scope);
|
|
670
|
+
return getStorageModule().getKeysByPrefix(prefix, scope) ?? [];
|
|
526
671
|
});
|
|
527
672
|
},
|
|
528
673
|
getByPrefix: (prefix, scope) => {
|
|
@@ -547,7 +692,7 @@ const storage = exports.storage = {
|
|
|
547
692
|
if (scope === _Storage.StorageScope.Secure) {
|
|
548
693
|
flushSecureWrites();
|
|
549
694
|
}
|
|
550
|
-
const values = getStorageModule().getBatch(keys, scope);
|
|
695
|
+
const values = getStorageModule().getBatch(keys, scope) ?? [];
|
|
551
696
|
keys.forEach((key, idx) => {
|
|
552
697
|
const value = (0, _internal.decodeNativeBatchValue)(values[idx]);
|
|
553
698
|
if (value !== undefined) {
|
|
@@ -562,9 +707,10 @@ const storage = exports.storage = {
|
|
|
562
707
|
(0, _internal.assertValidScope)(scope);
|
|
563
708
|
const result = {};
|
|
564
709
|
if (scope === _Storage.StorageScope.Memory) {
|
|
565
|
-
memoryStore.
|
|
710
|
+
for (const key of memoryStore.keys()) {
|
|
711
|
+
const value = memoryStore.get(key);
|
|
566
712
|
if (typeof value === "string") result[key] = value;
|
|
567
|
-
}
|
|
713
|
+
}
|
|
568
714
|
return result;
|
|
569
715
|
}
|
|
570
716
|
if (scope === _Storage.StorageScope.Disk) {
|
|
@@ -573,9 +719,9 @@ const storage = exports.storage = {
|
|
|
573
719
|
if (scope === _Storage.StorageScope.Secure) {
|
|
574
720
|
flushSecureWrites();
|
|
575
721
|
}
|
|
576
|
-
const keys = getStorageModule().getAllKeys(scope);
|
|
722
|
+
const keys = getStorageModule().getAllKeys(scope) ?? [];
|
|
577
723
|
if (keys.length === 0) return result;
|
|
578
|
-
const values = getStorageModule().getBatch(keys, scope);
|
|
724
|
+
const values = getStorageModule().getBatch(keys, scope) ?? [];
|
|
579
725
|
keys.forEach((key, idx) => {
|
|
580
726
|
const val = (0, _internal.decodeNativeBatchValue)(values[idx]);
|
|
581
727
|
if (val !== undefined) result[key] = val;
|
|
@@ -583,6 +729,9 @@ const storage = exports.storage = {
|
|
|
583
729
|
return result;
|
|
584
730
|
});
|
|
585
731
|
},
|
|
732
|
+
export: scope => {
|
|
733
|
+
return measureOperation("storage:export", scope, () => storage.getAll(scope));
|
|
734
|
+
},
|
|
586
735
|
size: scope => {
|
|
587
736
|
return measureOperation("storage:size", scope, () => {
|
|
588
737
|
(0, _internal.assertValidScope)(scope);
|
|
@@ -654,7 +803,7 @@ const storage = exports.storage = {
|
|
|
654
803
|
platform: "native",
|
|
655
804
|
backend: {
|
|
656
805
|
disk: "platform-preferences",
|
|
657
|
-
secure:
|
|
806
|
+
secure: nativeSecureBackend
|
|
658
807
|
},
|
|
659
808
|
writeBuffering: {
|
|
660
809
|
disk: true,
|
|
@@ -662,6 +811,55 @@ const storage = exports.storage = {
|
|
|
662
811
|
},
|
|
663
812
|
errorClassification: true
|
|
664
813
|
}),
|
|
814
|
+
getSecurityCapabilities: () => ({
|
|
815
|
+
platform: "native",
|
|
816
|
+
secureStorage: {
|
|
817
|
+
backend: nativeSecureBackend,
|
|
818
|
+
encrypted: "available",
|
|
819
|
+
accessControl: "unknown",
|
|
820
|
+
keychainAccessGroup: "unknown",
|
|
821
|
+
hardwareBacked: "unknown"
|
|
822
|
+
},
|
|
823
|
+
biometric: {
|
|
824
|
+
storage: "unknown",
|
|
825
|
+
prompt: "unknown",
|
|
826
|
+
biometryOnly: "unknown",
|
|
827
|
+
biometryOrPasscode: "unknown"
|
|
828
|
+
},
|
|
829
|
+
metadata: {
|
|
830
|
+
perKey: true,
|
|
831
|
+
listsWithoutValues: true,
|
|
832
|
+
persistsTimestamps: false
|
|
833
|
+
}
|
|
834
|
+
}),
|
|
835
|
+
getSecureMetadata: key => {
|
|
836
|
+
return measureOperation("storage:getSecureMetadata", _Storage.StorageScope.Secure, () => {
|
|
837
|
+
flushSecureWrites();
|
|
838
|
+
const storageModule = getStorageModule();
|
|
839
|
+
const biometricProtected = storageModule.hasSecureBiometric(key);
|
|
840
|
+
const exists = biometricProtected || storageModule.has(key, _Storage.StorageScope.Secure);
|
|
841
|
+
let kind = "missing";
|
|
842
|
+
if (exists) {
|
|
843
|
+
kind = biometricProtected ? "biometric" : "secure";
|
|
844
|
+
}
|
|
845
|
+
return {
|
|
846
|
+
key,
|
|
847
|
+
exists,
|
|
848
|
+
kind,
|
|
849
|
+
backend: nativeSecureBackend,
|
|
850
|
+
encrypted: "available",
|
|
851
|
+
hardwareBacked: "unknown",
|
|
852
|
+
biometricProtected,
|
|
853
|
+
valueExposed: false
|
|
854
|
+
};
|
|
855
|
+
});
|
|
856
|
+
},
|
|
857
|
+
getAllSecureMetadata: () => {
|
|
858
|
+
return measureOperation("storage:getAllSecureMetadata", _Storage.StorageScope.Secure, () => {
|
|
859
|
+
flushSecureWrites();
|
|
860
|
+
return getStorageModule().getAllKeys(_Storage.StorageScope.Secure).map(key => storage.getSecureMetadata(key));
|
|
861
|
+
});
|
|
862
|
+
},
|
|
665
863
|
getString: (key, scope) => {
|
|
666
864
|
return measureOperation("storage:getString", scope, () => {
|
|
667
865
|
return getRawValue(key, scope);
|
|
@@ -683,11 +881,13 @@ const storage = exports.storage = {
|
|
|
683
881
|
(0, _internal.assertValidScope)(scope);
|
|
684
882
|
if (keys.length === 0) return;
|
|
685
883
|
const values = keys.map(k => data[k]);
|
|
884
|
+
const changes = keys.map((key, index) => createKeyChange(scope, key, getEventRawValue(scope, key), values[index], "import", scope === _Storage.StorageScope.Memory ? "memory" : "native"));
|
|
686
885
|
if (scope === _Storage.StorageScope.Memory) {
|
|
687
886
|
keys.forEach((key, index) => {
|
|
688
887
|
memoryStore.set(key, values[index]);
|
|
689
888
|
});
|
|
690
889
|
keys.forEach(key => notifyKeyListeners(memoryListeners, key));
|
|
890
|
+
emitBatchChange(scope, "import", "memory", changes);
|
|
691
891
|
return;
|
|
692
892
|
}
|
|
693
893
|
if (scope === _Storage.StorageScope.Secure) {
|
|
@@ -696,6 +896,7 @@ const storage = exports.storage = {
|
|
|
696
896
|
}
|
|
697
897
|
getStorageModule().setBatch(keys, values, scope);
|
|
698
898
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
899
|
+
emitBatchChange(scope, "import", "native", changes);
|
|
699
900
|
}, keys.length);
|
|
700
901
|
}
|
|
701
902
|
};
|
|
@@ -816,20 +1017,24 @@ function createStorageItem(config) {
|
|
|
816
1017
|
return raw;
|
|
817
1018
|
};
|
|
818
1019
|
const writeStoredRaw = rawValue => {
|
|
1020
|
+
const oldValue = undefined;
|
|
819
1021
|
if (isBiometric) {
|
|
820
1022
|
getStorageModule().setSecureBiometricWithLevel(storageKey, rawValue, resolvedBiometricLevel);
|
|
1023
|
+
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "native");
|
|
821
1024
|
return;
|
|
822
1025
|
}
|
|
823
1026
|
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
824
1027
|
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
825
1028
|
if (coalesceDiskWrites || diskWritesAsync) {
|
|
826
1029
|
scheduleDiskWrite(storageKey, rawValue);
|
|
1030
|
+
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "native");
|
|
827
1031
|
return;
|
|
828
1032
|
}
|
|
829
1033
|
clearPendingDiskWrite(storageKey);
|
|
830
1034
|
}
|
|
831
1035
|
if (coalesceSecureWrites) {
|
|
832
1036
|
scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
|
|
1037
|
+
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "native");
|
|
833
1038
|
return;
|
|
834
1039
|
}
|
|
835
1040
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
@@ -837,36 +1042,44 @@ function createStorageItem(config) {
|
|
|
837
1042
|
getStorageModule().setSecureAccessControl(secureAccessControl ?? secureDefaultAccessControl);
|
|
838
1043
|
}
|
|
839
1044
|
getStorageModule().set(storageKey, rawValue, config.scope);
|
|
1045
|
+
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "native");
|
|
840
1046
|
};
|
|
841
1047
|
const removeStoredRaw = () => {
|
|
1048
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
842
1049
|
if (isBiometric) {
|
|
843
1050
|
getStorageModule().deleteSecureBiometric(storageKey);
|
|
1051
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "native");
|
|
844
1052
|
return;
|
|
845
1053
|
}
|
|
846
1054
|
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
847
1055
|
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
848
1056
|
if (coalesceDiskWrites || diskWritesAsync) {
|
|
849
1057
|
scheduleDiskWrite(storageKey, undefined);
|
|
1058
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "native");
|
|
850
1059
|
return;
|
|
851
1060
|
}
|
|
852
1061
|
clearPendingDiskWrite(storageKey);
|
|
853
1062
|
}
|
|
854
1063
|
if (coalesceSecureWrites) {
|
|
855
1064
|
scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
|
|
1065
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "native");
|
|
856
1066
|
return;
|
|
857
1067
|
}
|
|
858
1068
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
859
1069
|
clearPendingSecureWrite(storageKey);
|
|
860
1070
|
}
|
|
861
1071
|
getStorageModule().remove(storageKey, config.scope);
|
|
1072
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "native");
|
|
862
1073
|
};
|
|
863
1074
|
const writeValueWithoutValidation = value => {
|
|
864
1075
|
if (isMemory) {
|
|
1076
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
865
1077
|
if (memoryExpiration) {
|
|
866
1078
|
memoryExpiration.set(storageKey, Date.now() + (expirationTtlMs ?? 0));
|
|
867
1079
|
}
|
|
868
1080
|
memoryStore.set(storageKey, value);
|
|
869
1081
|
notifyKeyListeners(memoryListeners, storageKey);
|
|
1082
|
+
emitKeyChange(config.scope, storageKey, oldValue, typeof value === "string" ? value : undefined, "set", "memory");
|
|
870
1083
|
return;
|
|
871
1084
|
}
|
|
872
1085
|
const serialized = serialize(value);
|
|
@@ -998,11 +1211,13 @@ function createStorageItem(config) {
|
|
|
998
1211
|
measureOperation("item:delete", config.scope, () => {
|
|
999
1212
|
invalidateParsedCache();
|
|
1000
1213
|
if (isMemory) {
|
|
1214
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1001
1215
|
if (memoryExpiration) {
|
|
1002
1216
|
memoryExpiration.delete(storageKey);
|
|
1003
1217
|
}
|
|
1004
1218
|
memoryStore.delete(storageKey);
|
|
1005
1219
|
notifyKeyListeners(memoryListeners, storageKey);
|
|
1220
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "memory");
|
|
1006
1221
|
return;
|
|
1007
1222
|
}
|
|
1008
1223
|
removeStoredRaw();
|
|
@@ -1039,6 +1254,22 @@ function createStorageItem(config) {
|
|
|
1039
1254
|
}
|
|
1040
1255
|
};
|
|
1041
1256
|
};
|
|
1257
|
+
const subscribeSelector = (selector, listener, options = {}) => {
|
|
1258
|
+
const isEqual = options.isEqual ?? Object.is;
|
|
1259
|
+
let currentValue = selector(getInternal());
|
|
1260
|
+
if (options.fireImmediately === true) {
|
|
1261
|
+
listener(currentValue, currentValue);
|
|
1262
|
+
}
|
|
1263
|
+
return subscribe(() => {
|
|
1264
|
+
const nextValue = selector(getInternal());
|
|
1265
|
+
if (isEqual(currentValue, nextValue)) {
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
const previousValue = currentValue;
|
|
1269
|
+
currentValue = nextValue;
|
|
1270
|
+
listener(nextValue, previousValue);
|
|
1271
|
+
});
|
|
1272
|
+
};
|
|
1042
1273
|
const storageItem = {
|
|
1043
1274
|
get,
|
|
1044
1275
|
getWithVersion,
|
|
@@ -1047,6 +1278,7 @@ function createStorageItem(config) {
|
|
|
1047
1278
|
delete: deleteItem,
|
|
1048
1279
|
has: hasItem,
|
|
1049
1280
|
subscribe,
|
|
1281
|
+
subscribeSelector,
|
|
1050
1282
|
serialize,
|
|
1051
1283
|
deserialize,
|
|
1052
1284
|
_triggerListeners: () => {
|
|
@@ -1148,6 +1380,10 @@ function setBatch(items, scope) {
|
|
|
1148
1380
|
}) => item.set(value));
|
|
1149
1381
|
return;
|
|
1150
1382
|
}
|
|
1383
|
+
const changes = items.map(({
|
|
1384
|
+
item,
|
|
1385
|
+
value
|
|
1386
|
+
}) => createKeyChange(scope, item.key, getEventRawValue(scope, item.key), typeof value === "string" ? value : undefined, "setBatch", "memory"));
|
|
1151
1387
|
|
|
1152
1388
|
// Atomic write: update all values in memoryStore, invalidate caches, then batch-notify
|
|
1153
1389
|
items.forEach(({
|
|
@@ -1160,6 +1396,7 @@ function setBatch(items, scope) {
|
|
|
1160
1396
|
items.forEach(({
|
|
1161
1397
|
item
|
|
1162
1398
|
}) => notifyKeyListeners(memoryListeners, item.key));
|
|
1399
|
+
emitBatchChange(scope, "setBatch", "memory", changes);
|
|
1163
1400
|
return;
|
|
1164
1401
|
}
|
|
1165
1402
|
if (scope === _Storage.StorageScope.Secure) {
|
|
@@ -1183,6 +1420,10 @@ function setBatch(items, scope) {
|
|
|
1183
1420
|
}
|
|
1184
1421
|
flushSecureWrites();
|
|
1185
1422
|
const storageModule = getStorageModule();
|
|
1423
|
+
const keys = secureEntries.map(({
|
|
1424
|
+
item
|
|
1425
|
+
}) => item.key);
|
|
1426
|
+
const oldValues = hasStorageChangeObservers(scope) ? storageModule.getBatch(keys, scope) ?? [] : [];
|
|
1186
1427
|
const groupedByAccessControl = new Map();
|
|
1187
1428
|
secureEntries.forEach(({
|
|
1188
1429
|
item,
|
|
@@ -1206,6 +1447,10 @@ function setBatch(items, scope) {
|
|
|
1206
1447
|
storageModule.setBatch(group.keys, group.values, scope);
|
|
1207
1448
|
group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
|
|
1208
1449
|
});
|
|
1450
|
+
emitBatchChange(scope, "setBatch", "native", secureEntries.map(({
|
|
1451
|
+
item,
|
|
1452
|
+
value
|
|
1453
|
+
}, index) => createKeyChange(scope, item.key, oldValues[index], item.serialize(value), "setBatch", "native")));
|
|
1209
1454
|
return;
|
|
1210
1455
|
}
|
|
1211
1456
|
flushDiskWrites();
|
|
@@ -1221,15 +1466,19 @@ function setBatch(items, scope) {
|
|
|
1221
1466
|
}
|
|
1222
1467
|
const keys = items.map(entry => entry.item.key);
|
|
1223
1468
|
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
1469
|
+
const oldValues = hasStorageChangeObservers(scope) ? getStorageModule().getBatch(keys, scope) ?? [] : [];
|
|
1224
1470
|
getStorageModule().setBatch(keys, values, scope);
|
|
1225
1471
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
1472
|
+
emitBatchChange(scope, "setBatch", "native", keys.map((key, index) => createKeyChange(scope, key, oldValues[index], values[index], "setBatch", "native")));
|
|
1226
1473
|
}, items.length);
|
|
1227
1474
|
}
|
|
1228
1475
|
function removeBatch(items, scope) {
|
|
1229
1476
|
measureOperation("batch:remove", scope, () => {
|
|
1230
1477
|
(0, _internal.assertBatchScope)(items, scope);
|
|
1231
1478
|
if (scope === _Storage.StorageScope.Memory) {
|
|
1479
|
+
const changes = items.map(item => createKeyChange(scope, item.key, getEventRawValue(scope, item.key), undefined, "removeBatch", "memory"));
|
|
1232
1480
|
items.forEach(item => item.delete());
|
|
1481
|
+
emitBatchChange(scope, "removeBatch", "memory", changes);
|
|
1233
1482
|
return;
|
|
1234
1483
|
}
|
|
1235
1484
|
const keys = items.map(item => item.key);
|
|
@@ -1239,8 +1488,10 @@ function removeBatch(items, scope) {
|
|
|
1239
1488
|
if (scope === _Storage.StorageScope.Secure) {
|
|
1240
1489
|
flushSecureWrites();
|
|
1241
1490
|
}
|
|
1491
|
+
const oldValues = hasStorageChangeObservers(scope) ? getStorageModule().getBatch(keys, scope) ?? [] : [];
|
|
1242
1492
|
getStorageModule().removeBatch(keys, scope);
|
|
1243
1493
|
keys.forEach(key => cacheRawValue(scope, key, undefined));
|
|
1494
|
+
emitBatchChange(scope, "removeBatch", "native", keys.map((key, index) => createKeyChange(scope, key, oldValues[index], undefined, "removeBatch", "native")));
|
|
1244
1495
|
}, items.length);
|
|
1245
1496
|
}
|
|
1246
1497
|
function registerMigration(version, migration) {
|