react-native-nitro-storage 0.3.0 → 0.3.2
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 +594 -247
- package/android/CMakeLists.txt +2 -0
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +102 -11
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +16 -0
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +154 -34
- package/android/src/main/java/com/nitrostorage/NitroStoragePackage.kt +2 -2
- package/cpp/bindings/HybridStorage.cpp +176 -21
- package/cpp/bindings/HybridStorage.hpp +29 -2
- package/cpp/core/NativeStorageAdapter.hpp +16 -0
- package/ios/IOSStorageAdapterCpp.hpp +20 -0
- package/ios/IOSStorageAdapterCpp.mm +239 -32
- package/lib/commonjs/Storage.types.js +23 -1
- package/lib/commonjs/Storage.types.js.map +1 -1
- package/lib/commonjs/index.js +292 -75
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +473 -86
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/internal.js +10 -0
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/commonjs/storage-hooks.js +36 -0
- package/lib/commonjs/storage-hooks.js.map +1 -0
- package/lib/module/Storage.types.js +22 -0
- package/lib/module/Storage.types.js.map +1 -1
- package/lib/module/index.js +264 -75
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +445 -86
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/internal.js +8 -0
- package/lib/module/internal.js.map +1 -1
- package/lib/module/storage-hooks.js +30 -0
- package/lib/module/storage-hooks.js.map +1 -0
- package/lib/typescript/Storage.nitro.d.ts +12 -0
- package/lib/typescript/Storage.nitro.d.ts.map +1 -1
- package/lib/typescript/Storage.types.d.ts +20 -0
- package/lib/typescript/Storage.types.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +33 -10
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +45 -10
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts +2 -0
- package/lib/typescript/internal.d.ts.map +1 -1
- package/lib/typescript/storage-hooks.d.ts +10 -0
- package/lib/typescript/storage-hooks.d.ts.map +1 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +12 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +12 -0
- package/package.json +8 -3
- package/src/Storage.nitro.ts +13 -2
- package/src/Storage.types.ts +22 -0
- package/src/index.ts +382 -123
- package/src/index.web.ts +618 -134
- package/src/internal.ts +14 -4
- package/src/migration.ts +1 -1
- package/src/storage-hooks.ts +48 -0
package/lib/module/index.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { useRef, useSyncExternalStore } from "react";
|
|
4
3
|
import { NitroModules } from "react-native-nitro-modules";
|
|
5
|
-
import { StorageScope } from "./Storage.types";
|
|
6
|
-
import { MIGRATION_VERSION_KEY, isStoredEnvelope, assertBatchScope, assertValidScope, decodeNativeBatchValue, serializeWithPrimitiveFastPath, deserializeWithPrimitiveFastPath } from "./internal";
|
|
7
|
-
export { StorageScope } from "./Storage.types";
|
|
4
|
+
import { StorageScope, AccessControl } from "./Storage.types";
|
|
5
|
+
import { MIGRATION_VERSION_KEY, isStoredEnvelope, assertBatchScope, assertValidScope, decodeNativeBatchValue, serializeWithPrimitiveFastPath, deserializeWithPrimitiveFastPath, prefixKey, isNamespaced } from "./internal";
|
|
6
|
+
export { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
|
|
8
7
|
export { migrateFromMMKV } from "./migration";
|
|
8
|
+
function asInternal(item) {
|
|
9
|
+
return item;
|
|
10
|
+
}
|
|
11
|
+
function isUpdater(valueOrFn) {
|
|
12
|
+
return typeof valueOrFn === "function";
|
|
13
|
+
}
|
|
14
|
+
function typedKeys(record) {
|
|
15
|
+
return Object.keys(record);
|
|
16
|
+
}
|
|
9
17
|
const registeredMigrations = new Map();
|
|
10
18
|
const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
|
|
11
19
|
Promise.resolve().then(task);
|
|
@@ -24,6 +32,7 @@ const scopedUnsubscribers = new Map();
|
|
|
24
32
|
const scopedRawCache = new Map([[StorageScope.Disk, new Map()], [StorageScope.Secure, new Map()]]);
|
|
25
33
|
const pendingSecureWrites = new Map();
|
|
26
34
|
let secureFlushScheduled = false;
|
|
35
|
+
let secureDefaultAccessControl = AccessControl.WhenUnlocked;
|
|
27
36
|
function getScopedListeners(scope) {
|
|
28
37
|
return scopedListeners.get(scope);
|
|
29
38
|
}
|
|
@@ -99,6 +108,7 @@ function flushSecureWrites() {
|
|
|
99
108
|
}
|
|
100
109
|
});
|
|
101
110
|
const storageModule = getStorageModule();
|
|
111
|
+
storageModule.setSecureAccessControl(secureDefaultAccessControl);
|
|
102
112
|
if (keysToSet.length > 0) {
|
|
103
113
|
storageModule.setBatch(keysToSet, valuesToSet, StorageScope.Secure);
|
|
104
114
|
}
|
|
@@ -172,6 +182,7 @@ function setRawValue(key, value, scope) {
|
|
|
172
182
|
if (scope === StorageScope.Secure) {
|
|
173
183
|
flushSecureWrites();
|
|
174
184
|
clearPendingSecureWrite(key);
|
|
185
|
+
getStorageModule().setSecureAccessControl(secureDefaultAccessControl);
|
|
175
186
|
}
|
|
176
187
|
getStorageModule().set(key, value, scope);
|
|
177
188
|
cacheRawValue(scope, key, value);
|
|
@@ -219,10 +230,86 @@ export const storage = {
|
|
|
219
230
|
storage.clear(StorageScope.Memory);
|
|
220
231
|
storage.clear(StorageScope.Disk);
|
|
221
232
|
storage.clear(StorageScope.Secure);
|
|
233
|
+
},
|
|
234
|
+
clearNamespace: (namespace, scope) => {
|
|
235
|
+
assertValidScope(scope);
|
|
236
|
+
if (scope === StorageScope.Memory) {
|
|
237
|
+
for (const key of memoryStore.keys()) {
|
|
238
|
+
if (isNamespaced(key, namespace)) {
|
|
239
|
+
memoryStore.delete(key);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
notifyAllListeners(memoryListeners);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const keyPrefix = prefixKey(namespace, "");
|
|
246
|
+
if (scope === StorageScope.Secure) {
|
|
247
|
+
flushSecureWrites();
|
|
248
|
+
}
|
|
249
|
+
clearScopeRawCache(scope);
|
|
250
|
+
getStorageModule().removeByPrefix(keyPrefix, scope);
|
|
251
|
+
},
|
|
252
|
+
clearBiometric: () => {
|
|
253
|
+
getStorageModule().clearSecureBiometric();
|
|
254
|
+
},
|
|
255
|
+
has: (key, scope) => {
|
|
256
|
+
assertValidScope(scope);
|
|
257
|
+
if (scope === StorageScope.Memory) {
|
|
258
|
+
return memoryStore.has(key);
|
|
259
|
+
}
|
|
260
|
+
return getStorageModule().has(key, scope);
|
|
261
|
+
},
|
|
262
|
+
getAllKeys: scope => {
|
|
263
|
+
assertValidScope(scope);
|
|
264
|
+
if (scope === StorageScope.Memory) {
|
|
265
|
+
return Array.from(memoryStore.keys());
|
|
266
|
+
}
|
|
267
|
+
return getStorageModule().getAllKeys(scope);
|
|
268
|
+
},
|
|
269
|
+
getAll: scope => {
|
|
270
|
+
assertValidScope(scope);
|
|
271
|
+
const result = {};
|
|
272
|
+
if (scope === StorageScope.Memory) {
|
|
273
|
+
memoryStore.forEach((value, key) => {
|
|
274
|
+
if (typeof value === "string") result[key] = value;
|
|
275
|
+
});
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
const keys = getStorageModule().getAllKeys(scope);
|
|
279
|
+
if (keys.length === 0) return result;
|
|
280
|
+
const values = getStorageModule().getBatch(keys, scope);
|
|
281
|
+
keys.forEach((key, idx) => {
|
|
282
|
+
const val = decodeNativeBatchValue(values[idx]);
|
|
283
|
+
if (val !== undefined) result[key] = val;
|
|
284
|
+
});
|
|
285
|
+
return result;
|
|
286
|
+
},
|
|
287
|
+
size: scope => {
|
|
288
|
+
assertValidScope(scope);
|
|
289
|
+
if (scope === StorageScope.Memory) {
|
|
290
|
+
return memoryStore.size;
|
|
291
|
+
}
|
|
292
|
+
return getStorageModule().size(scope);
|
|
293
|
+
},
|
|
294
|
+
setAccessControl: level => {
|
|
295
|
+
secureDefaultAccessControl = level;
|
|
296
|
+
getStorageModule().setSecureAccessControl(level);
|
|
297
|
+
},
|
|
298
|
+
setSecureWritesAsync: enabled => {
|
|
299
|
+
getStorageModule().setSecureWritesAsync(enabled);
|
|
300
|
+
},
|
|
301
|
+
flushSecureWrites: () => {
|
|
302
|
+
flushSecureWrites();
|
|
303
|
+
},
|
|
304
|
+
setKeychainAccessGroup: group => {
|
|
305
|
+
getStorageModule().setKeychainAccessGroup(group);
|
|
222
306
|
}
|
|
223
307
|
};
|
|
224
308
|
function canUseRawBatchPath(item) {
|
|
225
|
-
return item._hasExpiration === false && item._hasValidation === false;
|
|
309
|
+
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
|
|
310
|
+
}
|
|
311
|
+
function canUseSecureRawBatchPath(item) {
|
|
312
|
+
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true;
|
|
226
313
|
}
|
|
227
314
|
function defaultSerialize(value) {
|
|
228
315
|
return serializeWithPrimitiveFastPath(value);
|
|
@@ -231,16 +318,21 @@ function defaultDeserialize(value) {
|
|
|
231
318
|
return deserializeWithPrimitiveFastPath(value);
|
|
232
319
|
}
|
|
233
320
|
export function createStorageItem(config) {
|
|
321
|
+
const storageKey = prefixKey(config.namespace, config.key);
|
|
234
322
|
const serialize = config.serialize ?? defaultSerialize;
|
|
235
323
|
const deserialize = config.deserialize ?? defaultDeserialize;
|
|
236
324
|
const isMemory = config.scope === StorageScope.Memory;
|
|
325
|
+
const isBiometric = config.biometric === true && config.scope === StorageScope.Secure;
|
|
326
|
+
const secureAccessControl = config.accessControl;
|
|
237
327
|
const validate = config.validate;
|
|
238
328
|
const onValidationError = config.onValidationError;
|
|
239
329
|
const expiration = config.expiration;
|
|
330
|
+
const onExpired = config.onExpired;
|
|
240
331
|
const expirationTtlMs = expiration?.ttlMs;
|
|
241
332
|
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
242
333
|
const readCache = !isMemory && config.readCache === true;
|
|
243
|
-
const coalesceSecureWrites = config.scope === StorageScope.Secure && config.coalesceSecureWrites === true;
|
|
334
|
+
const coalesceSecureWrites = config.scope === StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric && secureAccessControl === undefined;
|
|
335
|
+
const defaultValue = config.defaultValue;
|
|
244
336
|
const nonMemoryScope = config.scope === StorageScope.Disk ? StorageScope.Disk : config.scope === StorageScope.Secure ? StorageScope.Secure : null;
|
|
245
337
|
if (expiration && expiration.ttlMs <= 0) {
|
|
246
338
|
throw new Error("expiration.ttlMs must be greater than 0.");
|
|
@@ -250,10 +342,12 @@ export function createStorageItem(config) {
|
|
|
250
342
|
let lastRaw = undefined;
|
|
251
343
|
let lastValue;
|
|
252
344
|
let hasLastValue = false;
|
|
345
|
+
let lastExpiresAt = undefined;
|
|
253
346
|
const invalidateParsedCache = () => {
|
|
254
347
|
lastRaw = undefined;
|
|
255
348
|
lastValue = undefined;
|
|
256
349
|
hasLastValue = false;
|
|
350
|
+
lastExpiresAt = undefined;
|
|
257
351
|
};
|
|
258
352
|
const ensureSubscription = () => {
|
|
259
353
|
if (unsubscribe) {
|
|
@@ -264,66 +358,79 @@ export function createStorageItem(config) {
|
|
|
264
358
|
listeners.forEach(callback => callback());
|
|
265
359
|
};
|
|
266
360
|
if (isMemory) {
|
|
267
|
-
unsubscribe = addKeyListener(memoryListeners,
|
|
361
|
+
unsubscribe = addKeyListener(memoryListeners, storageKey, listener);
|
|
268
362
|
return;
|
|
269
363
|
}
|
|
270
364
|
ensureNativeScopeSubscription(nonMemoryScope);
|
|
271
|
-
unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope),
|
|
365
|
+
unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), storageKey, listener);
|
|
272
366
|
};
|
|
273
367
|
const readStoredRaw = () => {
|
|
274
368
|
if (isMemory) {
|
|
275
369
|
if (memoryExpiration) {
|
|
276
|
-
const expiresAt = memoryExpiration.get(
|
|
370
|
+
const expiresAt = memoryExpiration.get(storageKey);
|
|
277
371
|
if (expiresAt !== undefined && expiresAt <= Date.now()) {
|
|
278
|
-
memoryExpiration.delete(
|
|
279
|
-
memoryStore.delete(
|
|
280
|
-
notifyKeyListeners(memoryListeners,
|
|
372
|
+
memoryExpiration.delete(storageKey);
|
|
373
|
+
memoryStore.delete(storageKey);
|
|
374
|
+
notifyKeyListeners(memoryListeners, storageKey);
|
|
375
|
+
onExpired?.(storageKey);
|
|
281
376
|
return undefined;
|
|
282
377
|
}
|
|
283
378
|
}
|
|
284
|
-
return memoryStore.get(
|
|
379
|
+
return memoryStore.get(storageKey);
|
|
285
380
|
}
|
|
286
|
-
if (nonMemoryScope === StorageScope.Secure && hasPendingSecureWrite(
|
|
287
|
-
return readPendingSecureWrite(
|
|
381
|
+
if (nonMemoryScope === StorageScope.Secure && !isBiometric && hasPendingSecureWrite(storageKey)) {
|
|
382
|
+
return readPendingSecureWrite(storageKey);
|
|
288
383
|
}
|
|
289
384
|
if (readCache) {
|
|
290
|
-
if (hasCachedRawValue(nonMemoryScope,
|
|
291
|
-
return readCachedRawValue(nonMemoryScope,
|
|
385
|
+
if (hasCachedRawValue(nonMemoryScope, storageKey)) {
|
|
386
|
+
return readCachedRawValue(nonMemoryScope, storageKey);
|
|
292
387
|
}
|
|
293
388
|
}
|
|
294
|
-
|
|
295
|
-
|
|
389
|
+
if (isBiometric) {
|
|
390
|
+
return getStorageModule().getSecureBiometric(storageKey);
|
|
391
|
+
}
|
|
392
|
+
const raw = getStorageModule().get(storageKey, config.scope);
|
|
393
|
+
cacheRawValue(nonMemoryScope, storageKey, raw);
|
|
296
394
|
return raw;
|
|
297
395
|
};
|
|
298
396
|
const writeStoredRaw = rawValue => {
|
|
299
|
-
|
|
397
|
+
if (isBiometric) {
|
|
398
|
+
getStorageModule().setSecureBiometric(storageKey, rawValue);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
300
402
|
if (coalesceSecureWrites) {
|
|
301
|
-
scheduleSecureWrite(
|
|
403
|
+
scheduleSecureWrite(storageKey, rawValue);
|
|
302
404
|
return;
|
|
303
405
|
}
|
|
304
406
|
if (nonMemoryScope === StorageScope.Secure) {
|
|
305
|
-
clearPendingSecureWrite(
|
|
407
|
+
clearPendingSecureWrite(storageKey);
|
|
408
|
+
getStorageModule().setSecureAccessControl(secureAccessControl ?? secureDefaultAccessControl);
|
|
306
409
|
}
|
|
307
|
-
getStorageModule().set(
|
|
410
|
+
getStorageModule().set(storageKey, rawValue, config.scope);
|
|
308
411
|
};
|
|
309
412
|
const removeStoredRaw = () => {
|
|
310
|
-
|
|
413
|
+
if (isBiometric) {
|
|
414
|
+
getStorageModule().deleteSecureBiometric(storageKey);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
311
418
|
if (coalesceSecureWrites) {
|
|
312
|
-
scheduleSecureWrite(
|
|
419
|
+
scheduleSecureWrite(storageKey, undefined);
|
|
313
420
|
return;
|
|
314
421
|
}
|
|
315
422
|
if (nonMemoryScope === StorageScope.Secure) {
|
|
316
|
-
clearPendingSecureWrite(
|
|
423
|
+
clearPendingSecureWrite(storageKey);
|
|
317
424
|
}
|
|
318
|
-
getStorageModule().remove(
|
|
425
|
+
getStorageModule().remove(storageKey, config.scope);
|
|
319
426
|
};
|
|
320
427
|
const writeValueWithoutValidation = value => {
|
|
321
428
|
if (isMemory) {
|
|
322
429
|
if (memoryExpiration) {
|
|
323
|
-
memoryExpiration.set(
|
|
430
|
+
memoryExpiration.set(storageKey, Date.now() + (expirationTtlMs ?? 0));
|
|
324
431
|
}
|
|
325
|
-
memoryStore.set(
|
|
326
|
-
notifyKeyListeners(memoryListeners,
|
|
432
|
+
memoryStore.set(storageKey, value);
|
|
433
|
+
notifyKeyListeners(memoryListeners, storageKey);
|
|
327
434
|
return;
|
|
328
435
|
}
|
|
329
436
|
const serialized = serialize(value);
|
|
@@ -342,7 +449,7 @@ export function createStorageItem(config) {
|
|
|
342
449
|
if (onValidationError) {
|
|
343
450
|
return onValidationError(invalidValue);
|
|
344
451
|
}
|
|
345
|
-
return
|
|
452
|
+
return defaultValue;
|
|
346
453
|
};
|
|
347
454
|
const ensureValidatedValue = (candidate, hadStoredValue) => {
|
|
348
455
|
if (!validate || validate(candidate)) {
|
|
@@ -350,7 +457,7 @@ export function createStorageItem(config) {
|
|
|
350
457
|
}
|
|
351
458
|
const resolved = resolveInvalidValue(candidate);
|
|
352
459
|
if (validate && !validate(resolved)) {
|
|
353
|
-
return
|
|
460
|
+
return defaultValue;
|
|
354
461
|
}
|
|
355
462
|
if (hadStoredValue) {
|
|
356
463
|
writeValueWithoutValidation(resolved);
|
|
@@ -359,30 +466,53 @@ export function createStorageItem(config) {
|
|
|
359
466
|
};
|
|
360
467
|
const get = () => {
|
|
361
468
|
const raw = readStoredRaw();
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
469
|
+
if (!memoryExpiration && raw === lastRaw && hasLastValue) {
|
|
470
|
+
if (!expiration || lastExpiresAt === null) {
|
|
471
|
+
return lastValue;
|
|
472
|
+
}
|
|
473
|
+
if (typeof lastExpiresAt === "number") {
|
|
474
|
+
if (lastExpiresAt > Date.now()) {
|
|
475
|
+
return lastValue;
|
|
476
|
+
}
|
|
477
|
+
removeStoredRaw();
|
|
478
|
+
invalidateParsedCache();
|
|
479
|
+
onExpired?.(storageKey);
|
|
480
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
481
|
+
hasLastValue = true;
|
|
482
|
+
return lastValue;
|
|
483
|
+
}
|
|
365
484
|
}
|
|
366
485
|
lastRaw = raw;
|
|
367
486
|
if (raw === undefined) {
|
|
368
|
-
|
|
487
|
+
lastExpiresAt = undefined;
|
|
488
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
369
489
|
hasLastValue = true;
|
|
370
490
|
return lastValue;
|
|
371
491
|
}
|
|
372
492
|
if (isMemory) {
|
|
493
|
+
lastExpiresAt = undefined;
|
|
373
494
|
lastValue = ensureValidatedValue(raw, true);
|
|
374
495
|
hasLastValue = true;
|
|
375
496
|
return lastValue;
|
|
376
497
|
}
|
|
498
|
+
if (typeof raw !== "string") {
|
|
499
|
+
lastExpiresAt = undefined;
|
|
500
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
501
|
+
hasLastValue = true;
|
|
502
|
+
return lastValue;
|
|
503
|
+
}
|
|
377
504
|
let deserializableRaw = raw;
|
|
378
505
|
if (expiration) {
|
|
506
|
+
let envelopeExpiresAt = null;
|
|
379
507
|
try {
|
|
380
508
|
const parsed = JSON.parse(raw);
|
|
381
509
|
if (isStoredEnvelope(parsed)) {
|
|
510
|
+
envelopeExpiresAt = parsed.expiresAt;
|
|
382
511
|
if (parsed.expiresAt <= Date.now()) {
|
|
383
512
|
removeStoredRaw();
|
|
384
513
|
invalidateParsedCache();
|
|
385
|
-
|
|
514
|
+
onExpired?.(storageKey);
|
|
515
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
386
516
|
hasLastValue = true;
|
|
387
517
|
return lastValue;
|
|
388
518
|
}
|
|
@@ -391,17 +521,19 @@ export function createStorageItem(config) {
|
|
|
391
521
|
} catch {
|
|
392
522
|
// Keep backward compatibility with legacy raw values.
|
|
393
523
|
}
|
|
524
|
+
lastExpiresAt = envelopeExpiresAt;
|
|
525
|
+
} else {
|
|
526
|
+
lastExpiresAt = undefined;
|
|
394
527
|
}
|
|
395
528
|
lastValue = ensureValidatedValue(deserialize(deserializableRaw), true);
|
|
396
529
|
hasLastValue = true;
|
|
397
530
|
return lastValue;
|
|
398
531
|
};
|
|
399
532
|
const set = valueOrFn => {
|
|
400
|
-
const
|
|
401
|
-
const newValue = typeof valueOrFn === "function" ? valueOrFn(currentValue) : valueOrFn;
|
|
533
|
+
const newValue = isUpdater(valueOrFn) ? valueOrFn(get()) : valueOrFn;
|
|
402
534
|
invalidateParsedCache();
|
|
403
535
|
if (validate && !validate(newValue)) {
|
|
404
|
-
throw new Error(`Validation failed for key "${
|
|
536
|
+
throw new Error(`Validation failed for key "${storageKey}" in scope "${StorageScope[config.scope]}".`);
|
|
405
537
|
}
|
|
406
538
|
writeValueWithoutValidation(newValue);
|
|
407
539
|
};
|
|
@@ -409,14 +541,19 @@ export function createStorageItem(config) {
|
|
|
409
541
|
invalidateParsedCache();
|
|
410
542
|
if (isMemory) {
|
|
411
543
|
if (memoryExpiration) {
|
|
412
|
-
memoryExpiration.delete(
|
|
544
|
+
memoryExpiration.delete(storageKey);
|
|
413
545
|
}
|
|
414
|
-
memoryStore.delete(
|
|
415
|
-
notifyKeyListeners(memoryListeners,
|
|
546
|
+
memoryStore.delete(storageKey);
|
|
547
|
+
notifyKeyListeners(memoryListeners, storageKey);
|
|
416
548
|
return;
|
|
417
549
|
}
|
|
418
550
|
removeStoredRaw();
|
|
419
551
|
};
|
|
552
|
+
const hasItem = () => {
|
|
553
|
+
if (isMemory) return memoryStore.has(storageKey);
|
|
554
|
+
if (isBiometric) return getStorageModule().hasSecureBiometric(storageKey);
|
|
555
|
+
return getStorageModule().has(storageKey, config.scope);
|
|
556
|
+
};
|
|
420
557
|
const subscribe = callback => {
|
|
421
558
|
ensureSubscription();
|
|
422
559
|
listeners.add(callback);
|
|
@@ -435,6 +572,7 @@ export function createStorageItem(config) {
|
|
|
435
572
|
get,
|
|
436
573
|
set,
|
|
437
574
|
delete: deleteItem,
|
|
575
|
+
has: hasItem,
|
|
438
576
|
subscribe,
|
|
439
577
|
serialize,
|
|
440
578
|
deserialize,
|
|
@@ -445,43 +583,22 @@ export function createStorageItem(config) {
|
|
|
445
583
|
_hasValidation: validate !== undefined,
|
|
446
584
|
_hasExpiration: expiration !== undefined,
|
|
447
585
|
_readCacheEnabled: readCache,
|
|
586
|
+
_isBiometric: isBiometric,
|
|
587
|
+
...(secureAccessControl !== undefined ? {
|
|
588
|
+
_secureAccessControl: secureAccessControl
|
|
589
|
+
} : {}),
|
|
448
590
|
scope: config.scope,
|
|
449
|
-
key:
|
|
591
|
+
key: storageKey
|
|
450
592
|
};
|
|
451
593
|
return storageItem;
|
|
452
594
|
}
|
|
453
|
-
export
|
|
454
|
-
const value = useSyncExternalStore(item.subscribe, item.get, item.get);
|
|
455
|
-
return [value, item.set];
|
|
456
|
-
}
|
|
457
|
-
export function useStorageSelector(item, selector, isEqual = Object.is) {
|
|
458
|
-
const selectedRef = useRef({
|
|
459
|
-
hasValue: false
|
|
460
|
-
});
|
|
461
|
-
const getSelectedSnapshot = () => {
|
|
462
|
-
const nextSelected = selector(item.get());
|
|
463
|
-
const current = selectedRef.current;
|
|
464
|
-
if (current.hasValue && isEqual(current.value, nextSelected)) {
|
|
465
|
-
return current.value;
|
|
466
|
-
}
|
|
467
|
-
selectedRef.current = {
|
|
468
|
-
hasValue: true,
|
|
469
|
-
value: nextSelected
|
|
470
|
-
};
|
|
471
|
-
return nextSelected;
|
|
472
|
-
};
|
|
473
|
-
const selectedValue = useSyncExternalStore(item.subscribe, getSelectedSnapshot, getSelectedSnapshot);
|
|
474
|
-
return [selectedValue, item.set];
|
|
475
|
-
}
|
|
476
|
-
export function useSetStorage(item) {
|
|
477
|
-
return item.set;
|
|
478
|
-
}
|
|
595
|
+
export { useStorage, useStorageSelector, useSetStorage } from "./storage-hooks";
|
|
479
596
|
export function getBatch(items, scope) {
|
|
480
597
|
assertBatchScope(items, scope);
|
|
481
598
|
if (scope === StorageScope.Memory) {
|
|
482
599
|
return items.map(item => item.get());
|
|
483
600
|
}
|
|
484
|
-
const useRawBatchPath = items.every(item => canUseRawBatchPath(item));
|
|
601
|
+
const useRawBatchPath = items.every(item => scope === StorageScope.Secure ? canUseSecureRawBatchPath(item) : canUseRawBatchPath(item));
|
|
485
602
|
if (!useRawBatchPath) {
|
|
486
603
|
return items.map(item => item.get());
|
|
487
604
|
}
|
|
@@ -510,6 +627,9 @@ export function getBatch(items, scope) {
|
|
|
510
627
|
fetchedValues.forEach((value, index) => {
|
|
511
628
|
const key = keysToFetch[index];
|
|
512
629
|
const targetIndex = keyIndexes[index];
|
|
630
|
+
if (key === undefined || targetIndex === undefined) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
513
633
|
rawValues[targetIndex] = value;
|
|
514
634
|
cacheRawValue(scope, key, value);
|
|
515
635
|
});
|
|
@@ -531,9 +651,55 @@ export function setBatch(items, scope) {
|
|
|
531
651
|
}) => item.set(value));
|
|
532
652
|
return;
|
|
533
653
|
}
|
|
654
|
+
if (scope === StorageScope.Secure) {
|
|
655
|
+
const secureEntries = items.map(({
|
|
656
|
+
item,
|
|
657
|
+
value
|
|
658
|
+
}) => ({
|
|
659
|
+
item,
|
|
660
|
+
value,
|
|
661
|
+
internal: asInternal(item)
|
|
662
|
+
}));
|
|
663
|
+
const canUseSecureBatchPath = secureEntries.every(({
|
|
664
|
+
internal
|
|
665
|
+
}) => canUseSecureRawBatchPath(internal));
|
|
666
|
+
if (!canUseSecureBatchPath) {
|
|
667
|
+
items.forEach(({
|
|
668
|
+
item,
|
|
669
|
+
value
|
|
670
|
+
}) => item.set(value));
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
flushSecureWrites();
|
|
674
|
+
const storageModule = getStorageModule();
|
|
675
|
+
const groupedByAccessControl = new Map();
|
|
676
|
+
secureEntries.forEach(({
|
|
677
|
+
item,
|
|
678
|
+
value,
|
|
679
|
+
internal
|
|
680
|
+
}) => {
|
|
681
|
+
const accessControl = internal._secureAccessControl ?? secureDefaultAccessControl;
|
|
682
|
+
const existingGroup = groupedByAccessControl.get(accessControl);
|
|
683
|
+
const group = existingGroup ?? {
|
|
684
|
+
keys: [],
|
|
685
|
+
values: []
|
|
686
|
+
};
|
|
687
|
+
group.keys.push(item.key);
|
|
688
|
+
group.values.push(item.serialize(value));
|
|
689
|
+
if (!existingGroup) {
|
|
690
|
+
groupedByAccessControl.set(accessControl, group);
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
groupedByAccessControl.forEach((group, accessControl) => {
|
|
694
|
+
storageModule.setSecureAccessControl(accessControl);
|
|
695
|
+
storageModule.setBatch(group.keys, group.values, scope);
|
|
696
|
+
group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
|
|
697
|
+
});
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
534
700
|
const useRawBatchPath = items.every(({
|
|
535
701
|
item
|
|
536
|
-
}) => canUseRawBatchPath(item));
|
|
702
|
+
}) => canUseRawBatchPath(asInternal(item)));
|
|
537
703
|
if (!useRawBatchPath) {
|
|
538
704
|
items.forEach(({
|
|
539
705
|
item,
|
|
@@ -543,9 +709,6 @@ export function setBatch(items, scope) {
|
|
|
543
709
|
}
|
|
544
710
|
const keys = items.map(entry => entry.item.key);
|
|
545
711
|
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
546
|
-
if (scope === StorageScope.Secure) {
|
|
547
|
-
flushSecureWrites();
|
|
548
|
-
}
|
|
549
712
|
getStorageModule().setBatch(keys, values, scope);
|
|
550
713
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
551
714
|
}
|
|
@@ -644,4 +807,30 @@ export function runTransaction(scope, transaction) {
|
|
|
644
807
|
throw error;
|
|
645
808
|
}
|
|
646
809
|
}
|
|
810
|
+
export function createSecureAuthStorage(config, options) {
|
|
811
|
+
const ns = options?.namespace ?? "auth";
|
|
812
|
+
const result = {};
|
|
813
|
+
for (const key of typedKeys(config)) {
|
|
814
|
+
const itemConfig = config[key];
|
|
815
|
+
const expirationConfig = itemConfig.ttlMs !== undefined ? {
|
|
816
|
+
ttlMs: itemConfig.ttlMs
|
|
817
|
+
} : undefined;
|
|
818
|
+
result[key] = createStorageItem({
|
|
819
|
+
key,
|
|
820
|
+
scope: StorageScope.Secure,
|
|
821
|
+
defaultValue: "",
|
|
822
|
+
namespace: ns,
|
|
823
|
+
...(itemConfig.biometric !== undefined ? {
|
|
824
|
+
biometric: itemConfig.biometric
|
|
825
|
+
} : {}),
|
|
826
|
+
...(itemConfig.accessControl !== undefined ? {
|
|
827
|
+
accessControl: itemConfig.accessControl
|
|
828
|
+
} : {}),
|
|
829
|
+
...(expirationConfig !== undefined ? {
|
|
830
|
+
expiration: expirationConfig
|
|
831
|
+
} : {})
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
return result;
|
|
835
|
+
}
|
|
647
836
|
//# sourceMappingURL=index.js.map
|