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/src/index.ts
CHANGED
|
@@ -21,18 +21,39 @@ import type {
|
|
|
21
21
|
import {
|
|
22
22
|
getStorageErrorCode,
|
|
23
23
|
isLockedStorageErrorCode,
|
|
24
|
+
type SecureStorageMetadata,
|
|
25
|
+
type SecurityCapabilities,
|
|
24
26
|
type StorageCapabilities,
|
|
25
27
|
type StorageErrorCode,
|
|
26
28
|
} from "./storage-runtime";
|
|
29
|
+
import {
|
|
30
|
+
StorageEventRegistry,
|
|
31
|
+
type StorageBatchChangeEvent,
|
|
32
|
+
type StorageChangeEvent,
|
|
33
|
+
type StorageChangeOperation,
|
|
34
|
+
type StorageChangeSource,
|
|
35
|
+
type StorageEventListener,
|
|
36
|
+
type StorageKeyChangeEvent,
|
|
37
|
+
} from "./storage-events";
|
|
27
38
|
|
|
28
39
|
export { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
|
|
29
40
|
export type { Storage } from "./Storage.nitro";
|
|
30
41
|
export { migrateFromMMKV } from "./migration";
|
|
31
42
|
export {
|
|
32
43
|
getStorageErrorCode,
|
|
44
|
+
type SecureStorageMetadata,
|
|
45
|
+
type SecurityCapabilities,
|
|
33
46
|
type StorageCapabilities,
|
|
34
47
|
type StorageErrorCode,
|
|
35
48
|
} from "./storage-runtime";
|
|
49
|
+
export type {
|
|
50
|
+
StorageBatchChangeEvent,
|
|
51
|
+
StorageChangeEvent,
|
|
52
|
+
StorageChangeOperation,
|
|
53
|
+
StorageChangeSource,
|
|
54
|
+
StorageEventListener,
|
|
55
|
+
StorageKeyChangeEvent,
|
|
56
|
+
} from "./storage-events";
|
|
36
57
|
export type {
|
|
37
58
|
WebStorageBackend,
|
|
38
59
|
WebStorageChangeEvent,
|
|
@@ -61,6 +82,14 @@ export type StorageMetricSummary = {
|
|
|
61
82
|
avgDurationMs: number;
|
|
62
83
|
maxDurationMs: number;
|
|
63
84
|
};
|
|
85
|
+
export type StorageSelectorListener<TSelected> = (
|
|
86
|
+
value: TSelected,
|
|
87
|
+
previousValue: TSelected,
|
|
88
|
+
) => void;
|
|
89
|
+
export type StorageSelectorSubscribeOptions<TSelected> = {
|
|
90
|
+
isEqual?: (previousValue: TSelected, nextValue: TSelected) => boolean;
|
|
91
|
+
fireImmediately?: boolean;
|
|
92
|
+
};
|
|
64
93
|
export type MigrationContext = {
|
|
65
94
|
scope: StorageScope;
|
|
66
95
|
getRaw: (key: string) => string | undefined;
|
|
@@ -157,11 +186,18 @@ let diskWritesAsync = false;
|
|
|
157
186
|
const pendingSecureWrites = new Map<string, PendingSecureWrite>();
|
|
158
187
|
let secureFlushScheduled = false;
|
|
159
188
|
let secureDefaultAccessControl: AccessControl = AccessControl.WhenUnlocked;
|
|
189
|
+
const suppressedNativeEvents = new Map<NonMemoryScope, Map<string, number>>([
|
|
190
|
+
[StorageScope.Disk, new Map()],
|
|
191
|
+
[StorageScope.Secure, new Map()],
|
|
192
|
+
]);
|
|
160
193
|
let metricsObserver: StorageMetricsObserver | undefined;
|
|
194
|
+
let eventObserver: StorageEventListener | undefined;
|
|
161
195
|
const metricsCounters = new Map<
|
|
162
196
|
string,
|
|
163
197
|
{ count: number; totalDurationMs: number; maxDurationMs: number }
|
|
164
198
|
>();
|
|
199
|
+
const storageEvents = new StorageEventRegistry();
|
|
200
|
+
const nativeSecureBackend = "platform-secure-storage";
|
|
165
201
|
|
|
166
202
|
function recordMetric(
|
|
167
203
|
operation: string,
|
|
@@ -240,6 +276,28 @@ function clearScopeRawCache(scope: NonMemoryScope): void {
|
|
|
240
276
|
getScopeRawCache(scope).clear();
|
|
241
277
|
}
|
|
242
278
|
|
|
279
|
+
function suppressNativeEvent(scope: NonMemoryScope, key: string): void {
|
|
280
|
+
const suppressedEvents = suppressedNativeEvents.get(scope)!;
|
|
281
|
+
suppressedEvents.set(key, (suppressedEvents.get(key) ?? 0) + 1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function consumeSuppressedNativeEvent(
|
|
285
|
+
scope: NonMemoryScope,
|
|
286
|
+
key: string,
|
|
287
|
+
): boolean {
|
|
288
|
+
const suppressedEvents = suppressedNativeEvents.get(scope)!;
|
|
289
|
+
const count = suppressedEvents.get(key);
|
|
290
|
+
if (count === undefined) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
if (count <= 1) {
|
|
294
|
+
suppressedEvents.delete(key);
|
|
295
|
+
} else {
|
|
296
|
+
suppressedEvents.set(key, count - 1);
|
|
297
|
+
}
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
|
|
243
301
|
function notifyKeyListeners(registry: KeyListenerRegistry, key: string): void {
|
|
244
302
|
const listeners = registry.get(key);
|
|
245
303
|
if (listeners) {
|
|
@@ -281,6 +339,99 @@ function addKeyListener(
|
|
|
281
339
|
};
|
|
282
340
|
}
|
|
283
341
|
|
|
342
|
+
function getEventRawValue(
|
|
343
|
+
scope: StorageScope,
|
|
344
|
+
key: string,
|
|
345
|
+
): string | undefined {
|
|
346
|
+
if (scope === StorageScope.Memory) {
|
|
347
|
+
const value = memoryStore.get(key);
|
|
348
|
+
return typeof value === "string" ? value : undefined;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return getRawValue(key, scope);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function createKeyChange(
|
|
355
|
+
scope: StorageScope,
|
|
356
|
+
key: string,
|
|
357
|
+
oldValue: string | undefined,
|
|
358
|
+
newValue: string | undefined,
|
|
359
|
+
operation: StorageChangeOperation,
|
|
360
|
+
source: StorageChangeSource,
|
|
361
|
+
): StorageKeyChangeEvent {
|
|
362
|
+
return {
|
|
363
|
+
type: "key",
|
|
364
|
+
scope,
|
|
365
|
+
key,
|
|
366
|
+
oldValue,
|
|
367
|
+
newValue,
|
|
368
|
+
operation,
|
|
369
|
+
source,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function hasStorageChangeObservers(scope: StorageScope): boolean {
|
|
374
|
+
return storageEvents.hasListeners(scope) || eventObserver !== undefined;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function emitKeyChange(
|
|
378
|
+
scope: StorageScope,
|
|
379
|
+
key: string,
|
|
380
|
+
oldValue: string | undefined,
|
|
381
|
+
newValue: string | undefined,
|
|
382
|
+
operation: StorageChangeOperation,
|
|
383
|
+
source: StorageChangeSource,
|
|
384
|
+
): void {
|
|
385
|
+
if (
|
|
386
|
+
source === "native" &&
|
|
387
|
+
operation !== "external" &&
|
|
388
|
+
scope !== StorageScope.Memory &&
|
|
389
|
+
scopedUnsubscribers.has(scope)
|
|
390
|
+
) {
|
|
391
|
+
suppressNativeEvent(scope, key);
|
|
392
|
+
}
|
|
393
|
+
const event = createKeyChange(
|
|
394
|
+
scope,
|
|
395
|
+
key,
|
|
396
|
+
oldValue,
|
|
397
|
+
newValue,
|
|
398
|
+
operation,
|
|
399
|
+
source,
|
|
400
|
+
);
|
|
401
|
+
storageEvents.emitKey(event);
|
|
402
|
+
eventObserver?.(event);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function emitBatchChange(
|
|
406
|
+
scope: StorageScope,
|
|
407
|
+
operation: StorageChangeOperation,
|
|
408
|
+
source: StorageChangeSource,
|
|
409
|
+
changes: StorageKeyChangeEvent[],
|
|
410
|
+
): void {
|
|
411
|
+
if (changes.length === 0) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (
|
|
416
|
+
source === "native" &&
|
|
417
|
+
operation !== "external" &&
|
|
418
|
+
scope !== StorageScope.Memory &&
|
|
419
|
+
scopedUnsubscribers.has(scope)
|
|
420
|
+
) {
|
|
421
|
+
changes.forEach((change) => suppressNativeEvent(scope, change.key));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const event: StorageBatchChangeEvent = {
|
|
425
|
+
type: "batch",
|
|
426
|
+
scope,
|
|
427
|
+
operation,
|
|
428
|
+
source,
|
|
429
|
+
changes,
|
|
430
|
+
};
|
|
431
|
+
storageEvents.emitBatch(event);
|
|
432
|
+
eventObserver?.(event);
|
|
433
|
+
}
|
|
434
|
+
|
|
284
435
|
function readPendingSecureWrite(key: string): string | undefined {
|
|
285
436
|
return pendingSecureWrites.get(key)?.value;
|
|
286
437
|
}
|
|
@@ -433,15 +584,27 @@ function ensureNativeScopeSubscription(scope: NonMemoryScope): void {
|
|
|
433
584
|
return;
|
|
434
585
|
}
|
|
435
586
|
|
|
587
|
+
const oldValue = readCachedRawValue(scope, key);
|
|
436
588
|
cacheRawValue(scope, key, value);
|
|
437
589
|
notifyKeyListeners(getScopedListeners(scope), key);
|
|
590
|
+
if (consumeSuppressedNativeEvent(scope, key)) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
emitKeyChange(scope, key, oldValue, value, "external", "native");
|
|
438
594
|
});
|
|
439
|
-
scopedUnsubscribers.set(
|
|
595
|
+
scopedUnsubscribers.set(
|
|
596
|
+
scope,
|
|
597
|
+
typeof unsubscribe === "function" ? unsubscribe : () => {},
|
|
598
|
+
);
|
|
440
599
|
}
|
|
441
600
|
|
|
442
601
|
function maybeCleanupNativeScopeSubscription(scope: NonMemoryScope): void {
|
|
443
602
|
const listeners = getScopedListeners(scope);
|
|
444
|
-
if (
|
|
603
|
+
if (
|
|
604
|
+
listeners.size > 0 ||
|
|
605
|
+
storageEvents.hasListeners(scope) ||
|
|
606
|
+
eventObserver !== undefined
|
|
607
|
+
) {
|
|
445
608
|
return;
|
|
446
609
|
}
|
|
447
610
|
|
|
@@ -474,9 +637,12 @@ function getRawValue(key: string, scope: StorageScope): string | undefined {
|
|
|
474
637
|
|
|
475
638
|
function setRawValue(key: string, value: string, scope: StorageScope): void {
|
|
476
639
|
assertValidScope(scope);
|
|
640
|
+
const oldValue =
|
|
641
|
+
scope === StorageScope.Memory ? getEventRawValue(scope, key) : undefined;
|
|
477
642
|
if (scope === StorageScope.Memory) {
|
|
478
643
|
memoryStore.set(key, value);
|
|
479
644
|
notifyKeyListeners(memoryListeners, key);
|
|
645
|
+
emitKeyChange(scope, key, oldValue, value, "set", "memory");
|
|
480
646
|
return;
|
|
481
647
|
}
|
|
482
648
|
|
|
@@ -484,6 +650,7 @@ function setRawValue(key: string, value: string, scope: StorageScope): void {
|
|
|
484
650
|
cacheRawValue(scope, key, value);
|
|
485
651
|
if (diskWritesAsync) {
|
|
486
652
|
scheduleDiskWrite(key, value);
|
|
653
|
+
emitKeyChange(scope, key, oldValue, value, "set", "native");
|
|
487
654
|
return;
|
|
488
655
|
}
|
|
489
656
|
|
|
@@ -499,13 +666,16 @@ function setRawValue(key: string, value: string, scope: StorageScope): void {
|
|
|
499
666
|
|
|
500
667
|
getStorageModule().set(key, value, scope);
|
|
501
668
|
cacheRawValue(scope, key, value);
|
|
669
|
+
emitKeyChange(scope, key, oldValue, value, "set", "native");
|
|
502
670
|
}
|
|
503
671
|
|
|
504
672
|
function removeRawValue(key: string, scope: StorageScope): void {
|
|
505
673
|
assertValidScope(scope);
|
|
674
|
+
const oldValue = getEventRawValue(scope, key);
|
|
506
675
|
if (scope === StorageScope.Memory) {
|
|
507
676
|
memoryStore.delete(key);
|
|
508
677
|
notifyKeyListeners(memoryListeners, key);
|
|
678
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "memory");
|
|
509
679
|
return;
|
|
510
680
|
}
|
|
511
681
|
|
|
@@ -513,6 +683,7 @@ function removeRawValue(key: string, scope: StorageScope): void {
|
|
|
513
683
|
cacheRawValue(scope, key, undefined);
|
|
514
684
|
if (diskWritesAsync) {
|
|
515
685
|
scheduleDiskWrite(key, undefined);
|
|
686
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "native");
|
|
516
687
|
return;
|
|
517
688
|
}
|
|
518
689
|
|
|
@@ -527,6 +698,7 @@ function removeRawValue(key: string, scope: StorageScope): void {
|
|
|
527
698
|
|
|
528
699
|
getStorageModule().remove(key, scope);
|
|
529
700
|
cacheRawValue(scope, key, undefined);
|
|
701
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "native");
|
|
530
702
|
}
|
|
531
703
|
|
|
532
704
|
function readMigrationVersion(scope: StorageScope): number {
|
|
@@ -544,11 +716,97 @@ function writeMigrationVersion(scope: StorageScope, version: number): void {
|
|
|
544
716
|
}
|
|
545
717
|
|
|
546
718
|
export const storage = {
|
|
719
|
+
subscribe: (
|
|
720
|
+
scope: StorageScope,
|
|
721
|
+
listener: StorageEventListener,
|
|
722
|
+
): (() => void) => {
|
|
723
|
+
assertValidScope(scope);
|
|
724
|
+
if (scope !== StorageScope.Memory) {
|
|
725
|
+
ensureNativeScopeSubscription(scope);
|
|
726
|
+
const unsubscribe = storageEvents.subscribe(scope, listener);
|
|
727
|
+
return () => {
|
|
728
|
+
unsubscribe();
|
|
729
|
+
maybeCleanupNativeScopeSubscription(scope);
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
return storageEvents.subscribe(scope, listener);
|
|
733
|
+
},
|
|
734
|
+
subscribeKey: (
|
|
735
|
+
scope: StorageScope,
|
|
736
|
+
key: string,
|
|
737
|
+
listener: StorageEventListener,
|
|
738
|
+
): (() => void) => {
|
|
739
|
+
assertValidScope(scope);
|
|
740
|
+
if (scope !== StorageScope.Memory) {
|
|
741
|
+
ensureNativeScopeSubscription(scope);
|
|
742
|
+
const unsubscribe = storageEvents.subscribeKey(scope, key, listener);
|
|
743
|
+
return () => {
|
|
744
|
+
unsubscribe();
|
|
745
|
+
maybeCleanupNativeScopeSubscription(scope);
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
return storageEvents.subscribeKey(scope, key, listener);
|
|
749
|
+
},
|
|
750
|
+
subscribePrefix: (
|
|
751
|
+
scope: StorageScope,
|
|
752
|
+
prefix: string,
|
|
753
|
+
listener: StorageEventListener,
|
|
754
|
+
): (() => void) => {
|
|
755
|
+
assertValidScope(scope);
|
|
756
|
+
if (scope !== StorageScope.Memory) {
|
|
757
|
+
ensureNativeScopeSubscription(scope);
|
|
758
|
+
const unsubscribe = storageEvents.subscribePrefix(
|
|
759
|
+
scope,
|
|
760
|
+
prefix,
|
|
761
|
+
listener,
|
|
762
|
+
);
|
|
763
|
+
return () => {
|
|
764
|
+
unsubscribe();
|
|
765
|
+
maybeCleanupNativeScopeSubscription(scope);
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
return storageEvents.subscribePrefix(scope, prefix, listener);
|
|
769
|
+
},
|
|
770
|
+
subscribeNamespace: (
|
|
771
|
+
namespace: string,
|
|
772
|
+
scope: StorageScope,
|
|
773
|
+
listener: StorageEventListener,
|
|
774
|
+
): (() => void) => {
|
|
775
|
+
return storage.subscribePrefix(scope, prefixKey(namespace, ""), listener);
|
|
776
|
+
},
|
|
777
|
+
setEventObserver: (observer?: StorageEventListener) => {
|
|
778
|
+
eventObserver = observer;
|
|
779
|
+
if (observer) {
|
|
780
|
+
ensureNativeScopeSubscription(StorageScope.Disk);
|
|
781
|
+
ensureNativeScopeSubscription(StorageScope.Secure);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
maybeCleanupNativeScopeSubscription(StorageScope.Disk);
|
|
785
|
+
maybeCleanupNativeScopeSubscription(StorageScope.Secure);
|
|
786
|
+
},
|
|
547
787
|
clear: (scope: StorageScope) => {
|
|
548
788
|
measureOperation("storage:clear", scope, () => {
|
|
789
|
+
const previousValues = hasStorageChangeObservers(scope)
|
|
790
|
+
? storage.getAll(scope)
|
|
791
|
+
: {};
|
|
549
792
|
if (scope === StorageScope.Memory) {
|
|
550
793
|
memoryStore.clear();
|
|
551
794
|
notifyAllListeners(memoryListeners);
|
|
795
|
+
emitBatchChange(
|
|
796
|
+
scope,
|
|
797
|
+
"clear",
|
|
798
|
+
"memory",
|
|
799
|
+
Object.keys(previousValues).map((key) =>
|
|
800
|
+
createKeyChange(
|
|
801
|
+
scope,
|
|
802
|
+
key,
|
|
803
|
+
previousValues[key],
|
|
804
|
+
undefined,
|
|
805
|
+
"clear",
|
|
806
|
+
"memory",
|
|
807
|
+
),
|
|
808
|
+
),
|
|
809
|
+
);
|
|
552
810
|
return;
|
|
553
811
|
}
|
|
554
812
|
|
|
@@ -564,6 +822,21 @@ export const storage = {
|
|
|
564
822
|
|
|
565
823
|
clearScopeRawCache(scope);
|
|
566
824
|
getStorageModule().clear(scope);
|
|
825
|
+
emitBatchChange(
|
|
826
|
+
scope,
|
|
827
|
+
"clear",
|
|
828
|
+
"native",
|
|
829
|
+
Object.keys(previousValues).map((key) =>
|
|
830
|
+
createKeyChange(
|
|
831
|
+
scope,
|
|
832
|
+
key,
|
|
833
|
+
previousValues[key],
|
|
834
|
+
undefined,
|
|
835
|
+
"clear",
|
|
836
|
+
"native",
|
|
837
|
+
),
|
|
838
|
+
),
|
|
839
|
+
);
|
|
567
840
|
});
|
|
568
841
|
},
|
|
569
842
|
clearAll: () => {
|
|
@@ -582,16 +855,44 @@ export const storage = {
|
|
|
582
855
|
measureOperation("storage:clearNamespace", scope, () => {
|
|
583
856
|
assertValidScope(scope);
|
|
584
857
|
if (scope === StorageScope.Memory) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
858
|
+
const affectedKeys = Array.from(memoryStore.keys()).filter((key) =>
|
|
859
|
+
isNamespaced(key, namespace),
|
|
860
|
+
);
|
|
861
|
+
const previousValues = affectedKeys.map((key) => ({
|
|
862
|
+
key,
|
|
863
|
+
value: getEventRawValue(scope, key),
|
|
864
|
+
}));
|
|
865
|
+
|
|
866
|
+
if (affectedKeys.length === 0) {
|
|
867
|
+
return;
|
|
589
868
|
}
|
|
590
|
-
|
|
869
|
+
|
|
870
|
+
affectedKeys.forEach((key) => {
|
|
871
|
+
memoryStore.delete(key);
|
|
872
|
+
});
|
|
873
|
+
affectedKeys.forEach((key) => notifyKeyListeners(memoryListeners, key));
|
|
874
|
+
emitBatchChange(
|
|
875
|
+
scope,
|
|
876
|
+
"clearNamespace",
|
|
877
|
+
"memory",
|
|
878
|
+
previousValues.map(({ key, value }) =>
|
|
879
|
+
createKeyChange(
|
|
880
|
+
scope,
|
|
881
|
+
key,
|
|
882
|
+
value,
|
|
883
|
+
undefined,
|
|
884
|
+
"clearNamespace",
|
|
885
|
+
"memory",
|
|
886
|
+
),
|
|
887
|
+
),
|
|
888
|
+
);
|
|
591
889
|
return;
|
|
592
890
|
}
|
|
593
891
|
|
|
594
892
|
const keyPrefix = prefixKey(namespace, "");
|
|
893
|
+
const previousValues = hasStorageChangeObservers(scope)
|
|
894
|
+
? storage.getByPrefix(keyPrefix, scope)
|
|
895
|
+
: {};
|
|
595
896
|
if (scope === StorageScope.Disk) {
|
|
596
897
|
flushDiskWrites();
|
|
597
898
|
}
|
|
@@ -606,6 +907,21 @@ export const storage = {
|
|
|
606
907
|
}
|
|
607
908
|
}
|
|
608
909
|
getStorageModule().removeByPrefix(keyPrefix, scope);
|
|
910
|
+
emitBatchChange(
|
|
911
|
+
scope,
|
|
912
|
+
"clearNamespace",
|
|
913
|
+
"native",
|
|
914
|
+
Object.keys(previousValues).map((key) =>
|
|
915
|
+
createKeyChange(
|
|
916
|
+
scope,
|
|
917
|
+
key,
|
|
918
|
+
previousValues[key],
|
|
919
|
+
undefined,
|
|
920
|
+
"clearNamespace",
|
|
921
|
+
"native",
|
|
922
|
+
),
|
|
923
|
+
),
|
|
924
|
+
);
|
|
609
925
|
});
|
|
610
926
|
},
|
|
611
927
|
clearBiometric: () => {
|
|
@@ -657,7 +973,7 @@ export const storage = {
|
|
|
657
973
|
if (scope === StorageScope.Secure) {
|
|
658
974
|
flushSecureWrites();
|
|
659
975
|
}
|
|
660
|
-
return getStorageModule().getKeysByPrefix(prefix, scope);
|
|
976
|
+
return getStorageModule().getKeysByPrefix(prefix, scope) ?? [];
|
|
661
977
|
});
|
|
662
978
|
},
|
|
663
979
|
getByPrefix: (
|
|
@@ -687,7 +1003,7 @@ export const storage = {
|
|
|
687
1003
|
if (scope === StorageScope.Secure) {
|
|
688
1004
|
flushSecureWrites();
|
|
689
1005
|
}
|
|
690
|
-
const values = getStorageModule().getBatch(keys, scope);
|
|
1006
|
+
const values = getStorageModule().getBatch(keys, scope) ?? [];
|
|
691
1007
|
keys.forEach((key, idx) => {
|
|
692
1008
|
const value = decodeNativeBatchValue(values[idx]);
|
|
693
1009
|
if (value !== undefined) {
|
|
@@ -702,9 +1018,10 @@ export const storage = {
|
|
|
702
1018
|
assertValidScope(scope);
|
|
703
1019
|
const result: Record<string, string> = {};
|
|
704
1020
|
if (scope === StorageScope.Memory) {
|
|
705
|
-
memoryStore.
|
|
1021
|
+
for (const key of memoryStore.keys()) {
|
|
1022
|
+
const value = memoryStore.get(key);
|
|
706
1023
|
if (typeof value === "string") result[key] = value;
|
|
707
|
-
}
|
|
1024
|
+
}
|
|
708
1025
|
return result;
|
|
709
1026
|
}
|
|
710
1027
|
if (scope === StorageScope.Disk) {
|
|
@@ -713,9 +1030,9 @@ export const storage = {
|
|
|
713
1030
|
if (scope === StorageScope.Secure) {
|
|
714
1031
|
flushSecureWrites();
|
|
715
1032
|
}
|
|
716
|
-
const keys = getStorageModule().getAllKeys(scope);
|
|
1033
|
+
const keys = getStorageModule().getAllKeys(scope) ?? [];
|
|
717
1034
|
if (keys.length === 0) return result;
|
|
718
|
-
const values = getStorageModule().getBatch(keys, scope);
|
|
1035
|
+
const values = getStorageModule().getBatch(keys, scope) ?? [];
|
|
719
1036
|
keys.forEach((key, idx) => {
|
|
720
1037
|
const val = decodeNativeBatchValue(values[idx]);
|
|
721
1038
|
if (val !== undefined) result[key] = val;
|
|
@@ -723,6 +1040,11 @@ export const storage = {
|
|
|
723
1040
|
return result;
|
|
724
1041
|
});
|
|
725
1042
|
},
|
|
1043
|
+
export: (scope: StorageScope): Record<string, string> => {
|
|
1044
|
+
return measureOperation("storage:export", scope, () =>
|
|
1045
|
+
storage.getAll(scope),
|
|
1046
|
+
);
|
|
1047
|
+
},
|
|
726
1048
|
size: (scope: StorageScope): number => {
|
|
727
1049
|
return measureOperation("storage:size", scope, () => {
|
|
728
1050
|
assertValidScope(scope);
|
|
@@ -803,7 +1125,7 @@ export const storage = {
|
|
|
803
1125
|
platform: "native",
|
|
804
1126
|
backend: {
|
|
805
1127
|
disk: "platform-preferences",
|
|
806
|
-
secure:
|
|
1128
|
+
secure: nativeSecureBackend,
|
|
807
1129
|
},
|
|
808
1130
|
writeBuffering: {
|
|
809
1131
|
disk: true,
|
|
@@ -811,6 +1133,67 @@ export const storage = {
|
|
|
811
1133
|
},
|
|
812
1134
|
errorClassification: true,
|
|
813
1135
|
}),
|
|
1136
|
+
getSecurityCapabilities: (): SecurityCapabilities => ({
|
|
1137
|
+
platform: "native",
|
|
1138
|
+
secureStorage: {
|
|
1139
|
+
backend: nativeSecureBackend,
|
|
1140
|
+
encrypted: "available",
|
|
1141
|
+
accessControl: "unknown",
|
|
1142
|
+
keychainAccessGroup: "unknown",
|
|
1143
|
+
hardwareBacked: "unknown",
|
|
1144
|
+
},
|
|
1145
|
+
biometric: {
|
|
1146
|
+
storage: "unknown",
|
|
1147
|
+
prompt: "unknown",
|
|
1148
|
+
biometryOnly: "unknown",
|
|
1149
|
+
biometryOrPasscode: "unknown",
|
|
1150
|
+
},
|
|
1151
|
+
metadata: {
|
|
1152
|
+
perKey: true,
|
|
1153
|
+
listsWithoutValues: true,
|
|
1154
|
+
persistsTimestamps: false,
|
|
1155
|
+
},
|
|
1156
|
+
}),
|
|
1157
|
+
getSecureMetadata: (key: string): SecureStorageMetadata => {
|
|
1158
|
+
return measureOperation(
|
|
1159
|
+
"storage:getSecureMetadata",
|
|
1160
|
+
StorageScope.Secure,
|
|
1161
|
+
() => {
|
|
1162
|
+
flushSecureWrites();
|
|
1163
|
+
const storageModule = getStorageModule();
|
|
1164
|
+
const biometricProtected = storageModule.hasSecureBiometric(key);
|
|
1165
|
+
const exists =
|
|
1166
|
+
biometricProtected || storageModule.has(key, StorageScope.Secure);
|
|
1167
|
+
let kind: SecureStorageMetadata["kind"] = "missing";
|
|
1168
|
+
if (exists) {
|
|
1169
|
+
kind = biometricProtected ? "biometric" : "secure";
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
return {
|
|
1173
|
+
key,
|
|
1174
|
+
exists,
|
|
1175
|
+
kind,
|
|
1176
|
+
backend: nativeSecureBackend,
|
|
1177
|
+
encrypted: "available",
|
|
1178
|
+
hardwareBacked: "unknown",
|
|
1179
|
+
biometricProtected,
|
|
1180
|
+
valueExposed: false,
|
|
1181
|
+
};
|
|
1182
|
+
},
|
|
1183
|
+
);
|
|
1184
|
+
},
|
|
1185
|
+
getAllSecureMetadata: (): SecureStorageMetadata[] => {
|
|
1186
|
+
return measureOperation(
|
|
1187
|
+
"storage:getAllSecureMetadata",
|
|
1188
|
+
StorageScope.Secure,
|
|
1189
|
+
() => {
|
|
1190
|
+
flushSecureWrites();
|
|
1191
|
+
return getStorageModule()
|
|
1192
|
+
.getAllKeys(StorageScope.Secure)
|
|
1193
|
+
.map((key) => storage.getSecureMetadata(key));
|
|
1194
|
+
},
|
|
1195
|
+
);
|
|
1196
|
+
},
|
|
814
1197
|
getString: (key: string, scope: StorageScope): string | undefined => {
|
|
815
1198
|
return measureOperation("storage:getString", scope, () => {
|
|
816
1199
|
return getRawValue(key, scope);
|
|
@@ -835,12 +1218,23 @@ export const storage = {
|
|
|
835
1218
|
assertValidScope(scope);
|
|
836
1219
|
if (keys.length === 0) return;
|
|
837
1220
|
const values = keys.map((k) => data[k]!);
|
|
1221
|
+
const changes = keys.map((key, index) =>
|
|
1222
|
+
createKeyChange(
|
|
1223
|
+
scope,
|
|
1224
|
+
key,
|
|
1225
|
+
getEventRawValue(scope, key),
|
|
1226
|
+
values[index],
|
|
1227
|
+
"import",
|
|
1228
|
+
scope === StorageScope.Memory ? "memory" : "native",
|
|
1229
|
+
),
|
|
1230
|
+
);
|
|
838
1231
|
|
|
839
1232
|
if (scope === StorageScope.Memory) {
|
|
840
1233
|
keys.forEach((key, index) => {
|
|
841
1234
|
memoryStore.set(key, values[index]);
|
|
842
1235
|
});
|
|
843
1236
|
keys.forEach((key) => notifyKeyListeners(memoryListeners, key));
|
|
1237
|
+
emitBatchChange(scope, "import", "memory", changes);
|
|
844
1238
|
return;
|
|
845
1239
|
}
|
|
846
1240
|
|
|
@@ -851,6 +1245,7 @@ export const storage = {
|
|
|
851
1245
|
|
|
852
1246
|
getStorageModule().setBatch(keys, values, scope);
|
|
853
1247
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
1248
|
+
emitBatchChange(scope, "import", "native", changes);
|
|
854
1249
|
},
|
|
855
1250
|
keys.length,
|
|
856
1251
|
);
|
|
@@ -913,6 +1308,11 @@ export interface StorageItem<T> {
|
|
|
913
1308
|
delete: () => void;
|
|
914
1309
|
has: () => boolean;
|
|
915
1310
|
subscribe: (callback: () => void) => () => void;
|
|
1311
|
+
subscribeSelector: <TSelected>(
|
|
1312
|
+
selector: (value: T) => TSelected,
|
|
1313
|
+
listener: StorageSelectorListener<TSelected>,
|
|
1314
|
+
options?: StorageSelectorSubscribeOptions<TSelected>,
|
|
1315
|
+
) => () => void;
|
|
916
1316
|
serialize: (value: T) => string;
|
|
917
1317
|
deserialize: (value: string) => T;
|
|
918
1318
|
scope: StorageScope;
|
|
@@ -1081,12 +1481,21 @@ export function createStorageItem<T = undefined>(
|
|
|
1081
1481
|
};
|
|
1082
1482
|
|
|
1083
1483
|
const writeStoredRaw = (rawValue: string): void => {
|
|
1484
|
+
const oldValue = undefined;
|
|
1084
1485
|
if (isBiometric) {
|
|
1085
1486
|
getStorageModule().setSecureBiometricWithLevel(
|
|
1086
1487
|
storageKey,
|
|
1087
1488
|
rawValue,
|
|
1088
1489
|
resolvedBiometricLevel,
|
|
1089
1490
|
);
|
|
1491
|
+
emitKeyChange(
|
|
1492
|
+
config.scope,
|
|
1493
|
+
storageKey,
|
|
1494
|
+
oldValue,
|
|
1495
|
+
rawValue,
|
|
1496
|
+
"set",
|
|
1497
|
+
"native",
|
|
1498
|
+
);
|
|
1090
1499
|
return;
|
|
1091
1500
|
}
|
|
1092
1501
|
|
|
@@ -1095,6 +1504,14 @@ export function createStorageItem<T = undefined>(
|
|
|
1095
1504
|
if (nonMemoryScope === StorageScope.Disk) {
|
|
1096
1505
|
if (coalesceDiskWrites || diskWritesAsync) {
|
|
1097
1506
|
scheduleDiskWrite(storageKey, rawValue);
|
|
1507
|
+
emitKeyChange(
|
|
1508
|
+
config.scope,
|
|
1509
|
+
storageKey,
|
|
1510
|
+
oldValue,
|
|
1511
|
+
rawValue,
|
|
1512
|
+
"set",
|
|
1513
|
+
"native",
|
|
1514
|
+
);
|
|
1098
1515
|
return;
|
|
1099
1516
|
}
|
|
1100
1517
|
|
|
@@ -1107,6 +1524,14 @@ export function createStorageItem<T = undefined>(
|
|
|
1107
1524
|
rawValue,
|
|
1108
1525
|
secureAccessControl ?? secureDefaultAccessControl,
|
|
1109
1526
|
);
|
|
1527
|
+
emitKeyChange(
|
|
1528
|
+
config.scope,
|
|
1529
|
+
storageKey,
|
|
1530
|
+
oldValue,
|
|
1531
|
+
rawValue,
|
|
1532
|
+
"set",
|
|
1533
|
+
"native",
|
|
1534
|
+
);
|
|
1110
1535
|
return;
|
|
1111
1536
|
}
|
|
1112
1537
|
|
|
@@ -1118,11 +1543,28 @@ export function createStorageItem<T = undefined>(
|
|
|
1118
1543
|
}
|
|
1119
1544
|
|
|
1120
1545
|
getStorageModule().set(storageKey, rawValue, config.scope);
|
|
1546
|
+
emitKeyChange(
|
|
1547
|
+
config.scope,
|
|
1548
|
+
storageKey,
|
|
1549
|
+
oldValue,
|
|
1550
|
+
rawValue,
|
|
1551
|
+
"set",
|
|
1552
|
+
"native",
|
|
1553
|
+
);
|
|
1121
1554
|
};
|
|
1122
1555
|
|
|
1123
1556
|
const removeStoredRaw = (): void => {
|
|
1557
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1124
1558
|
if (isBiometric) {
|
|
1125
1559
|
getStorageModule().deleteSecureBiometric(storageKey);
|
|
1560
|
+
emitKeyChange(
|
|
1561
|
+
config.scope,
|
|
1562
|
+
storageKey,
|
|
1563
|
+
oldValue,
|
|
1564
|
+
undefined,
|
|
1565
|
+
"remove",
|
|
1566
|
+
"native",
|
|
1567
|
+
);
|
|
1126
1568
|
return;
|
|
1127
1569
|
}
|
|
1128
1570
|
|
|
@@ -1131,6 +1573,14 @@ export function createStorageItem<T = undefined>(
|
|
|
1131
1573
|
if (nonMemoryScope === StorageScope.Disk) {
|
|
1132
1574
|
if (coalesceDiskWrites || diskWritesAsync) {
|
|
1133
1575
|
scheduleDiskWrite(storageKey, undefined);
|
|
1576
|
+
emitKeyChange(
|
|
1577
|
+
config.scope,
|
|
1578
|
+
storageKey,
|
|
1579
|
+
oldValue,
|
|
1580
|
+
undefined,
|
|
1581
|
+
"remove",
|
|
1582
|
+
"native",
|
|
1583
|
+
);
|
|
1134
1584
|
return;
|
|
1135
1585
|
}
|
|
1136
1586
|
|
|
@@ -1143,6 +1593,14 @@ export function createStorageItem<T = undefined>(
|
|
|
1143
1593
|
undefined,
|
|
1144
1594
|
secureAccessControl ?? secureDefaultAccessControl,
|
|
1145
1595
|
);
|
|
1596
|
+
emitKeyChange(
|
|
1597
|
+
config.scope,
|
|
1598
|
+
storageKey,
|
|
1599
|
+
oldValue,
|
|
1600
|
+
undefined,
|
|
1601
|
+
"remove",
|
|
1602
|
+
"native",
|
|
1603
|
+
);
|
|
1146
1604
|
return;
|
|
1147
1605
|
}
|
|
1148
1606
|
|
|
@@ -1151,15 +1609,32 @@ export function createStorageItem<T = undefined>(
|
|
|
1151
1609
|
}
|
|
1152
1610
|
|
|
1153
1611
|
getStorageModule().remove(storageKey, config.scope);
|
|
1612
|
+
emitKeyChange(
|
|
1613
|
+
config.scope,
|
|
1614
|
+
storageKey,
|
|
1615
|
+
oldValue,
|
|
1616
|
+
undefined,
|
|
1617
|
+
"remove",
|
|
1618
|
+
"native",
|
|
1619
|
+
);
|
|
1154
1620
|
};
|
|
1155
1621
|
|
|
1156
1622
|
const writeValueWithoutValidation = (value: T): void => {
|
|
1157
1623
|
if (isMemory) {
|
|
1624
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1158
1625
|
if (memoryExpiration) {
|
|
1159
1626
|
memoryExpiration.set(storageKey, Date.now() + (expirationTtlMs ?? 0));
|
|
1160
1627
|
}
|
|
1161
1628
|
memoryStore.set(storageKey, value);
|
|
1162
1629
|
notifyKeyListeners(memoryListeners, storageKey);
|
|
1630
|
+
emitKeyChange(
|
|
1631
|
+
config.scope,
|
|
1632
|
+
storageKey,
|
|
1633
|
+
oldValue,
|
|
1634
|
+
typeof value === "string" ? value : undefined,
|
|
1635
|
+
"set",
|
|
1636
|
+
"memory",
|
|
1637
|
+
);
|
|
1163
1638
|
return;
|
|
1164
1639
|
}
|
|
1165
1640
|
|
|
@@ -1331,11 +1806,20 @@ export function createStorageItem<T = undefined>(
|
|
|
1331
1806
|
invalidateParsedCache();
|
|
1332
1807
|
|
|
1333
1808
|
if (isMemory) {
|
|
1809
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1334
1810
|
if (memoryExpiration) {
|
|
1335
1811
|
memoryExpiration.delete(storageKey);
|
|
1336
1812
|
}
|
|
1337
1813
|
memoryStore.delete(storageKey);
|
|
1338
1814
|
notifyKeyListeners(memoryListeners, storageKey);
|
|
1815
|
+
emitKeyChange(
|
|
1816
|
+
config.scope,
|
|
1817
|
+
storageKey,
|
|
1818
|
+
oldValue,
|
|
1819
|
+
undefined,
|
|
1820
|
+
"remove",
|
|
1821
|
+
"memory",
|
|
1822
|
+
);
|
|
1339
1823
|
return;
|
|
1340
1824
|
}
|
|
1341
1825
|
|
|
@@ -1377,6 +1861,30 @@ export function createStorageItem<T = undefined>(
|
|
|
1377
1861
|
};
|
|
1378
1862
|
};
|
|
1379
1863
|
|
|
1864
|
+
const subscribeSelector = <TSelected>(
|
|
1865
|
+
selector: (value: T) => TSelected,
|
|
1866
|
+
listener: StorageSelectorListener<TSelected>,
|
|
1867
|
+
options: StorageSelectorSubscribeOptions<TSelected> = {},
|
|
1868
|
+
): (() => void) => {
|
|
1869
|
+
const isEqual = options.isEqual ?? Object.is;
|
|
1870
|
+
let currentValue = selector(getInternal());
|
|
1871
|
+
|
|
1872
|
+
if (options.fireImmediately === true) {
|
|
1873
|
+
listener(currentValue, currentValue);
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
return subscribe(() => {
|
|
1877
|
+
const nextValue = selector(getInternal());
|
|
1878
|
+
if (isEqual(currentValue, nextValue)) {
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
const previousValue = currentValue;
|
|
1883
|
+
currentValue = nextValue;
|
|
1884
|
+
listener(nextValue, previousValue);
|
|
1885
|
+
});
|
|
1886
|
+
};
|
|
1887
|
+
|
|
1380
1888
|
const storageItem: StorageItemInternal<T> = {
|
|
1381
1889
|
get,
|
|
1382
1890
|
getWithVersion,
|
|
@@ -1385,6 +1893,7 @@ export function createStorageItem<T = undefined>(
|
|
|
1385
1893
|
delete: deleteItem,
|
|
1386
1894
|
has: hasItem,
|
|
1387
1895
|
subscribe,
|
|
1896
|
+
subscribeSelector,
|
|
1388
1897
|
serialize,
|
|
1389
1898
|
deserialize,
|
|
1390
1899
|
_triggerListeners: () => {
|
|
@@ -1541,6 +2050,17 @@ export function setBatch<T>(
|
|
|
1541
2050
|
return;
|
|
1542
2051
|
}
|
|
1543
2052
|
|
|
2053
|
+
const changes = items.map(({ item, value }) =>
|
|
2054
|
+
createKeyChange(
|
|
2055
|
+
scope,
|
|
2056
|
+
item.key,
|
|
2057
|
+
getEventRawValue(scope, item.key),
|
|
2058
|
+
typeof value === "string" ? value : undefined,
|
|
2059
|
+
"setBatch",
|
|
2060
|
+
"memory",
|
|
2061
|
+
),
|
|
2062
|
+
);
|
|
2063
|
+
|
|
1544
2064
|
// Atomic write: update all values in memoryStore, invalidate caches, then batch-notify
|
|
1545
2065
|
items.forEach(({ item, value }) => {
|
|
1546
2066
|
memoryStore.set(item.key, value);
|
|
@@ -1549,6 +2069,7 @@ export function setBatch<T>(
|
|
|
1549
2069
|
items.forEach(({ item }) =>
|
|
1550
2070
|
notifyKeyListeners(memoryListeners, item.key),
|
|
1551
2071
|
);
|
|
2072
|
+
emitBatchChange(scope, "setBatch", "memory", changes);
|
|
1552
2073
|
return;
|
|
1553
2074
|
}
|
|
1554
2075
|
|
|
@@ -1568,6 +2089,10 @@ export function setBatch<T>(
|
|
|
1568
2089
|
|
|
1569
2090
|
flushSecureWrites();
|
|
1570
2091
|
const storageModule = getStorageModule();
|
|
2092
|
+
const keys = secureEntries.map(({ item }) => item.key);
|
|
2093
|
+
const oldValues = hasStorageChangeObservers(scope)
|
|
2094
|
+
? (storageModule.getBatch(keys, scope) ?? [])
|
|
2095
|
+
: [];
|
|
1571
2096
|
const groupedByAccessControl = new Map<
|
|
1572
2097
|
number,
|
|
1573
2098
|
{ keys: string[]; values: string[] }
|
|
@@ -1592,6 +2117,21 @@ export function setBatch<T>(
|
|
|
1592
2117
|
cacheRawValue(scope, key, group.values[index]),
|
|
1593
2118
|
);
|
|
1594
2119
|
});
|
|
2120
|
+
emitBatchChange(
|
|
2121
|
+
scope,
|
|
2122
|
+
"setBatch",
|
|
2123
|
+
"native",
|
|
2124
|
+
secureEntries.map(({ item, value }, index) =>
|
|
2125
|
+
createKeyChange(
|
|
2126
|
+
scope,
|
|
2127
|
+
item.key,
|
|
2128
|
+
oldValues[index],
|
|
2129
|
+
item.serialize(value),
|
|
2130
|
+
"setBatch",
|
|
2131
|
+
"native",
|
|
2132
|
+
),
|
|
2133
|
+
),
|
|
2134
|
+
);
|
|
1595
2135
|
return;
|
|
1596
2136
|
}
|
|
1597
2137
|
|
|
@@ -1607,9 +2147,27 @@ export function setBatch<T>(
|
|
|
1607
2147
|
|
|
1608
2148
|
const keys = items.map((entry) => entry.item.key);
|
|
1609
2149
|
const values = items.map((entry) => entry.item.serialize(entry.value));
|
|
2150
|
+
const oldValues = hasStorageChangeObservers(scope)
|
|
2151
|
+
? (getStorageModule().getBatch(keys, scope) ?? [])
|
|
2152
|
+
: [];
|
|
1610
2153
|
|
|
1611
2154
|
getStorageModule().setBatch(keys, values, scope);
|
|
1612
2155
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
2156
|
+
emitBatchChange(
|
|
2157
|
+
scope,
|
|
2158
|
+
"setBatch",
|
|
2159
|
+
"native",
|
|
2160
|
+
keys.map((key, index) =>
|
|
2161
|
+
createKeyChange(
|
|
2162
|
+
scope,
|
|
2163
|
+
key,
|
|
2164
|
+
oldValues[index],
|
|
2165
|
+
values[index],
|
|
2166
|
+
"setBatch",
|
|
2167
|
+
"native",
|
|
2168
|
+
),
|
|
2169
|
+
),
|
|
2170
|
+
);
|
|
1613
2171
|
},
|
|
1614
2172
|
items.length,
|
|
1615
2173
|
);
|
|
@@ -1626,7 +2184,18 @@ export function removeBatch(
|
|
|
1626
2184
|
assertBatchScope(items, scope);
|
|
1627
2185
|
|
|
1628
2186
|
if (scope === StorageScope.Memory) {
|
|
2187
|
+
const changes = items.map((item) =>
|
|
2188
|
+
createKeyChange(
|
|
2189
|
+
scope,
|
|
2190
|
+
item.key,
|
|
2191
|
+
getEventRawValue(scope, item.key),
|
|
2192
|
+
undefined,
|
|
2193
|
+
"removeBatch",
|
|
2194
|
+
"memory",
|
|
2195
|
+
),
|
|
2196
|
+
);
|
|
1629
2197
|
items.forEach((item) => item.delete());
|
|
2198
|
+
emitBatchChange(scope, "removeBatch", "memory", changes);
|
|
1630
2199
|
return;
|
|
1631
2200
|
}
|
|
1632
2201
|
|
|
@@ -1637,8 +2206,26 @@ export function removeBatch(
|
|
|
1637
2206
|
if (scope === StorageScope.Secure) {
|
|
1638
2207
|
flushSecureWrites();
|
|
1639
2208
|
}
|
|
2209
|
+
const oldValues = hasStorageChangeObservers(scope)
|
|
2210
|
+
? (getStorageModule().getBatch(keys, scope) ?? [])
|
|
2211
|
+
: [];
|
|
1640
2212
|
getStorageModule().removeBatch(keys, scope);
|
|
1641
2213
|
keys.forEach((key) => cacheRawValue(scope, key, undefined));
|
|
2214
|
+
emitBatchChange(
|
|
2215
|
+
scope,
|
|
2216
|
+
"removeBatch",
|
|
2217
|
+
"native",
|
|
2218
|
+
keys.map((key, index) =>
|
|
2219
|
+
createKeyChange(
|
|
2220
|
+
scope,
|
|
2221
|
+
key,
|
|
2222
|
+
oldValues[index],
|
|
2223
|
+
undefined,
|
|
2224
|
+
"removeBatch",
|
|
2225
|
+
"native",
|
|
2226
|
+
),
|
|
2227
|
+
),
|
|
2228
|
+
);
|
|
1642
2229
|
},
|
|
1643
2230
|
items.length,
|
|
1644
2231
|
);
|