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
|
@@ -76,6 +76,7 @@ var _Storage = require("./Storage.types");
|
|
|
76
76
|
var _internal = require("./internal");
|
|
77
77
|
var _webStorageBackend = require("./web-storage-backend");
|
|
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");
|
|
@@ -110,7 +111,9 @@ const BIOMETRIC_WEB_PREFIX = "__bio_";
|
|
|
110
111
|
let hasWarnedAboutWebBiometricFallback = false;
|
|
111
112
|
let hasWindowStorageEventSubscription = false;
|
|
112
113
|
let metricsObserver;
|
|
114
|
+
let eventObserver;
|
|
113
115
|
const metricsCounters = new Map();
|
|
116
|
+
const storageEvents = new _storageEvents.StorageEventRegistry();
|
|
114
117
|
function recordMetric(operation, scope, durationMs, keysCount = 1) {
|
|
115
118
|
const existing = metricsCounters.get(operation);
|
|
116
119
|
if (!existing) {
|
|
@@ -161,6 +164,9 @@ function getBackendName(scope, backend) {
|
|
|
161
164
|
const scopeName = scope === _Storage.StorageScope.Disk ? "disk" : "secure";
|
|
162
165
|
return backend?.name ?? `web:${scopeName}`;
|
|
163
166
|
}
|
|
167
|
+
function getWebSecureEncryptionStatus(backend) {
|
|
168
|
+
return backend?.name === "localStorage:secure" ? "unavailable" : "unknown";
|
|
169
|
+
}
|
|
164
170
|
function createWebStorageError(scope, operation, error, backend) {
|
|
165
171
|
const backendName = getBackendName(scope, backend);
|
|
166
172
|
const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
|
|
@@ -232,6 +238,7 @@ function applyExternalChangeEvent(scope, key, newValue) {
|
|
|
232
238
|
}
|
|
233
239
|
if (scope === _Storage.StorageScope.Secure && key.startsWith(SECURE_WEB_PREFIX)) {
|
|
234
240
|
const plainKey = fromSecureStorageKey(key);
|
|
241
|
+
const oldValue = readCachedRawValue(_Storage.StorageScope.Secure, plainKey);
|
|
235
242
|
if (newValue === null) {
|
|
236
243
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(plainKey);
|
|
237
244
|
cacheRawValue(_Storage.StorageScope.Secure, plainKey, undefined);
|
|
@@ -240,10 +247,12 @@ function applyExternalChangeEvent(scope, key, newValue) {
|
|
|
240
247
|
cacheRawValue(_Storage.StorageScope.Secure, plainKey, newValue);
|
|
241
248
|
}
|
|
242
249
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), plainKey);
|
|
250
|
+
emitKeyChange(_Storage.StorageScope.Secure, plainKey, oldValue, newValue ?? undefined, "external", "external");
|
|
243
251
|
return;
|
|
244
252
|
}
|
|
245
253
|
if (scope === _Storage.StorageScope.Secure && key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
246
254
|
const plainKey = fromBiometricStorageKey(key);
|
|
255
|
+
const oldValue = readCachedRawValue(_Storage.StorageScope.Secure, plainKey);
|
|
247
256
|
if (newValue === null) {
|
|
248
257
|
if (withWebBackendOperation(_Storage.StorageScope.Secure, "external-sync:getItem", backend => backend.getItem(toSecureStorageKey(plainKey))) === null) {
|
|
249
258
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(plainKey);
|
|
@@ -254,8 +263,10 @@ function applyExternalChangeEvent(scope, key, newValue) {
|
|
|
254
263
|
cacheRawValue(_Storage.StorageScope.Secure, plainKey, newValue);
|
|
255
264
|
}
|
|
256
265
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), plainKey);
|
|
266
|
+
emitKeyChange(_Storage.StorageScope.Secure, plainKey, oldValue, newValue ?? undefined, "external", "external");
|
|
257
267
|
return;
|
|
258
268
|
}
|
|
269
|
+
const oldValue = readCachedRawValue(scope, key);
|
|
259
270
|
if (newValue === null) {
|
|
260
271
|
ensureWebScopeKeyIndex(scope).delete(key);
|
|
261
272
|
cacheRawValue(scope, key, undefined);
|
|
@@ -264,6 +275,7 @@ function applyExternalChangeEvent(scope, key, newValue) {
|
|
|
264
275
|
cacheRawValue(scope, key, newValue);
|
|
265
276
|
}
|
|
266
277
|
notifyKeyListeners(getScopedListeners(scope), key);
|
|
278
|
+
emitKeyChange(scope, key, oldValue, newValue ?? undefined, "external", "external");
|
|
267
279
|
}
|
|
268
280
|
function handleWebStorageEvent(event) {
|
|
269
281
|
const key = event.key;
|
|
@@ -354,6 +366,46 @@ function addKeyListener(registry, key, listener) {
|
|
|
354
366
|
}
|
|
355
367
|
};
|
|
356
368
|
}
|
|
369
|
+
function getEventRawValue(scope, key) {
|
|
370
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
371
|
+
const value = memoryStore.get(key);
|
|
372
|
+
return typeof value === "string" ? value : undefined;
|
|
373
|
+
}
|
|
374
|
+
return getRawValue(key, scope);
|
|
375
|
+
}
|
|
376
|
+
function createKeyChange(scope, key, oldValue, newValue, operation, source) {
|
|
377
|
+
return {
|
|
378
|
+
type: "key",
|
|
379
|
+
scope,
|
|
380
|
+
key,
|
|
381
|
+
oldValue,
|
|
382
|
+
newValue,
|
|
383
|
+
operation,
|
|
384
|
+
source
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function hasStorageChangeObservers(scope) {
|
|
388
|
+
return storageEvents.hasListeners(scope) || eventObserver !== undefined;
|
|
389
|
+
}
|
|
390
|
+
function emitKeyChange(scope, key, oldValue, newValue, operation, source) {
|
|
391
|
+
const event = createKeyChange(scope, key, oldValue, newValue, operation, source);
|
|
392
|
+
storageEvents.emitKey(event);
|
|
393
|
+
eventObserver?.(event);
|
|
394
|
+
}
|
|
395
|
+
function emitBatchChange(scope, operation, source, changes) {
|
|
396
|
+
if (changes.length === 0) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const event = {
|
|
400
|
+
type: "batch",
|
|
401
|
+
scope,
|
|
402
|
+
operation,
|
|
403
|
+
source,
|
|
404
|
+
changes
|
|
405
|
+
};
|
|
406
|
+
storageEvents.emitBatch(event);
|
|
407
|
+
eventObserver?.(event);
|
|
408
|
+
}
|
|
357
409
|
function readPendingSecureWrite(key) {
|
|
358
410
|
return pendingSecureWrites.get(key)?.value;
|
|
359
411
|
}
|
|
@@ -603,13 +655,10 @@ const WebStorage = {
|
|
|
603
655
|
return () => {};
|
|
604
656
|
},
|
|
605
657
|
has: (key, scope) => {
|
|
606
|
-
if (scope === _Storage.StorageScope.Secure) {
|
|
607
|
-
return
|
|
608
|
-
}
|
|
609
|
-
if (scope !== _Storage.StorageScope.Disk) {
|
|
610
|
-
return false;
|
|
658
|
+
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
659
|
+
return ensureWebScopeKeyIndex(scope).has(key);
|
|
611
660
|
}
|
|
612
|
-
return
|
|
661
|
+
return false;
|
|
613
662
|
},
|
|
614
663
|
getAllKeys: scope => {
|
|
615
664
|
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
@@ -700,15 +749,18 @@ function getRawValue(key, scope) {
|
|
|
700
749
|
}
|
|
701
750
|
function setRawValue(key, value, scope) {
|
|
702
751
|
(0, _internal.assertValidScope)(scope);
|
|
752
|
+
const oldValue = scope === _Storage.StorageScope.Memory ? getEventRawValue(scope, key) : undefined;
|
|
703
753
|
if (scope === _Storage.StorageScope.Memory) {
|
|
704
754
|
memoryStore.set(key, value);
|
|
705
755
|
notifyKeyListeners(memoryListeners, key);
|
|
756
|
+
emitKeyChange(scope, key, oldValue, value, "set", "memory");
|
|
706
757
|
return;
|
|
707
758
|
}
|
|
708
759
|
if (scope === _Storage.StorageScope.Disk) {
|
|
709
760
|
cacheRawValue(scope, key, value);
|
|
710
761
|
if (diskWritesAsync) {
|
|
711
762
|
scheduleDiskWrite(key, value);
|
|
763
|
+
emitKeyChange(scope, key, oldValue, value, "set", "web");
|
|
712
764
|
return;
|
|
713
765
|
}
|
|
714
766
|
flushDiskWrites();
|
|
@@ -720,18 +772,22 @@ function setRawValue(key, value, scope) {
|
|
|
720
772
|
}
|
|
721
773
|
WebStorage.set(key, value, scope);
|
|
722
774
|
cacheRawValue(scope, key, value);
|
|
775
|
+
emitKeyChange(scope, key, oldValue, value, "set", "web");
|
|
723
776
|
}
|
|
724
777
|
function removeRawValue(key, scope) {
|
|
725
778
|
(0, _internal.assertValidScope)(scope);
|
|
779
|
+
const oldValue = getEventRawValue(scope, key);
|
|
726
780
|
if (scope === _Storage.StorageScope.Memory) {
|
|
727
781
|
memoryStore.delete(key);
|
|
728
782
|
notifyKeyListeners(memoryListeners, key);
|
|
783
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "memory");
|
|
729
784
|
return;
|
|
730
785
|
}
|
|
731
786
|
if (scope === _Storage.StorageScope.Disk) {
|
|
732
787
|
cacheRawValue(scope, key, undefined);
|
|
733
788
|
if (diskWritesAsync) {
|
|
734
789
|
scheduleDiskWrite(key, undefined);
|
|
790
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "web");
|
|
735
791
|
return;
|
|
736
792
|
}
|
|
737
793
|
flushDiskWrites();
|
|
@@ -743,6 +799,7 @@ function removeRawValue(key, scope) {
|
|
|
743
799
|
}
|
|
744
800
|
WebStorage.remove(key, scope);
|
|
745
801
|
cacheRawValue(scope, key, undefined);
|
|
802
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "web");
|
|
746
803
|
}
|
|
747
804
|
function readMigrationVersion(scope) {
|
|
748
805
|
const raw = getRawValue(_internal.MIGRATION_VERSION_KEY, scope);
|
|
@@ -756,11 +813,43 @@ function writeMigrationVersion(scope, version) {
|
|
|
756
813
|
setRawValue(_internal.MIGRATION_VERSION_KEY, String(version), scope);
|
|
757
814
|
}
|
|
758
815
|
const storage = exports.storage = {
|
|
816
|
+
subscribe: (scope, listener) => {
|
|
817
|
+
(0, _internal.assertValidScope)(scope);
|
|
818
|
+
if (scope !== _Storage.StorageScope.Memory) {
|
|
819
|
+
ensureExternalSyncSubscriptions();
|
|
820
|
+
}
|
|
821
|
+
return storageEvents.subscribe(scope, listener);
|
|
822
|
+
},
|
|
823
|
+
subscribeKey: (scope, key, listener) => {
|
|
824
|
+
(0, _internal.assertValidScope)(scope);
|
|
825
|
+
if (scope !== _Storage.StorageScope.Memory) {
|
|
826
|
+
ensureExternalSyncSubscriptions();
|
|
827
|
+
}
|
|
828
|
+
return storageEvents.subscribeKey(scope, key, listener);
|
|
829
|
+
},
|
|
830
|
+
subscribePrefix: (scope, prefix, listener) => {
|
|
831
|
+
(0, _internal.assertValidScope)(scope);
|
|
832
|
+
if (scope !== _Storage.StorageScope.Memory) {
|
|
833
|
+
ensureExternalSyncSubscriptions();
|
|
834
|
+
}
|
|
835
|
+
return storageEvents.subscribePrefix(scope, prefix, listener);
|
|
836
|
+
},
|
|
837
|
+
subscribeNamespace: (namespace, scope, listener) => {
|
|
838
|
+
return storage.subscribePrefix(scope, (0, _internal.prefixKey)(namespace, ""), listener);
|
|
839
|
+
},
|
|
840
|
+
setEventObserver: observer => {
|
|
841
|
+
eventObserver = observer;
|
|
842
|
+
if (observer) {
|
|
843
|
+
ensureExternalSyncSubscriptions();
|
|
844
|
+
}
|
|
845
|
+
},
|
|
759
846
|
clear: scope => {
|
|
760
847
|
measureOperation("storage:clear", scope, () => {
|
|
848
|
+
const previousValues = hasStorageChangeObservers(scope) ? storage.getAll(scope) : {};
|
|
761
849
|
if (scope === _Storage.StorageScope.Memory) {
|
|
762
850
|
memoryStore.clear();
|
|
763
851
|
notifyAllListeners(memoryListeners);
|
|
852
|
+
emitBatchChange(scope, "clear", "memory", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clear", "memory")));
|
|
764
853
|
return;
|
|
765
854
|
}
|
|
766
855
|
if (scope === _Storage.StorageScope.Disk) {
|
|
@@ -773,6 +862,7 @@ const storage = exports.storage = {
|
|
|
773
862
|
}
|
|
774
863
|
clearScopeRawCache(scope);
|
|
775
864
|
WebStorage.clear(scope);
|
|
865
|
+
emitBatchChange(scope, "clear", "web", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clear", "web")));
|
|
776
866
|
});
|
|
777
867
|
},
|
|
778
868
|
clearAll: () => {
|
|
@@ -786,15 +876,26 @@ const storage = exports.storage = {
|
|
|
786
876
|
measureOperation("storage:clearNamespace", scope, () => {
|
|
787
877
|
(0, _internal.assertValidScope)(scope);
|
|
788
878
|
if (scope === _Storage.StorageScope.Memory) {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
879
|
+
const affectedKeys = Array.from(memoryStore.keys()).filter(key => (0, _internal.isNamespaced)(key, namespace));
|
|
880
|
+
const previousValues = affectedKeys.map(key => ({
|
|
881
|
+
key,
|
|
882
|
+
value: getEventRawValue(scope, key)
|
|
883
|
+
}));
|
|
884
|
+
if (affectedKeys.length === 0) {
|
|
885
|
+
return;
|
|
793
886
|
}
|
|
794
|
-
|
|
887
|
+
affectedKeys.forEach(key => {
|
|
888
|
+
memoryStore.delete(key);
|
|
889
|
+
});
|
|
890
|
+
affectedKeys.forEach(key => notifyKeyListeners(memoryListeners, key));
|
|
891
|
+
emitBatchChange(scope, "clearNamespace", "memory", previousValues.map(({
|
|
892
|
+
key,
|
|
893
|
+
value
|
|
894
|
+
}) => createKeyChange(scope, key, value, undefined, "clearNamespace", "memory")));
|
|
795
895
|
return;
|
|
796
896
|
}
|
|
797
897
|
const keyPrefix = (0, _internal.prefixKey)(namespace, "");
|
|
898
|
+
const previousValues = hasStorageChangeObservers(scope) ? storage.getByPrefix(keyPrefix, scope) : {};
|
|
798
899
|
if (scope === _Storage.StorageScope.Disk) {
|
|
799
900
|
flushDiskWrites();
|
|
800
901
|
}
|
|
@@ -808,6 +909,7 @@ const storage = exports.storage = {
|
|
|
808
909
|
}
|
|
809
910
|
}
|
|
810
911
|
WebStorage.removeByPrefix(keyPrefix, scope);
|
|
912
|
+
emitBatchChange(scope, "clearNamespace", "web", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clearNamespace", "web")));
|
|
811
913
|
});
|
|
812
914
|
},
|
|
813
915
|
clearBiometric: () => {
|
|
@@ -916,6 +1018,9 @@ const storage = exports.storage = {
|
|
|
916
1018
|
return result;
|
|
917
1019
|
});
|
|
918
1020
|
},
|
|
1021
|
+
export: scope => {
|
|
1022
|
+
return measureOperation("storage:export", scope, () => storage.getAll(scope));
|
|
1023
|
+
},
|
|
919
1024
|
size: scope => {
|
|
920
1025
|
return measureOperation("storage:size", scope, () => {
|
|
921
1026
|
(0, _internal.assertValidScope)(scope);
|
|
@@ -987,6 +1092,57 @@ const storage = exports.storage = {
|
|
|
987
1092
|
},
|
|
988
1093
|
errorClassification: true
|
|
989
1094
|
}),
|
|
1095
|
+
getSecurityCapabilities: () => {
|
|
1096
|
+
const secureBackend = getBackendName(_Storage.StorageScope.Secure, webSecureStorageBackend);
|
|
1097
|
+
return {
|
|
1098
|
+
platform: "web",
|
|
1099
|
+
secureStorage: {
|
|
1100
|
+
backend: secureBackend,
|
|
1101
|
+
encrypted: getWebSecureEncryptionStatus(webSecureStorageBackend),
|
|
1102
|
+
accessControl: "unavailable",
|
|
1103
|
+
keychainAccessGroup: "unavailable",
|
|
1104
|
+
hardwareBacked: "unavailable"
|
|
1105
|
+
},
|
|
1106
|
+
biometric: {
|
|
1107
|
+
storage: "unavailable",
|
|
1108
|
+
prompt: "unavailable",
|
|
1109
|
+
biometryOnly: "unavailable",
|
|
1110
|
+
biometryOrPasscode: "unavailable"
|
|
1111
|
+
},
|
|
1112
|
+
metadata: {
|
|
1113
|
+
perKey: true,
|
|
1114
|
+
listsWithoutValues: true,
|
|
1115
|
+
persistsTimestamps: false
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
},
|
|
1119
|
+
getSecureMetadata: key => {
|
|
1120
|
+
return measureOperation("storage:getSecureMetadata", _Storage.StorageScope.Secure, () => {
|
|
1121
|
+
flushSecureWrites();
|
|
1122
|
+
const biometricProtected = WebStorage.hasSecureBiometric(key);
|
|
1123
|
+
const exists = biometricProtected || WebStorage.has(key, _Storage.StorageScope.Secure);
|
|
1124
|
+
let kind = "missing";
|
|
1125
|
+
if (exists) {
|
|
1126
|
+
kind = biometricProtected ? "biometric" : "secure";
|
|
1127
|
+
}
|
|
1128
|
+
return {
|
|
1129
|
+
key,
|
|
1130
|
+
exists,
|
|
1131
|
+
kind,
|
|
1132
|
+
backend: getBackendName(_Storage.StorageScope.Secure, webSecureStorageBackend),
|
|
1133
|
+
encrypted: getWebSecureEncryptionStatus(webSecureStorageBackend),
|
|
1134
|
+
hardwareBacked: "unavailable",
|
|
1135
|
+
biometricProtected,
|
|
1136
|
+
valueExposed: false
|
|
1137
|
+
};
|
|
1138
|
+
});
|
|
1139
|
+
},
|
|
1140
|
+
getAllSecureMetadata: () => {
|
|
1141
|
+
return measureOperation("storage:getAllSecureMetadata", _Storage.StorageScope.Secure, () => {
|
|
1142
|
+
flushSecureWrites();
|
|
1143
|
+
return WebStorage.getAllKeys(_Storage.StorageScope.Secure).map(key => storage.getSecureMetadata(key));
|
|
1144
|
+
});
|
|
1145
|
+
},
|
|
990
1146
|
getString: (key, scope) => {
|
|
991
1147
|
return measureOperation("storage:getString", scope, () => {
|
|
992
1148
|
return getRawValue(key, scope);
|
|
@@ -1008,11 +1164,13 @@ const storage = exports.storage = {
|
|
|
1008
1164
|
(0, _internal.assertValidScope)(scope);
|
|
1009
1165
|
if (keys.length === 0) return;
|
|
1010
1166
|
const values = keys.map(k => data[k]);
|
|
1167
|
+
const changes = keys.map((key, index) => createKeyChange(scope, key, getEventRawValue(scope, key), values[index], "import", scope === _Storage.StorageScope.Memory ? "memory" : "web"));
|
|
1011
1168
|
if (scope === _Storage.StorageScope.Memory) {
|
|
1012
1169
|
keys.forEach((key, index) => {
|
|
1013
1170
|
memoryStore.set(key, values[index]);
|
|
1014
1171
|
});
|
|
1015
1172
|
keys.forEach(key => notifyKeyListeners(memoryListeners, key));
|
|
1173
|
+
emitBatchChange(scope, "import", "memory", changes);
|
|
1016
1174
|
return;
|
|
1017
1175
|
}
|
|
1018
1176
|
if (scope === _Storage.StorageScope.Secure) {
|
|
@@ -1024,6 +1182,7 @@ const storage = exports.storage = {
|
|
|
1024
1182
|
}
|
|
1025
1183
|
WebStorage.setBatch(keys, values, scope);
|
|
1026
1184
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
1185
|
+
emitBatchChange(scope, "import", "web", changes);
|
|
1027
1186
|
}, keys.length);
|
|
1028
1187
|
}
|
|
1029
1188
|
};
|
|
@@ -1165,56 +1324,68 @@ function createStorageItem(config) {
|
|
|
1165
1324
|
return raw;
|
|
1166
1325
|
};
|
|
1167
1326
|
const writeStoredRaw = rawValue => {
|
|
1327
|
+
const oldValue = config.scope === _Storage.StorageScope.Memory ? getEventRawValue(config.scope, storageKey) : undefined;
|
|
1168
1328
|
if (isBiometric) {
|
|
1169
1329
|
WebStorage.setSecureBiometricWithLevel(storageKey, rawValue, resolvedBiometricLevel);
|
|
1330
|
+
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "web");
|
|
1170
1331
|
return;
|
|
1171
1332
|
}
|
|
1172
1333
|
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
1173
1334
|
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1174
1335
|
if (coalesceDiskWrites || diskWritesAsync) {
|
|
1175
1336
|
scheduleDiskWrite(storageKey, rawValue);
|
|
1337
|
+
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "web");
|
|
1176
1338
|
return;
|
|
1177
1339
|
}
|
|
1178
1340
|
clearPendingDiskWrite(storageKey);
|
|
1179
1341
|
}
|
|
1180
1342
|
if (coalesceSecureWrites) {
|
|
1181
1343
|
scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
|
|
1344
|
+
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "web");
|
|
1182
1345
|
return;
|
|
1183
1346
|
}
|
|
1184
1347
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
1185
1348
|
clearPendingSecureWrite(storageKey);
|
|
1186
1349
|
}
|
|
1187
1350
|
WebStorage.set(storageKey, rawValue, config.scope);
|
|
1351
|
+
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "web");
|
|
1188
1352
|
};
|
|
1189
1353
|
const removeStoredRaw = () => {
|
|
1354
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1190
1355
|
if (isBiometric) {
|
|
1191
1356
|
WebStorage.deleteSecureBiometric(storageKey);
|
|
1357
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "web");
|
|
1192
1358
|
return;
|
|
1193
1359
|
}
|
|
1194
1360
|
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
1195
1361
|
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1196
1362
|
if (coalesceDiskWrites || diskWritesAsync) {
|
|
1197
1363
|
scheduleDiskWrite(storageKey, undefined);
|
|
1364
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "web");
|
|
1198
1365
|
return;
|
|
1199
1366
|
}
|
|
1200
1367
|
clearPendingDiskWrite(storageKey);
|
|
1201
1368
|
}
|
|
1202
1369
|
if (coalesceSecureWrites) {
|
|
1203
1370
|
scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
|
|
1371
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "web");
|
|
1204
1372
|
return;
|
|
1205
1373
|
}
|
|
1206
1374
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
1207
1375
|
clearPendingSecureWrite(storageKey);
|
|
1208
1376
|
}
|
|
1209
1377
|
WebStorage.remove(storageKey, config.scope);
|
|
1378
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "web");
|
|
1210
1379
|
};
|
|
1211
1380
|
const writeValueWithoutValidation = value => {
|
|
1212
1381
|
if (isMemory) {
|
|
1382
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1213
1383
|
if (memoryExpiration) {
|
|
1214
1384
|
memoryExpiration.set(storageKey, Date.now() + (expirationTtlMs ?? 0));
|
|
1215
1385
|
}
|
|
1216
1386
|
memoryStore.set(storageKey, value);
|
|
1217
1387
|
notifyKeyListeners(memoryListeners, storageKey);
|
|
1388
|
+
emitKeyChange(config.scope, storageKey, oldValue, typeof value === "string" ? value : undefined, "set", "memory");
|
|
1218
1389
|
return;
|
|
1219
1390
|
}
|
|
1220
1391
|
const serialized = serialize(value);
|
|
@@ -1346,11 +1517,13 @@ function createStorageItem(config) {
|
|
|
1346
1517
|
measureOperation("item:delete", config.scope, () => {
|
|
1347
1518
|
invalidateParsedCache();
|
|
1348
1519
|
if (isMemory) {
|
|
1520
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1349
1521
|
if (memoryExpiration) {
|
|
1350
1522
|
memoryExpiration.delete(storageKey);
|
|
1351
1523
|
}
|
|
1352
1524
|
memoryStore.delete(storageKey);
|
|
1353
1525
|
notifyKeyListeners(memoryListeners, storageKey);
|
|
1526
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "memory");
|
|
1354
1527
|
return;
|
|
1355
1528
|
}
|
|
1356
1529
|
removeStoredRaw();
|
|
@@ -1384,6 +1557,22 @@ function createStorageItem(config) {
|
|
|
1384
1557
|
}
|
|
1385
1558
|
};
|
|
1386
1559
|
};
|
|
1560
|
+
const subscribeSelector = (selector, listener, options = {}) => {
|
|
1561
|
+
const isEqual = options.isEqual ?? Object.is;
|
|
1562
|
+
let currentValue = selector(getInternal());
|
|
1563
|
+
if (options.fireImmediately === true) {
|
|
1564
|
+
listener(currentValue, currentValue);
|
|
1565
|
+
}
|
|
1566
|
+
return subscribe(() => {
|
|
1567
|
+
const nextValue = selector(getInternal());
|
|
1568
|
+
if (isEqual(currentValue, nextValue)) {
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
const previousValue = currentValue;
|
|
1572
|
+
currentValue = nextValue;
|
|
1573
|
+
listener(nextValue, previousValue);
|
|
1574
|
+
});
|
|
1575
|
+
};
|
|
1387
1576
|
const storageItem = {
|
|
1388
1577
|
get,
|
|
1389
1578
|
getWithVersion,
|
|
@@ -1392,6 +1581,7 @@ function createStorageItem(config) {
|
|
|
1392
1581
|
delete: deleteItem,
|
|
1393
1582
|
has: hasItem,
|
|
1394
1583
|
subscribe,
|
|
1584
|
+
subscribeSelector,
|
|
1395
1585
|
serialize,
|
|
1396
1586
|
deserialize,
|
|
1397
1587
|
_triggerListeners: () => {
|
|
@@ -1492,6 +1682,10 @@ function setBatch(items, scope) {
|
|
|
1492
1682
|
}) => item.set(value));
|
|
1493
1683
|
return;
|
|
1494
1684
|
}
|
|
1685
|
+
const changes = items.map(({
|
|
1686
|
+
item,
|
|
1687
|
+
value
|
|
1688
|
+
}) => createKeyChange(scope, item.key, getEventRawValue(scope, item.key), typeof value === "string" ? value : undefined, "setBatch", "memory"));
|
|
1495
1689
|
|
|
1496
1690
|
// Atomic write: update all values in memoryStore, invalidate caches, then batch-notify
|
|
1497
1691
|
items.forEach(({
|
|
@@ -1504,6 +1698,7 @@ function setBatch(items, scope) {
|
|
|
1504
1698
|
items.forEach(({
|
|
1505
1699
|
item
|
|
1506
1700
|
}) => notifyKeyListeners(memoryListeners, item.key));
|
|
1701
|
+
emitBatchChange(scope, "setBatch", "memory", changes);
|
|
1507
1702
|
return;
|
|
1508
1703
|
}
|
|
1509
1704
|
if (scope === _Storage.StorageScope.Secure) {
|
|
@@ -1526,6 +1721,10 @@ function setBatch(items, scope) {
|
|
|
1526
1721
|
return;
|
|
1527
1722
|
}
|
|
1528
1723
|
flushSecureWrites();
|
|
1724
|
+
const keys = secureEntries.map(({
|
|
1725
|
+
item
|
|
1726
|
+
}) => item.key);
|
|
1727
|
+
const oldValues = hasStorageChangeObservers(scope) ? WebStorage.getBatch(keys, scope) : [];
|
|
1529
1728
|
const groupedByAccessControl = new Map();
|
|
1530
1729
|
secureEntries.forEach(({
|
|
1531
1730
|
item,
|
|
@@ -1549,6 +1748,10 @@ function setBatch(items, scope) {
|
|
|
1549
1748
|
WebStorage.setBatch(group.keys, group.values, scope);
|
|
1550
1749
|
group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
|
|
1551
1750
|
});
|
|
1751
|
+
emitBatchChange(scope, "setBatch", "web", secureEntries.map(({
|
|
1752
|
+
item,
|
|
1753
|
+
value
|
|
1754
|
+
}, index) => createKeyChange(scope, item.key, oldValues[index], item.serialize(value), "setBatch", "web")));
|
|
1552
1755
|
return;
|
|
1553
1756
|
}
|
|
1554
1757
|
flushDiskWrites();
|
|
@@ -1564,15 +1767,19 @@ function setBatch(items, scope) {
|
|
|
1564
1767
|
}
|
|
1565
1768
|
const keys = items.map(entry => entry.item.key);
|
|
1566
1769
|
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
1770
|
+
const oldValues = hasStorageChangeObservers(scope) ? WebStorage.getBatch(keys, scope) : [];
|
|
1567
1771
|
WebStorage.setBatch(keys, values, scope);
|
|
1568
1772
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
1773
|
+
emitBatchChange(scope, "setBatch", "web", keys.map((key, index) => createKeyChange(scope, key, oldValues[index], values[index], "setBatch", "web")));
|
|
1569
1774
|
}, items.length);
|
|
1570
1775
|
}
|
|
1571
1776
|
function removeBatch(items, scope) {
|
|
1572
1777
|
measureOperation("batch:remove", scope, () => {
|
|
1573
1778
|
(0, _internal.assertBatchScope)(items, scope);
|
|
1574
1779
|
if (scope === _Storage.StorageScope.Memory) {
|
|
1780
|
+
const changes = items.map(item => createKeyChange(scope, item.key, getEventRawValue(scope, item.key), undefined, "removeBatch", "memory"));
|
|
1575
1781
|
items.forEach(item => item.delete());
|
|
1782
|
+
emitBatchChange(scope, "removeBatch", "memory", changes);
|
|
1576
1783
|
return;
|
|
1577
1784
|
}
|
|
1578
1785
|
const keys = items.map(item => item.key);
|
|
@@ -1582,8 +1789,10 @@ function removeBatch(items, scope) {
|
|
|
1582
1789
|
if (scope === _Storage.StorageScope.Secure) {
|
|
1583
1790
|
flushSecureWrites();
|
|
1584
1791
|
}
|
|
1792
|
+
const oldValues = hasStorageChangeObservers(scope) ? WebStorage.getBatch(keys, scope) : [];
|
|
1585
1793
|
WebStorage.removeBatch(keys, scope);
|
|
1586
1794
|
keys.forEach(key => cacheRawValue(scope, key, undefined));
|
|
1795
|
+
emitBatchChange(scope, "removeBatch", "web", keys.map((key, index) => createKeyChange(scope, key, oldValues[index], undefined, "removeBatch", "web")));
|
|
1587
1796
|
}, items.length);
|
|
1588
1797
|
}
|
|
1589
1798
|
function registerMigration(version, migration) {
|