react-native-nitro-storage 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +260 -7
  3. package/cpp/bindings/HybridStorage.cpp +21 -0
  4. package/cpp/bindings/HybridStorage.hpp +2 -0
  5. package/cpp/core/NativeStorageAdapter.hpp +6 -0
  6. package/docs/api-reference.md +68 -43
  7. package/docs/batch-transactions-migrations.md +7 -0
  8. package/docs/qa/agent-device-replay.md +106 -0
  9. package/docs/secure-storage.md +12 -6
  10. package/ios/IOSStorageAdapterCpp.hpp +12 -0
  11. package/ios/IOSStorageAdapterCpp.mm +180 -2
  12. package/lib/commonjs/Storage.types.js +4 -2
  13. package/lib/commonjs/Storage.types.js.map +1 -1
  14. package/lib/commonjs/core/durability.js +30 -2
  15. package/lib/commonjs/core/durability.js.map +1 -1
  16. package/lib/commonjs/index.js +16 -1
  17. package/lib/commonjs/index.js.map +1 -1
  18. package/lib/commonjs/index.web.js +10 -1
  19. package/lib/commonjs/index.web.js.map +1 -1
  20. package/lib/commonjs/shared.js +6 -0
  21. package/lib/commonjs/shared.js.map +1 -1
  22. package/lib/commonjs/storage-core.js +116 -3
  23. package/lib/commonjs/storage-core.js.map +1 -1
  24. package/lib/commonjs/testing.js +32 -2
  25. package/lib/commonjs/testing.js.map +1 -1
  26. package/lib/module/Storage.types.js +4 -2
  27. package/lib/module/Storage.types.js.map +1 -1
  28. package/lib/module/core/durability.js +30 -2
  29. package/lib/module/core/durability.js.map +1 -1
  30. package/lib/module/index.js +16 -1
  31. package/lib/module/index.js.map +1 -1
  32. package/lib/module/index.web.js +10 -1
  33. package/lib/module/index.web.js.map +1 -1
  34. package/lib/module/shared.js +5 -0
  35. package/lib/module/shared.js.map +1 -1
  36. package/lib/module/storage-core.js +117 -4
  37. package/lib/module/storage-core.js.map +1 -1
  38. package/lib/module/testing.js +31 -1
  39. package/lib/module/testing.js.map +1 -1
  40. package/lib/typescript/Storage.nitro.d.ts +2 -0
  41. package/lib/typescript/Storage.nitro.d.ts.map +1 -1
  42. package/lib/typescript/Storage.types.d.ts +4 -2
  43. package/lib/typescript/Storage.types.d.ts.map +1 -1
  44. package/lib/typescript/core/durability.d.ts +7 -1
  45. package/lib/typescript/core/durability.d.ts.map +1 -1
  46. package/lib/typescript/index.d.ts +8 -3
  47. package/lib/typescript/index.d.ts.map +1 -1
  48. package/lib/typescript/index.web.d.ts +8 -3
  49. package/lib/typescript/index.web.d.ts.map +1 -1
  50. package/lib/typescript/shared.d.ts +9 -1
  51. package/lib/typescript/shared.d.ts.map +1 -1
  52. package/lib/typescript/storage-core.d.ts +34 -4
  53. package/lib/typescript/storage-core.d.ts.map +1 -1
  54. package/lib/typescript/storage-platform.d.ts +2 -0
  55. package/lib/typescript/storage-platform.d.ts.map +1 -1
  56. package/lib/typescript/testing.d.ts +16 -5
  57. package/lib/typescript/testing.d.ts.map +1 -1
  58. package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +2 -0
  59. package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +2 -0
  60. package/package.json +2 -2
  61. package/src/Storage.nitro.ts +2 -0
  62. package/src/Storage.types.ts +4 -2
  63. package/src/core/durability.ts +46 -2
  64. package/src/index.ts +24 -0
  65. package/src/index.web.ts +18 -0
  66. package/src/shared.ts +27 -1
  67. package/src/storage-core.ts +182 -8
  68. package/src/storage-platform.ts +2 -0
  69. package/src/testing.ts +42 -0
@@ -5,8 +5,19 @@ import { createMetricsRegistry } from "./core/metrics";
5
5
  import { MIGRATION_VERSION_KEY, isStoredEnvelope, assertBatchScope, assertValidScope, toVersionToken, assertValidStorageKey, prefixKey, isNamespaced, escapeCollidingRawValue, unescapeCollidingRawValue } from "./internal";
6
6
  import { assertAccessControlLevel, assertBiometricLevel, canUseRawBatchPath, canUseSecureRawBatchPath, createStorageCompositeError, createKeyChange, defaultDeserialize, defaultSerialize, isKeychainLockedError, isUpdater, normalizeStorageError, notifyAllListeners, notifyKeyListeners, redactSecureKeyChange, typedKeys } from "./shared";
7
7
  import { StorageEventRegistry } from "./storage-events";
8
- import { isStorageError } from "./storage-runtime";
8
+ import { getStorageErrorCode, isStorageError } from "./storage-runtime";
9
9
  import { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
10
+ function assertSynchronousCallbackResult(value, callbackKind) {
11
+ if (value === null || typeof value !== "object" && typeof value !== "function") {
12
+ return;
13
+ }
14
+ const then = value.then;
15
+ if (typeof then !== "function") {
16
+ return;
17
+ }
18
+ void Promise.resolve(value).catch(() => undefined);
19
+ throw new TypeError(`${callbackKind} callbacks must be synchronous and cannot return a Promise or thenable.`);
20
+ }
10
21
  const EMPTY_KEYS = Object.freeze([]);
11
22
  function asInternal(item) {
12
23
  return item;
@@ -802,6 +813,10 @@ export function createStorageCore(buildAdapter) {
802
813
  flushSecureWrites();
803
814
  });
804
815
  },
816
+ /** Observes scheduled flush failures; explicit flush calls still throw. */
817
+ setScheduledFlushErrorObserver: observer => {
818
+ durability.setScheduledFlushErrorObserver(observer);
819
+ },
805
820
  setMetricsObserver: observer => {
806
821
  metrics.setObserver(observer);
807
822
  },
@@ -2126,6 +2141,84 @@ export function createStorageCore(buildAdapter) {
2126
2141
  }
2127
2142
  registeredMigrations.set(version, migration);
2128
2143
  }
2144
+ function migrateSecureAccessControl(level, options) {
2145
+ return measureOperation("storage:migrateSecureAccessControl", StorageScope.Secure, () => {
2146
+ assertAccessControlLevel(level);
2147
+ const requestedKeys = options?.keys;
2148
+ requestedKeys?.forEach(key => {
2149
+ assertValidStorageKey(key);
2150
+ });
2151
+ let keys;
2152
+ try {
2153
+ flushSecureWrites();
2154
+ keys = Array.from(new Set(requestedKeys ?? adapter.backend.getAllKeys(StorageScope.Secure)));
2155
+ } catch (error) {
2156
+ if (getStorageErrorCode(error) !== "keychain_locked") {
2157
+ throw error;
2158
+ }
2159
+ return {
2160
+ migrated: [],
2161
+ locked: [],
2162
+ missing: [],
2163
+ skipped: [],
2164
+ failed: [],
2165
+ enumerationLocked: true
2166
+ };
2167
+ }
2168
+ secureDefaultAccessControl = level;
2169
+ adapter.backend.setSecureAccessControl(level);
2170
+ const result = {
2171
+ migrated: [],
2172
+ locked: [],
2173
+ missing: [],
2174
+ skipped: [],
2175
+ failed: [],
2176
+ enumerationLocked: false
2177
+ };
2178
+ if (!adapter.supportsSecureAccessControlMigration()) {
2179
+ result.skipped = keys;
2180
+ return result;
2181
+ }
2182
+ keys.forEach(key => {
2183
+ try {
2184
+ let hasBiometric;
2185
+ try {
2186
+ hasBiometric = adapter.backend.hasSecureBiometric(key);
2187
+ } catch (error) {
2188
+ if (getStorageErrorCode(error) !== "keychain_locked") {
2189
+ throw error;
2190
+ }
2191
+ result.skipped.push(key);
2192
+ return;
2193
+ }
2194
+ if (hasBiometric) {
2195
+ result.skipped.push(key);
2196
+ return;
2197
+ }
2198
+ const raw = adapter.backend.get(key, StorageScope.Secure);
2199
+ if (raw === undefined) {
2200
+ result.missing.push(key);
2201
+ return;
2202
+ }
2203
+ adapter.backend.set(key, raw, StorageScope.Secure);
2204
+ result.migrated.push(key);
2205
+ } catch (error) {
2206
+ const code = getStorageErrorCode(error);
2207
+ if (code === "keychain_locked") {
2208
+ result.locked.push(key);
2209
+ } else {
2210
+ result.failed.push(code === undefined ? {
2211
+ key
2212
+ } : {
2213
+ key,
2214
+ code
2215
+ });
2216
+ }
2217
+ }
2218
+ });
2219
+ return result;
2220
+ }, options?.keys?.length ?? 1);
2221
+ }
2129
2222
  function migrateToLatest(scope = StorageScope.Disk) {
2130
2223
  return measureOperation("migration:run", scope, () => {
2131
2224
  assertValidScope(scope);
@@ -2138,7 +2231,8 @@ export function createStorageCore(buildAdapter) {
2138
2231
  return;
2139
2232
  }
2140
2233
  runTransaction(scope, tx => {
2141
- migration(tx);
2234
+ const migrationResult = migration(tx);
2235
+ assertSynchronousCallbackResult(migrationResult, "Migration");
2142
2236
  tx.setRaw(MIGRATION_VERSION_KEY, String(version));
2143
2237
  });
2144
2238
  appliedVersion = version;
@@ -2158,6 +2252,12 @@ export function createStorageCore(buildAdapter) {
2158
2252
  const NOT_SET = Symbol();
2159
2253
  const rollback = new Map();
2160
2254
  const itemRenameStates = new Map();
2255
+ let transactionOpen = true;
2256
+ const assertTransactionOpen = () => {
2257
+ if (!transactionOpen) {
2258
+ throw new TypeError("Transaction context is closed.");
2259
+ }
2260
+ };
2161
2261
  const rememberRollback = (key, item, includeOtherSecureRepresentation = false) => {
2162
2262
  const internal = item ? item : undefined;
2163
2263
  const representation = scope === StorageScope.Secure && internal?._isBiometric === true ? "biometric" : "plain";
@@ -2228,34 +2328,46 @@ export function createStorageCore(buildAdapter) {
2228
2328
  };
2229
2329
  const tx = {
2230
2330
  scope,
2231
- getRaw: key => getRawValue(key, scope),
2331
+ getRaw: key => {
2332
+ assertTransactionOpen();
2333
+ return getRawValue(key, scope);
2334
+ },
2232
2335
  setRaw: (key, value) => {
2336
+ assertTransactionOpen();
2233
2337
  rememberRollback(key);
2234
2338
  setRawValue(key, value, scope);
2235
2339
  },
2236
2340
  removeRaw: key => {
2341
+ assertTransactionOpen();
2237
2342
  rememberRollback(key, undefined, scope === StorageScope.Secure);
2238
2343
  removeRawValue(key, scope);
2239
2344
  },
2240
2345
  getItem: item => {
2346
+ assertTransactionOpen();
2241
2347
  assertBatchScope([item], scope);
2242
2348
  rememberItemRollback(item);
2243
2349
  return item.get();
2244
2350
  },
2245
2351
  setItem: (item, value) => {
2352
+ assertTransactionOpen();
2246
2353
  assertBatchScope([item], scope);
2247
2354
  rememberItemRollback(item);
2248
2355
  item.set(value);
2249
2356
  },
2250
2357
  removeItem: item => {
2358
+ assertTransactionOpen();
2251
2359
  assertBatchScope([item], scope);
2252
2360
  rememberItemRollback(item);
2253
2361
  item.delete();
2254
2362
  }
2255
2363
  };
2256
2364
  try {
2257
- return transaction(tx);
2365
+ const result = transaction(tx);
2366
+ assertSynchronousCallbackResult(result, "Transaction");
2367
+ transactionOpen = false;
2368
+ return result;
2258
2369
  } catch (error) {
2370
+ transactionOpen = false;
2259
2371
  itemRenameStates.forEach((migrated, item) => {
2260
2372
  item._setRenameMigrationState(migrated);
2261
2373
  });
@@ -2586,6 +2698,7 @@ export function createStorageCore(buildAdapter) {
2586
2698
  removeBatch,
2587
2699
  registerMigration,
2588
2700
  migrateToLatest,
2701
+ migrateSecureAccessControl,
2589
2702
  runTransaction,
2590
2703
  createSecureAuthStorage,
2591
2704
  internals