react-native-nitro-storage 0.4.0 → 0.4.3

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.
Files changed (41) hide show
  1. package/README.md +90 -0
  2. package/android/build.gradle +0 -12
  3. package/android/consumer-rules.pro +26 -4
  4. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +7 -10
  5. package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +0 -4
  6. package/android/src/main/cpp/cpp-adapter.cpp +3 -1
  7. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +172 -77
  8. package/cpp/bindings/HybridStorage.cpp +120 -69
  9. package/cpp/bindings/HybridStorage.hpp +4 -0
  10. package/ios/IOSStorageAdapterCpp.hpp +2 -1
  11. package/ios/IOSStorageAdapterCpp.mm +264 -49
  12. package/lib/commonjs/index.js +128 -20
  13. package/lib/commonjs/index.js.map +1 -1
  14. package/lib/commonjs/index.web.js +169 -41
  15. package/lib/commonjs/index.web.js.map +1 -1
  16. package/lib/commonjs/indexeddb-backend.js +130 -0
  17. package/lib/commonjs/indexeddb-backend.js.map +1 -0
  18. package/lib/commonjs/internal.js +51 -23
  19. package/lib/commonjs/internal.js.map +1 -1
  20. package/lib/module/index.js +121 -20
  21. package/lib/module/index.js.map +1 -1
  22. package/lib/module/index.web.js +162 -41
  23. package/lib/module/index.web.js.map +1 -1
  24. package/lib/module/indexeddb-backend.js +126 -0
  25. package/lib/module/indexeddb-backend.js.map +1 -0
  26. package/lib/module/internal.js +51 -23
  27. package/lib/module/internal.js.map +1 -1
  28. package/lib/typescript/index.d.ts +6 -0
  29. package/lib/typescript/index.d.ts.map +1 -1
  30. package/lib/typescript/index.web.d.ts +7 -1
  31. package/lib/typescript/index.web.d.ts.map +1 -1
  32. package/lib/typescript/indexeddb-backend.d.ts +29 -0
  33. package/lib/typescript/indexeddb-backend.d.ts.map +1 -0
  34. package/lib/typescript/internal.d.ts.map +1 -1
  35. package/nitrogen/generated/android/NitroStorageOnLoad.cpp +22 -17
  36. package/nitrogen/generated/android/NitroStorageOnLoad.hpp +13 -4
  37. package/package.json +7 -3
  38. package/src/index.ts +137 -27
  39. package/src/index.web.ts +182 -49
  40. package/src/indexeddb-backend.ts +143 -0
  41. package/src/internal.ts +51 -23
@@ -21,10 +21,17 @@ Object.defineProperty(exports, "StorageScope", {
21
21
  return _Storage.StorageScope;
22
22
  }
23
23
  });
24
+ Object.defineProperty(exports, "createIndexedDBBackend", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _indexeddbBackend.createIndexedDBBackend;
28
+ }
29
+ });
24
30
  exports.createSecureAuthStorage = createSecureAuthStorage;
25
31
  exports.createStorageItem = createStorageItem;
26
32
  exports.getBatch = getBatch;
27
33
  exports.getWebSecureStorageBackend = getWebSecureStorageBackend;
34
+ exports.isKeychainLockedError = isKeychainLockedError;
28
35
  Object.defineProperty(exports, "migrateFromMMKV", {
29
36
  enumerable: true,
30
37
  get: function () {
@@ -60,6 +67,7 @@ var _Storage = require("./Storage.types");
60
67
  var _internal = require("./internal");
61
68
  var _migration = require("./migration");
62
69
  var _storageHooks = require("./storage-hooks");
70
+ var _indexeddbBackend = require("./indexeddb-backend");
63
71
  function asInternal(item) {
64
72
  return item;
65
73
  }
@@ -81,10 +89,12 @@ const webScopeKeyIndex = new Map([[_Storage.StorageScope.Disk, new Set()], [_Sto
81
89
  const hydratedWebScopeKeyIndex = new Set();
82
90
  const pendingSecureWrites = new Map();
83
91
  let secureFlushScheduled = false;
92
+ let secureDefaultAccessControl = _Storage.AccessControl.WhenUnlocked;
84
93
  const SECURE_WEB_PREFIX = "__secure_";
85
94
  const BIOMETRIC_WEB_PREFIX = "__bio_";
86
95
  let hasWarnedAboutWebBiometricFallback = false;
87
96
  let hasWebStorageEventSubscription = false;
97
+ let webStorageSubscriberCount = 0;
88
98
  let metricsObserver;
89
99
  const metricsCounters = new Map();
90
100
  function recordMetric(operation, scope, durationMs, keysCount = 1) {
@@ -108,6 +118,9 @@ function recordMetric(operation, scope, durationMs, keysCount = 1) {
108
118
  });
109
119
  }
110
120
  function measureOperation(operation, scope, fn, keysCount = 1) {
121
+ if (!metricsObserver) {
122
+ return fn();
123
+ }
111
124
  const start = Date.now();
112
125
  try {
113
126
  return fn();
@@ -136,6 +149,8 @@ function createLocalStorageWebSecureBackend() {
136
149
  };
137
150
  }
138
151
  let webSecureStorageBackend = createLocalStorageWebSecureBackend();
152
+ let cachedSecureBrowserStorage;
153
+ let cachedSecureBackendRef;
139
154
  function getBrowserStorage(scope) {
140
155
  if (scope === _Storage.StorageScope.Disk) {
141
156
  return globalThis.localStorage;
@@ -144,16 +159,21 @@ function getBrowserStorage(scope) {
144
159
  if (!webSecureStorageBackend) {
145
160
  return undefined;
146
161
  }
147
- return {
148
- setItem: (key, value) => webSecureStorageBackend?.setItem(key, value),
149
- getItem: key => webSecureStorageBackend?.getItem(key) ?? null,
150
- removeItem: key => webSecureStorageBackend?.removeItem(key),
151
- clear: () => webSecureStorageBackend?.clear(),
152
- key: index => webSecureStorageBackend?.getAllKeys()[index] ?? null,
162
+ if (cachedSecureBackendRef === webSecureStorageBackend && cachedSecureBrowserStorage) {
163
+ return cachedSecureBrowserStorage;
164
+ }
165
+ cachedSecureBackendRef = webSecureStorageBackend;
166
+ cachedSecureBrowserStorage = {
167
+ setItem: (key, value) => webSecureStorageBackend.setItem(key, value),
168
+ getItem: key => webSecureStorageBackend.getItem(key) ?? null,
169
+ removeItem: key => webSecureStorageBackend.removeItem(key),
170
+ clear: () => webSecureStorageBackend.clear(),
171
+ key: index => webSecureStorageBackend.getAllKeys()[index] ?? null,
153
172
  get length() {
154
- return webSecureStorageBackend?.getAllKeys().length ?? 0;
173
+ return webSecureStorageBackend.getAllKeys().length;
155
174
  }
156
175
  };
176
+ return cachedSecureBrowserStorage;
157
177
  }
158
178
  return undefined;
159
179
  }
@@ -253,14 +273,18 @@ function handleWebStorageEvent(event) {
253
273
  notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Disk), key);
254
274
  }
255
275
  function ensureWebStorageEventSubscription() {
256
- if (hasWebStorageEventSubscription) {
257
- return;
276
+ webStorageSubscriberCount += 1;
277
+ if (webStorageSubscriberCount === 1 && typeof window !== "undefined" && typeof window.addEventListener === "function") {
278
+ window.addEventListener("storage", handleWebStorageEvent);
279
+ hasWebStorageEventSubscription = true;
258
280
  }
259
- if (typeof window === "undefined" || typeof window.addEventListener !== "function") {
260
- return;
281
+ }
282
+ function maybeCleanupWebStorageSubscription() {
283
+ webStorageSubscriberCount = Math.max(0, webStorageSubscriberCount - 1);
284
+ if (webStorageSubscriberCount === 0 && hasWebStorageEventSubscription && typeof window !== "undefined") {
285
+ window.removeEventListener("storage", handleWebStorageEvent);
286
+ hasWebStorageEventSubscription = false;
261
287
  }
262
- window.addEventListener("storage", handleWebStorageEvent);
263
- hasWebStorageEventSubscription = true;
264
288
  }
265
289
  function getScopedListeners(scope) {
266
290
  return webScopeListeners.get(scope);
@@ -281,12 +305,19 @@ function clearScopeRawCache(scope) {
281
305
  getScopeRawCache(scope).clear();
282
306
  }
283
307
  function notifyKeyListeners(registry, key) {
284
- registry.get(key)?.forEach(listener => listener());
308
+ const listeners = registry.get(key);
309
+ if (listeners) {
310
+ for (const listener of listeners) {
311
+ listener();
312
+ }
313
+ }
285
314
  }
286
315
  function notifyAllListeners(registry) {
287
- registry.forEach(listeners => {
288
- listeners.forEach(listener => listener());
289
- });
316
+ for (const listeners of registry.values()) {
317
+ for (const listener of listeners) {
318
+ listener();
319
+ }
320
+ }
290
321
  }
291
322
  function addKeyListener(registry, key, listener) {
292
323
  let listeners = registry.get(key);
@@ -332,7 +363,7 @@ function flushSecureWrites() {
332
363
  if (value === undefined) {
333
364
  keysToRemove.push(key);
334
365
  } else {
335
- const resolvedAccessControl = accessControl ?? _Storage.AccessControl.WhenUnlocked;
366
+ const resolvedAccessControl = accessControl ?? secureDefaultAccessControl;
336
367
  const existingGroup = groupedSetWrites.get(resolvedAccessControl);
337
368
  const group = existingGroup ?? {
338
369
  keys: [],
@@ -665,7 +696,12 @@ const storage = exports.storage = {
665
696
  if (scope === _Storage.StorageScope.Secure) {
666
697
  flushSecureWrites();
667
698
  }
668
- clearScopeRawCache(scope);
699
+ const scopeCache = getScopeRawCache(scope);
700
+ for (const key of scopeCache.keys()) {
701
+ if ((0, _internal.isNamespaced)(key, namespace)) {
702
+ scopeCache.delete(key);
703
+ }
704
+ }
669
705
  WebStorage.removeByPrefix(keyPrefix, scope);
670
706
  });
671
707
  },
@@ -734,9 +770,13 @@ const storage = exports.storage = {
734
770
  return result;
735
771
  }
736
772
  const keys = WebStorage.getAllKeys(scope);
737
- keys.forEach(key => {
738
- const val = WebStorage.get(key, scope);
739
- if (val !== undefined) result[key] = val;
773
+ if (keys.length === 0) return {};
774
+ const values = WebStorage.getBatch(keys, scope);
775
+ keys.forEach((key, index) => {
776
+ const val = values[index];
777
+ if (val !== undefined && val !== null) {
778
+ result[key] = val;
779
+ }
740
780
  });
741
781
  return result;
742
782
  });
@@ -748,7 +788,8 @@ const storage = exports.storage = {
748
788
  return WebStorage.size(scope);
749
789
  });
750
790
  },
751
- setAccessControl: _level => {
791
+ setAccessControl: level => {
792
+ secureDefaultAccessControl = level;
752
793
  recordMetric("storage:setAccessControl", _Storage.StorageScope.Secure, 0);
753
794
  },
754
795
  setSecureWritesAsync: _enabled => {
@@ -779,10 +820,48 @@ const storage = exports.storage = {
779
820
  },
780
821
  resetMetrics: () => {
781
822
  metricsCounters.clear();
823
+ },
824
+ getString: (key, scope) => {
825
+ return measureOperation("storage:getString", scope, () => {
826
+ return getRawValue(key, scope);
827
+ });
828
+ },
829
+ setString: (key, value, scope) => {
830
+ measureOperation("storage:setString", scope, () => {
831
+ setRawValue(key, value, scope);
832
+ });
833
+ },
834
+ deleteString: (key, scope) => {
835
+ measureOperation("storage:deleteString", scope, () => {
836
+ removeRawValue(key, scope);
837
+ });
838
+ },
839
+ import: (data, scope) => {
840
+ const keys = Object.keys(data);
841
+ measureOperation("storage:import", scope, () => {
842
+ (0, _internal.assertValidScope)(scope);
843
+ if (keys.length === 0) return;
844
+ const values = keys.map(k => data[k]);
845
+ if (scope === _Storage.StorageScope.Memory) {
846
+ keys.forEach((key, index) => {
847
+ memoryStore.set(key, values[index]);
848
+ });
849
+ keys.forEach(key => notifyKeyListeners(memoryListeners, key));
850
+ return;
851
+ }
852
+ if (scope === _Storage.StorageScope.Secure) {
853
+ flushSecureWrites();
854
+ WebStorage.setSecureAccessControl(secureDefaultAccessControl);
855
+ }
856
+ WebStorage.setBatch(keys, values, scope);
857
+ keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
858
+ }, keys.length);
782
859
  }
783
860
  };
784
861
  function setWebSecureStorageBackend(backend) {
785
862
  webSecureStorageBackend = backend ?? createLocalStorageWebSecureBackend();
863
+ cachedSecureBrowserStorage = undefined;
864
+ cachedSecureBackendRef = undefined;
786
865
  hydratedWebScopeKeyIndex.delete(_Storage.StorageScope.Secure);
787
866
  clearScopeRawCache(_Storage.StorageScope.Secure);
788
867
  }
@@ -863,12 +942,17 @@ function createStorageItem(config) {
863
942
  }
864
943
  return memoryStore.get(storageKey);
865
944
  }
866
- if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric && hasPendingSecureWrite(storageKey)) {
867
- return readPendingSecureWrite(storageKey);
945
+ if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
946
+ const pending = pendingSecureWrites.get(storageKey);
947
+ if (pending !== undefined) {
948
+ return pending.value;
949
+ }
868
950
  }
869
951
  if (readCache) {
870
- if (hasCachedRawValue(nonMemoryScope, storageKey)) {
871
- return readCachedRawValue(nonMemoryScope, storageKey);
952
+ const cache = getScopeRawCache(nonMemoryScope);
953
+ const cached = cache.get(storageKey);
954
+ if (cached !== undefined || cache.has(storageKey)) {
955
+ return cached;
872
956
  }
873
957
  }
874
958
  if (isBiometric) {
@@ -885,7 +969,7 @@ function createStorageItem(config) {
885
969
  }
886
970
  cacheRawValue(nonMemoryScope, storageKey, rawValue);
887
971
  if (coalesceSecureWrites) {
888
- scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? _Storage.AccessControl.WhenUnlocked);
972
+ scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
889
973
  return;
890
974
  }
891
975
  if (nonMemoryScope === _Storage.StorageScope.Secure) {
@@ -900,7 +984,7 @@ function createStorageItem(config) {
900
984
  }
901
985
  cacheRawValue(nonMemoryScope, storageKey, undefined);
902
986
  if (coalesceSecureWrites) {
903
- scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? _Storage.AccessControl.WhenUnlocked);
987
+ scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
904
988
  return;
905
989
  }
906
990
  if (nonMemoryScope === _Storage.StorageScope.Secure) {
@@ -963,6 +1047,7 @@ function createStorageItem(config) {
963
1047
  onExpired?.(storageKey);
964
1048
  lastValue = ensureValidatedValue(defaultValue, false);
965
1049
  hasLastValue = true;
1050
+ listeners.forEach(cb => cb());
966
1051
  return lastValue;
967
1052
  }
968
1053
  }
@@ -998,6 +1083,7 @@ function createStorageItem(config) {
998
1083
  onExpired?.(storageKey);
999
1084
  lastValue = ensureValidatedValue(defaultValue, false);
1000
1085
  hasLastValue = true;
1086
+ listeners.forEach(cb => cb());
1001
1087
  return lastValue;
1002
1088
  }
1003
1089
  deserializableRaw = parsed.payload;
@@ -1025,10 +1111,10 @@ function createStorageItem(config) {
1025
1111
  const set = valueOrFn => {
1026
1112
  measureOperation("item:set", config.scope, () => {
1027
1113
  const newValue = isUpdater(valueOrFn) ? valueOrFn(getInternal()) : valueOrFn;
1028
- invalidateParsedCache();
1029
1114
  if (validate && !validate(newValue)) {
1030
1115
  throw new Error(`Validation failed for key "${storageKey}" in scope "${_Storage.StorageScope[config.scope]}".`);
1031
1116
  }
1117
+ invalidateParsedCache();
1032
1118
  writeValueWithoutValidation(newValue);
1033
1119
  });
1034
1120
  };
@@ -1067,6 +1153,9 @@ function createStorageItem(config) {
1067
1153
  if (listeners.size === 0 && unsubscribe) {
1068
1154
  unsubscribe();
1069
1155
  unsubscribe = null;
1156
+ if (!isMemory) {
1157
+ maybeCleanupWebStorageSubscription();
1158
+ }
1070
1159
  }
1071
1160
  };
1072
1161
  };
@@ -1084,6 +1173,9 @@ function createStorageItem(config) {
1084
1173
  invalidateParsedCache();
1085
1174
  listeners.forEach(listener => listener());
1086
1175
  },
1176
+ _invalidateParsedCacheOnly: () => {
1177
+ invalidateParsedCache();
1178
+ },
1087
1179
  _hasValidation: validate !== undefined,
1088
1180
  _hasExpiration: expiration !== undefined,
1089
1181
  _readCacheEnabled: readCache,
@@ -1112,14 +1204,17 @@ function getBatch(items, scope) {
1112
1204
  const keyIndexes = [];
1113
1205
  items.forEach((item, index) => {
1114
1206
  if (scope === _Storage.StorageScope.Secure) {
1115
- if (hasPendingSecureWrite(item.key)) {
1116
- rawValues[index] = readPendingSecureWrite(item.key);
1207
+ const pending = pendingSecureWrites.get(item.key);
1208
+ if (pending !== undefined) {
1209
+ rawValues[index] = pending.value;
1117
1210
  return;
1118
1211
  }
1119
1212
  }
1120
1213
  if (item._readCacheEnabled === true) {
1121
- if (hasCachedRawValue(scope, item.key)) {
1122
- rawValues[index] = readCachedRawValue(scope, item.key);
1214
+ const cache = getScopeRawCache(scope);
1215
+ const cached = cache.get(item.key);
1216
+ if (cached !== undefined || cache.has(item.key)) {
1217
+ rawValues[index] = cached;
1123
1218
  return;
1124
1219
  }
1125
1220
  }
@@ -1151,10 +1246,32 @@ function setBatch(items, scope) {
1151
1246
  measureOperation("batch:set", scope, () => {
1152
1247
  (0, _internal.assertBatchScope)(items.map(batchEntry => batchEntry.item), scope);
1153
1248
  if (scope === _Storage.StorageScope.Memory) {
1249
+ // Determine if any item needs per-item handling (validation or TTL)
1250
+ const needsIndividualSets = items.some(({
1251
+ item
1252
+ }) => {
1253
+ const internal = asInternal(item);
1254
+ return internal._hasValidation || internal._hasExpiration;
1255
+ });
1256
+ if (needsIndividualSets) {
1257
+ items.forEach(({
1258
+ item,
1259
+ value
1260
+ }) => item.set(value));
1261
+ return;
1262
+ }
1263
+
1264
+ // Atomic write: update all values in memoryStore, invalidate caches, then batch-notify
1154
1265
  items.forEach(({
1155
1266
  item,
1156
1267
  value
1157
- }) => item.set(value));
1268
+ }) => {
1269
+ memoryStore.set(item.key, value);
1270
+ asInternal(item)._invalidateParsedCacheOnly();
1271
+ });
1272
+ items.forEach(({
1273
+ item
1274
+ }) => notifyKeyListeners(memoryListeners, item.key));
1158
1275
  return;
1159
1276
  }
1160
1277
  if (scope === _Storage.StorageScope.Secure) {
@@ -1183,7 +1300,7 @@ function setBatch(items, scope) {
1183
1300
  value,
1184
1301
  internal
1185
1302
  }) => {
1186
- const accessControl = internal._secureAccessControl ?? _Storage.AccessControl.WhenUnlocked;
1303
+ const accessControl = internal._secureAccessControl ?? secureDefaultAccessControl;
1187
1304
  const existingGroup = groupedByAccessControl.get(accessControl);
1188
1305
  const group = existingGroup ?? {
1189
1306
  keys: [],
@@ -1260,9 +1377,11 @@ function migrateToLatest(scope = _Storage.StorageScope.Disk) {
1260
1377
  return;
1261
1378
  }
1262
1379
  migration(context);
1263
- writeMigrationVersion(scope, version);
1264
1380
  appliedVersion = version;
1265
1381
  });
1382
+ if (appliedVersion !== currentVersion) {
1383
+ writeMigrationVersion(scope, appliedVersion);
1384
+ }
1266
1385
  return appliedVersion;
1267
1386
  });
1268
1387
  }
@@ -1272,12 +1391,17 @@ function runTransaction(scope, transaction) {
1272
1391
  if (scope === _Storage.StorageScope.Secure) {
1273
1392
  flushSecureWrites();
1274
1393
  }
1394
+ const NOT_SET = Symbol();
1275
1395
  const rollback = new Map();
1276
1396
  const rememberRollback = key => {
1277
1397
  if (rollback.has(key)) {
1278
1398
  return;
1279
1399
  }
1280
- rollback.set(key, getRawValue(key, scope));
1400
+ if (scope === _Storage.StorageScope.Memory) {
1401
+ rollback.set(key, memoryStore.has(key) ? memoryStore.get(key) : NOT_SET);
1402
+ } else {
1403
+ rollback.set(key, getRawValue(key, scope));
1404
+ }
1281
1405
  };
1282
1406
  const tx = {
1283
1407
  scope,
@@ -1311,11 +1435,12 @@ function runTransaction(scope, transaction) {
1311
1435
  const rollbackEntries = Array.from(rollback.entries()).reverse();
1312
1436
  if (scope === _Storage.StorageScope.Memory) {
1313
1437
  rollbackEntries.forEach(([key, previousValue]) => {
1314
- if (previousValue === undefined) {
1315
- removeRawValue(key, scope);
1438
+ if (previousValue === NOT_SET) {
1439
+ memoryStore.delete(key);
1316
1440
  } else {
1317
- setRawValue(key, previousValue, scope);
1441
+ memoryStore.set(key, previousValue);
1318
1442
  }
1443
+ notifyKeyListeners(memoryListeners, key);
1319
1444
  });
1320
1445
  } else {
1321
1446
  const keysToSet = [];
@@ -1374,4 +1499,7 @@ function createSecureAuthStorage(config, options) {
1374
1499
  }
1375
1500
  return result;
1376
1501
  }
1502
+ function isKeychainLockedError(_err) {
1503
+ return false;
1504
+ }
1377
1505
  //# sourceMappingURL=index.web.js.map