react-native-nitro-storage 0.4.4 → 0.5.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.
- package/README.md +237 -862
- package/SECURITY.md +26 -0
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +61 -10
- package/docs/api-reference.md +217 -0
- package/docs/batch-transactions-migrations.md +186 -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 +281 -0
- package/docs/secure-storage.md +171 -0
- package/docs/web-backends.md +141 -0
- package/ios/IOSStorageAdapterCpp.mm +44 -14
- package/lib/commonjs/index.js +271 -5
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +498 -202
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/indexeddb-backend.js +129 -7
- package/lib/commonjs/indexeddb-backend.js.map +1 -1
- package/lib/commonjs/storage-runtime.js +41 -0
- package/lib/commonjs/storage-runtime.js.map +1 -0
- package/lib/commonjs/web-storage-backend.js +90 -0
- package/lib/commonjs/web-storage-backend.js.map +1 -0
- package/lib/module/index.js +263 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +490 -202
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/indexeddb-backend.js +129 -7
- package/lib/module/indexeddb-backend.js.map +1 -1
- package/lib/module/storage-runtime.js +36 -0
- package/lib/module/storage-runtime.js.map +1 -0
- package/lib/module/web-storage-backend.js +86 -0
- package/lib/module/web-storage-backend.js.map +1 -0
- package/lib/typescript/index.d.ts +14 -7
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +15 -8
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/indexeddb-backend.d.ts +6 -2
- package/lib/typescript/indexeddb-backend.d.ts.map +1 -1
- package/lib/typescript/storage-runtime.d.ts +48 -0
- package/lib/typescript/storage-runtime.d.ts.map +1 -0
- package/lib/typescript/web-storage-backend.d.ts +30 -0
- package/lib/typescript/web-storage-backend.d.ts.map +1 -0
- package/package.json +21 -8
- package/src/index.ts +330 -20
- package/src/index.web.ts +673 -245
- package/src/indexeddb-backend.ts +147 -6
- package/src/storage-runtime.ts +129 -0
- package/src/web-storage-backend.ts +129 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createLocalStorageWebBackend = createLocalStorageWebBackend;
|
|
7
|
+
function getResolvedStorage(resolveStorage) {
|
|
8
|
+
if (resolveStorage) {
|
|
9
|
+
return resolveStorage();
|
|
10
|
+
}
|
|
11
|
+
if (typeof globalThis.localStorage === "undefined") {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return globalThis.localStorage;
|
|
15
|
+
}
|
|
16
|
+
function createLocalStorageWebBackend(options = {}) {
|
|
17
|
+
const includeKey = options.includeKey;
|
|
18
|
+
const resolveStorage = options.resolveStorage;
|
|
19
|
+
const listKeys = () => {
|
|
20
|
+
const storage = getResolvedStorage(resolveStorage);
|
|
21
|
+
if (!storage) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
const keys = [];
|
|
25
|
+
for (let index = 0; index < storage.length; index += 1) {
|
|
26
|
+
const key = storage.key(index);
|
|
27
|
+
if (!key) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (includeKey && !includeKey(key)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
keys.push(key);
|
|
34
|
+
}
|
|
35
|
+
return keys;
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
name: options.name ?? "localStorage",
|
|
39
|
+
getItem(key) {
|
|
40
|
+
return getResolvedStorage(resolveStorage)?.getItem(key) ?? null;
|
|
41
|
+
},
|
|
42
|
+
setItem(key, value) {
|
|
43
|
+
getResolvedStorage(resolveStorage)?.setItem(key, value);
|
|
44
|
+
},
|
|
45
|
+
removeItem(key) {
|
|
46
|
+
getResolvedStorage(resolveStorage)?.removeItem(key);
|
|
47
|
+
},
|
|
48
|
+
clear() {
|
|
49
|
+
const storage = getResolvedStorage(resolveStorage);
|
|
50
|
+
if (!storage) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
listKeys().forEach(key => {
|
|
54
|
+
storage.removeItem(key);
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
getAllKeys() {
|
|
58
|
+
return listKeys();
|
|
59
|
+
},
|
|
60
|
+
getMany(keys) {
|
|
61
|
+
const storage = getResolvedStorage(resolveStorage);
|
|
62
|
+
if (!storage) {
|
|
63
|
+
return keys.map(() => null);
|
|
64
|
+
}
|
|
65
|
+
return keys.map(key => storage.getItem(key));
|
|
66
|
+
},
|
|
67
|
+
setMany(entries) {
|
|
68
|
+
const storage = getResolvedStorage(resolveStorage);
|
|
69
|
+
if (!storage) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
entries.forEach(([key, value]) => {
|
|
73
|
+
storage.setItem(key, value);
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
removeMany(keys) {
|
|
77
|
+
const storage = getResolvedStorage(resolveStorage);
|
|
78
|
+
if (!storage) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
keys.forEach(key => {
|
|
82
|
+
storage.removeItem(key);
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
size() {
|
|
86
|
+
return listKeys().length;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=web-storage-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["getResolvedStorage","resolveStorage","globalThis","localStorage","undefined","createLocalStorageWebBackend","options","includeKey","listKeys","storage","keys","index","length","key","push","name","getItem","setItem","value","removeItem","clear","forEach","getAllKeys","getMany","map","setMany","entries","removeMany","size"],"sourceRoot":"../../src","sources":["web-storage-backend.ts"],"mappings":"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","ignoreList":[]}
|
package/lib/module/index.js
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
import { NitroModules } from "react-native-nitro-modules";
|
|
4
4
|
import { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
|
|
5
5
|
import { MIGRATION_VERSION_KEY, isStoredEnvelope, assertBatchScope, assertValidScope, decodeNativeBatchValue, serializeWithPrimitiveFastPath, deserializeWithPrimitiveFastPath, toVersionToken, prefixKey, isNamespaced } from "./internal";
|
|
6
|
+
import { getStorageErrorCode, isLockedStorageErrorCode } from "./storage-runtime";
|
|
6
7
|
export { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
|
|
7
8
|
export { migrateFromMMKV } from "./migration";
|
|
9
|
+
export { getStorageErrorCode } from "./storage-runtime";
|
|
8
10
|
function asInternal(item) {
|
|
9
11
|
return item;
|
|
10
12
|
}
|
|
@@ -18,6 +20,7 @@ const registeredMigrations = new Map();
|
|
|
18
20
|
const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
|
|
19
21
|
Promise.resolve().then(task);
|
|
20
22
|
};
|
|
23
|
+
const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
|
|
21
24
|
let _storageModule = null;
|
|
22
25
|
function getStorageModule() {
|
|
23
26
|
if (!_storageModule) {
|
|
@@ -30,11 +33,15 @@ const memoryListeners = new Map();
|
|
|
30
33
|
const scopedListeners = new Map([[StorageScope.Disk, new Map()], [StorageScope.Secure, new Map()]]);
|
|
31
34
|
const scopedUnsubscribers = new Map();
|
|
32
35
|
const scopedRawCache = new Map([[StorageScope.Disk, new Map()], [StorageScope.Secure, new Map()]]);
|
|
36
|
+
const pendingDiskWrites = new Map();
|
|
37
|
+
let diskFlushScheduled = false;
|
|
38
|
+
let diskWritesAsync = false;
|
|
33
39
|
const pendingSecureWrites = new Map();
|
|
34
40
|
let secureFlushScheduled = false;
|
|
35
41
|
let secureDefaultAccessControl = AccessControl.WhenUnlocked;
|
|
36
42
|
let metricsObserver;
|
|
37
43
|
const metricsCounters = new Map();
|
|
44
|
+
const nativeSecureBackend = "platform-secure-storage";
|
|
38
45
|
function recordMetric(operation, scope, durationMs, keysCount = 1) {
|
|
39
46
|
const existing = metricsCounters.get(operation);
|
|
40
47
|
if (!existing) {
|
|
@@ -59,11 +66,11 @@ function measureOperation(operation, scope, fn, keysCount = 1) {
|
|
|
59
66
|
if (!metricsObserver) {
|
|
60
67
|
return fn();
|
|
61
68
|
}
|
|
62
|
-
const start =
|
|
69
|
+
const start = now();
|
|
63
70
|
try {
|
|
64
71
|
return fn();
|
|
65
72
|
} finally {
|
|
66
|
-
recordMetric(operation, scope,
|
|
73
|
+
recordMetric(operation, scope, now() - start, keysCount);
|
|
67
74
|
}
|
|
68
75
|
}
|
|
69
76
|
function getScopedListeners(scope) {
|
|
@@ -120,12 +127,50 @@ function addKeyListener(registry, key, listener) {
|
|
|
120
127
|
function readPendingSecureWrite(key) {
|
|
121
128
|
return pendingSecureWrites.get(key)?.value;
|
|
122
129
|
}
|
|
130
|
+
function readPendingDiskWrite(key) {
|
|
131
|
+
return pendingDiskWrites.get(key)?.value;
|
|
132
|
+
}
|
|
133
|
+
function hasPendingDiskWrite(key) {
|
|
134
|
+
return pendingDiskWrites.has(key);
|
|
135
|
+
}
|
|
123
136
|
function hasPendingSecureWrite(key) {
|
|
124
137
|
return pendingSecureWrites.has(key);
|
|
125
138
|
}
|
|
139
|
+
function clearPendingDiskWrite(key) {
|
|
140
|
+
pendingDiskWrites.delete(key);
|
|
141
|
+
}
|
|
126
142
|
function clearPendingSecureWrite(key) {
|
|
127
143
|
pendingSecureWrites.delete(key);
|
|
128
144
|
}
|
|
145
|
+
function flushDiskWrites() {
|
|
146
|
+
diskFlushScheduled = false;
|
|
147
|
+
if (pendingDiskWrites.size === 0) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const writes = Array.from(pendingDiskWrites.values());
|
|
151
|
+
pendingDiskWrites.clear();
|
|
152
|
+
const keysToSet = [];
|
|
153
|
+
const valuesToSet = [];
|
|
154
|
+
const keysToRemove = [];
|
|
155
|
+
writes.forEach(({
|
|
156
|
+
key,
|
|
157
|
+
value
|
|
158
|
+
}) => {
|
|
159
|
+
if (value === undefined) {
|
|
160
|
+
keysToRemove.push(key);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
keysToSet.push(key);
|
|
164
|
+
valuesToSet.push(value);
|
|
165
|
+
});
|
|
166
|
+
const storageModule = getStorageModule();
|
|
167
|
+
if (keysToSet.length > 0) {
|
|
168
|
+
storageModule.setBatch(keysToSet, valuesToSet, StorageScope.Disk);
|
|
169
|
+
}
|
|
170
|
+
if (keysToRemove.length > 0) {
|
|
171
|
+
storageModule.removeBatch(keysToRemove, StorageScope.Disk);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
129
174
|
function flushSecureWrites() {
|
|
130
175
|
secureFlushScheduled = false;
|
|
131
176
|
if (pendingSecureWrites.size === 0) {
|
|
@@ -165,6 +210,17 @@ function flushSecureWrites() {
|
|
|
165
210
|
storageModule.removeBatch(keysToRemove, StorageScope.Secure);
|
|
166
211
|
}
|
|
167
212
|
}
|
|
213
|
+
function scheduleDiskWrite(key, value) {
|
|
214
|
+
pendingDiskWrites.set(key, {
|
|
215
|
+
key,
|
|
216
|
+
value
|
|
217
|
+
});
|
|
218
|
+
if (diskFlushScheduled) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
diskFlushScheduled = true;
|
|
222
|
+
runMicrotask(flushDiskWrites);
|
|
223
|
+
}
|
|
168
224
|
function scheduleSecureWrite(key, value, accessControl) {
|
|
169
225
|
const pendingWrite = {
|
|
170
226
|
key,
|
|
@@ -185,6 +241,13 @@ function ensureNativeScopeSubscription(scope) {
|
|
|
185
241
|
return;
|
|
186
242
|
}
|
|
187
243
|
const unsubscribe = getStorageModule().addOnChange(scope, (key, value) => {
|
|
244
|
+
if (scope === StorageScope.Disk) {
|
|
245
|
+
if (key === "") {
|
|
246
|
+
pendingDiskWrites.clear();
|
|
247
|
+
} else {
|
|
248
|
+
clearPendingDiskWrite(key);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
188
251
|
if (scope === StorageScope.Secure) {
|
|
189
252
|
if (key === "") {
|
|
190
253
|
pendingSecureWrites.clear();
|
|
@@ -220,6 +283,9 @@ function getRawValue(key, scope) {
|
|
|
220
283
|
const value = memoryStore.get(key);
|
|
221
284
|
return typeof value === "string" ? value : undefined;
|
|
222
285
|
}
|
|
286
|
+
if (scope === StorageScope.Disk && hasPendingDiskWrite(key)) {
|
|
287
|
+
return readPendingDiskWrite(key);
|
|
288
|
+
}
|
|
223
289
|
if (scope === StorageScope.Secure && hasPendingSecureWrite(key)) {
|
|
224
290
|
return readPendingSecureWrite(key);
|
|
225
291
|
}
|
|
@@ -232,6 +298,15 @@ function setRawValue(key, value, scope) {
|
|
|
232
298
|
notifyKeyListeners(memoryListeners, key);
|
|
233
299
|
return;
|
|
234
300
|
}
|
|
301
|
+
if (scope === StorageScope.Disk) {
|
|
302
|
+
cacheRawValue(scope, key, value);
|
|
303
|
+
if (diskWritesAsync) {
|
|
304
|
+
scheduleDiskWrite(key, value);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
flushDiskWrites();
|
|
308
|
+
clearPendingDiskWrite(key);
|
|
309
|
+
}
|
|
235
310
|
if (scope === StorageScope.Secure) {
|
|
236
311
|
flushSecureWrites();
|
|
237
312
|
clearPendingSecureWrite(key);
|
|
@@ -247,6 +322,15 @@ function removeRawValue(key, scope) {
|
|
|
247
322
|
notifyKeyListeners(memoryListeners, key);
|
|
248
323
|
return;
|
|
249
324
|
}
|
|
325
|
+
if (scope === StorageScope.Disk) {
|
|
326
|
+
cacheRawValue(scope, key, undefined);
|
|
327
|
+
if (diskWritesAsync) {
|
|
328
|
+
scheduleDiskWrite(key, undefined);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
flushDiskWrites();
|
|
332
|
+
clearPendingDiskWrite(key);
|
|
333
|
+
}
|
|
250
334
|
if (scope === StorageScope.Secure) {
|
|
251
335
|
flushSecureWrites();
|
|
252
336
|
clearPendingSecureWrite(key);
|
|
@@ -273,6 +357,10 @@ export const storage = {
|
|
|
273
357
|
notifyAllListeners(memoryListeners);
|
|
274
358
|
return;
|
|
275
359
|
}
|
|
360
|
+
if (scope === StorageScope.Disk) {
|
|
361
|
+
flushDiskWrites();
|
|
362
|
+
pendingDiskWrites.clear();
|
|
363
|
+
}
|
|
276
364
|
if (scope === StorageScope.Secure) {
|
|
277
365
|
flushSecureWrites();
|
|
278
366
|
pendingSecureWrites.clear();
|
|
@@ -301,6 +389,9 @@ export const storage = {
|
|
|
301
389
|
return;
|
|
302
390
|
}
|
|
303
391
|
const keyPrefix = prefixKey(namespace, "");
|
|
392
|
+
if (scope === StorageScope.Disk) {
|
|
393
|
+
flushDiskWrites();
|
|
394
|
+
}
|
|
304
395
|
if (scope === StorageScope.Secure) {
|
|
305
396
|
flushSecureWrites();
|
|
306
397
|
}
|
|
@@ -324,6 +415,12 @@ export const storage = {
|
|
|
324
415
|
if (scope === StorageScope.Memory) {
|
|
325
416
|
return memoryStore.has(key);
|
|
326
417
|
}
|
|
418
|
+
if (scope === StorageScope.Disk) {
|
|
419
|
+
flushDiskWrites();
|
|
420
|
+
}
|
|
421
|
+
if (scope === StorageScope.Secure) {
|
|
422
|
+
flushSecureWrites();
|
|
423
|
+
}
|
|
327
424
|
return getStorageModule().has(key, scope);
|
|
328
425
|
});
|
|
329
426
|
},
|
|
@@ -333,6 +430,12 @@ export const storage = {
|
|
|
333
430
|
if (scope === StorageScope.Memory) {
|
|
334
431
|
return Array.from(memoryStore.keys());
|
|
335
432
|
}
|
|
433
|
+
if (scope === StorageScope.Disk) {
|
|
434
|
+
flushDiskWrites();
|
|
435
|
+
}
|
|
436
|
+
if (scope === StorageScope.Secure) {
|
|
437
|
+
flushSecureWrites();
|
|
438
|
+
}
|
|
336
439
|
return getStorageModule().getAllKeys(scope);
|
|
337
440
|
});
|
|
338
441
|
},
|
|
@@ -342,6 +445,12 @@ export const storage = {
|
|
|
342
445
|
if (scope === StorageScope.Memory) {
|
|
343
446
|
return Array.from(memoryStore.keys()).filter(key => key.startsWith(prefix));
|
|
344
447
|
}
|
|
448
|
+
if (scope === StorageScope.Disk) {
|
|
449
|
+
flushDiskWrites();
|
|
450
|
+
}
|
|
451
|
+
if (scope === StorageScope.Secure) {
|
|
452
|
+
flushSecureWrites();
|
|
453
|
+
}
|
|
345
454
|
return getStorageModule().getKeysByPrefix(prefix, scope);
|
|
346
455
|
});
|
|
347
456
|
},
|
|
@@ -361,6 +470,12 @@ export const storage = {
|
|
|
361
470
|
});
|
|
362
471
|
return result;
|
|
363
472
|
}
|
|
473
|
+
if (scope === StorageScope.Disk) {
|
|
474
|
+
flushDiskWrites();
|
|
475
|
+
}
|
|
476
|
+
if (scope === StorageScope.Secure) {
|
|
477
|
+
flushSecureWrites();
|
|
478
|
+
}
|
|
364
479
|
const values = getStorageModule().getBatch(keys, scope);
|
|
365
480
|
keys.forEach((key, idx) => {
|
|
366
481
|
const value = decodeNativeBatchValue(values[idx]);
|
|
@@ -381,6 +496,12 @@ export const storage = {
|
|
|
381
496
|
});
|
|
382
497
|
return result;
|
|
383
498
|
}
|
|
499
|
+
if (scope === StorageScope.Disk) {
|
|
500
|
+
flushDiskWrites();
|
|
501
|
+
}
|
|
502
|
+
if (scope === StorageScope.Secure) {
|
|
503
|
+
flushSecureWrites();
|
|
504
|
+
}
|
|
384
505
|
const keys = getStorageModule().getAllKeys(scope);
|
|
385
506
|
if (keys.length === 0) return result;
|
|
386
507
|
const values = getStorageModule().getBatch(keys, scope);
|
|
@@ -397,6 +518,12 @@ export const storage = {
|
|
|
397
518
|
if (scope === StorageScope.Memory) {
|
|
398
519
|
return memoryStore.size;
|
|
399
520
|
}
|
|
521
|
+
if (scope === StorageScope.Disk) {
|
|
522
|
+
flushDiskWrites();
|
|
523
|
+
}
|
|
524
|
+
if (scope === StorageScope.Secure) {
|
|
525
|
+
flushSecureWrites();
|
|
526
|
+
}
|
|
400
527
|
return getStorageModule().size(scope);
|
|
401
528
|
});
|
|
402
529
|
},
|
|
@@ -411,6 +538,19 @@ export const storage = {
|
|
|
411
538
|
getStorageModule().setSecureWritesAsync(enabled);
|
|
412
539
|
});
|
|
413
540
|
},
|
|
541
|
+
setDiskWritesAsync: enabled => {
|
|
542
|
+
measureOperation("storage:setDiskWritesAsync", StorageScope.Disk, () => {
|
|
543
|
+
diskWritesAsync = enabled;
|
|
544
|
+
if (!enabled) {
|
|
545
|
+
flushDiskWrites();
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
},
|
|
549
|
+
flushDiskWrites: () => {
|
|
550
|
+
measureOperation("storage:flushDiskWrites", StorageScope.Disk, () => {
|
|
551
|
+
flushDiskWrites();
|
|
552
|
+
});
|
|
553
|
+
},
|
|
414
554
|
flushSecureWrites: () => {
|
|
415
555
|
measureOperation("storage:flushSecureWrites", StorageScope.Secure, () => {
|
|
416
556
|
flushSecureWrites();
|
|
@@ -439,6 +579,67 @@ export const storage = {
|
|
|
439
579
|
resetMetrics: () => {
|
|
440
580
|
metricsCounters.clear();
|
|
441
581
|
},
|
|
582
|
+
getCapabilities: () => ({
|
|
583
|
+
platform: "native",
|
|
584
|
+
backend: {
|
|
585
|
+
disk: "platform-preferences",
|
|
586
|
+
secure: nativeSecureBackend
|
|
587
|
+
},
|
|
588
|
+
writeBuffering: {
|
|
589
|
+
disk: true,
|
|
590
|
+
secure: true
|
|
591
|
+
},
|
|
592
|
+
errorClassification: true
|
|
593
|
+
}),
|
|
594
|
+
getSecurityCapabilities: () => ({
|
|
595
|
+
platform: "native",
|
|
596
|
+
secureStorage: {
|
|
597
|
+
backend: nativeSecureBackend,
|
|
598
|
+
encrypted: "available",
|
|
599
|
+
accessControl: "unknown",
|
|
600
|
+
keychainAccessGroup: "unknown",
|
|
601
|
+
hardwareBacked: "unknown"
|
|
602
|
+
},
|
|
603
|
+
biometric: {
|
|
604
|
+
storage: "unknown",
|
|
605
|
+
prompt: "unknown",
|
|
606
|
+
biometryOnly: "unknown",
|
|
607
|
+
biometryOrPasscode: "unknown"
|
|
608
|
+
},
|
|
609
|
+
metadata: {
|
|
610
|
+
perKey: true,
|
|
611
|
+
listsWithoutValues: true,
|
|
612
|
+
persistsTimestamps: false
|
|
613
|
+
}
|
|
614
|
+
}),
|
|
615
|
+
getSecureMetadata: key => {
|
|
616
|
+
return measureOperation("storage:getSecureMetadata", StorageScope.Secure, () => {
|
|
617
|
+
flushSecureWrites();
|
|
618
|
+
const storageModule = getStorageModule();
|
|
619
|
+
const biometricProtected = storageModule.hasSecureBiometric(key);
|
|
620
|
+
const exists = biometricProtected || storageModule.has(key, StorageScope.Secure);
|
|
621
|
+
let kind = "missing";
|
|
622
|
+
if (exists) {
|
|
623
|
+
kind = biometricProtected ? "biometric" : "secure";
|
|
624
|
+
}
|
|
625
|
+
return {
|
|
626
|
+
key,
|
|
627
|
+
exists,
|
|
628
|
+
kind,
|
|
629
|
+
backend: nativeSecureBackend,
|
|
630
|
+
encrypted: "available",
|
|
631
|
+
hardwareBacked: "unknown",
|
|
632
|
+
biometricProtected,
|
|
633
|
+
valueExposed: false
|
|
634
|
+
};
|
|
635
|
+
});
|
|
636
|
+
},
|
|
637
|
+
getAllSecureMetadata: () => {
|
|
638
|
+
return measureOperation("storage:getAllSecureMetadata", StorageScope.Secure, () => {
|
|
639
|
+
flushSecureWrites();
|
|
640
|
+
return getStorageModule().getAllKeys(StorageScope.Secure).map(key => storage.getSecureMetadata(key));
|
|
641
|
+
});
|
|
642
|
+
},
|
|
442
643
|
getString: (key, scope) => {
|
|
443
644
|
return measureOperation("storage:getString", scope, () => {
|
|
444
645
|
return getRawValue(key, scope);
|
|
@@ -482,6 +683,15 @@ export function setWebSecureStorageBackend(_backend) {
|
|
|
482
683
|
export function getWebSecureStorageBackend() {
|
|
483
684
|
return undefined;
|
|
484
685
|
}
|
|
686
|
+
export function setWebDiskStorageBackend(_backend) {
|
|
687
|
+
// Native platforms do not use web disk backends.
|
|
688
|
+
}
|
|
689
|
+
export function getWebDiskStorageBackend() {
|
|
690
|
+
return undefined;
|
|
691
|
+
}
|
|
692
|
+
export async function flushWebStorageBackends() {
|
|
693
|
+
// Native platforms do not use web storage backends.
|
|
694
|
+
}
|
|
485
695
|
function canUseRawBatchPath(item) {
|
|
486
696
|
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
|
|
487
697
|
}
|
|
@@ -509,6 +719,7 @@ export function createStorageItem(config) {
|
|
|
509
719
|
const expirationTtlMs = expiration?.ttlMs;
|
|
510
720
|
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
511
721
|
const readCache = !isMemory && config.readCache === true;
|
|
722
|
+
const coalesceDiskWrites = config.scope === StorageScope.Disk && config.coalesceDiskWrites === true;
|
|
512
723
|
const coalesceSecureWrites = config.scope === StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
|
|
513
724
|
const defaultValue = config.defaultValue;
|
|
514
725
|
const nonMemoryScope = config.scope === StorageScope.Disk ? StorageScope.Disk : config.scope === StorageScope.Secure ? StorageScope.Secure : null;
|
|
@@ -556,6 +767,12 @@ export function createStorageItem(config) {
|
|
|
556
767
|
}
|
|
557
768
|
return memoryStore.get(storageKey);
|
|
558
769
|
}
|
|
770
|
+
if (nonMemoryScope === StorageScope.Disk) {
|
|
771
|
+
const pending = pendingDiskWrites.get(storageKey);
|
|
772
|
+
if (pending !== undefined) {
|
|
773
|
+
return pending.value;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
559
776
|
if (nonMemoryScope === StorageScope.Secure && !isBiometric) {
|
|
560
777
|
const pending = pendingSecureWrites.get(storageKey);
|
|
561
778
|
if (pending !== undefined) {
|
|
@@ -582,6 +799,13 @@ export function createStorageItem(config) {
|
|
|
582
799
|
return;
|
|
583
800
|
}
|
|
584
801
|
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
802
|
+
if (nonMemoryScope === StorageScope.Disk) {
|
|
803
|
+
if (coalesceDiskWrites || diskWritesAsync) {
|
|
804
|
+
scheduleDiskWrite(storageKey, rawValue);
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
clearPendingDiskWrite(storageKey);
|
|
808
|
+
}
|
|
585
809
|
if (coalesceSecureWrites) {
|
|
586
810
|
scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
|
|
587
811
|
return;
|
|
@@ -598,6 +822,13 @@ export function createStorageItem(config) {
|
|
|
598
822
|
return;
|
|
599
823
|
}
|
|
600
824
|
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
825
|
+
if (nonMemoryScope === StorageScope.Disk) {
|
|
826
|
+
if (coalesceDiskWrites || diskWritesAsync) {
|
|
827
|
+
scheduleDiskWrite(storageKey, undefined);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
clearPendingDiskWrite(storageKey);
|
|
831
|
+
}
|
|
601
832
|
if (coalesceSecureWrites) {
|
|
602
833
|
scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
|
|
603
834
|
return;
|
|
@@ -758,6 +989,18 @@ export function createStorageItem(config) {
|
|
|
758
989
|
const hasItem = () => measureOperation("item:has", config.scope, () => {
|
|
759
990
|
if (isMemory) return memoryStore.has(storageKey);
|
|
760
991
|
if (isBiometric) return getStorageModule().hasSecureBiometric(storageKey);
|
|
992
|
+
if (nonMemoryScope === StorageScope.Disk) {
|
|
993
|
+
const pending = pendingDiskWrites.get(storageKey);
|
|
994
|
+
if (pending !== undefined) {
|
|
995
|
+
return pending.value !== undefined;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
if (nonMemoryScope === StorageScope.Secure) {
|
|
999
|
+
const pending = pendingSecureWrites.get(storageKey);
|
|
1000
|
+
if (pending !== undefined) {
|
|
1001
|
+
return pending.value !== undefined;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
761
1004
|
return getStorageModule().has(storageKey, config.scope);
|
|
762
1005
|
});
|
|
763
1006
|
const subscribe = callback => {
|
|
@@ -820,6 +1063,13 @@ export function getBatch(items, scope) {
|
|
|
820
1063
|
const keysToFetch = [];
|
|
821
1064
|
const keyIndexes = [];
|
|
822
1065
|
items.forEach((item, index) => {
|
|
1066
|
+
if (scope === StorageScope.Disk) {
|
|
1067
|
+
const pending = pendingDiskWrites.get(item.key);
|
|
1068
|
+
if (pending !== undefined) {
|
|
1069
|
+
rawValues[index] = pending.value;
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
823
1073
|
if (scope === StorageScope.Secure) {
|
|
824
1074
|
const pending = pendingSecureWrites.get(item.key);
|
|
825
1075
|
if (pending !== undefined) {
|
|
@@ -938,6 +1188,7 @@ export function setBatch(items, scope) {
|
|
|
938
1188
|
});
|
|
939
1189
|
return;
|
|
940
1190
|
}
|
|
1191
|
+
flushDiskWrites();
|
|
941
1192
|
const useRawBatchPath = items.every(({
|
|
942
1193
|
item
|
|
943
1194
|
}) => canUseRawBatchPath(asInternal(item)));
|
|
@@ -962,6 +1213,9 @@ export function removeBatch(items, scope) {
|
|
|
962
1213
|
return;
|
|
963
1214
|
}
|
|
964
1215
|
const keys = items.map(item => item.key);
|
|
1216
|
+
if (scope === StorageScope.Disk) {
|
|
1217
|
+
flushDiskWrites();
|
|
1218
|
+
}
|
|
965
1219
|
if (scope === StorageScope.Secure) {
|
|
966
1220
|
flushSecureWrites();
|
|
967
1221
|
}
|
|
@@ -1007,6 +1261,9 @@ export function migrateToLatest(scope = StorageScope.Disk) {
|
|
|
1007
1261
|
export function runTransaction(scope, transaction) {
|
|
1008
1262
|
return measureOperation("transaction:run", scope, () => {
|
|
1009
1263
|
assertValidScope(scope);
|
|
1264
|
+
if (scope === StorageScope.Disk) {
|
|
1265
|
+
flushDiskWrites();
|
|
1266
|
+
}
|
|
1010
1267
|
if (scope === StorageScope.Secure) {
|
|
1011
1268
|
flushSecureWrites();
|
|
1012
1269
|
}
|
|
@@ -1073,6 +1330,9 @@ export function runTransaction(scope, transaction) {
|
|
|
1073
1330
|
valuesToSet.push(previousValue);
|
|
1074
1331
|
}
|
|
1075
1332
|
});
|
|
1333
|
+
if (scope === StorageScope.Disk) {
|
|
1334
|
+
flushDiskWrites();
|
|
1335
|
+
}
|
|
1076
1336
|
if (scope === StorageScope.Secure) {
|
|
1077
1337
|
flushSecureWrites();
|
|
1078
1338
|
}
|
|
@@ -1090,9 +1350,7 @@ export function runTransaction(scope, transaction) {
|
|
|
1090
1350
|
});
|
|
1091
1351
|
}
|
|
1092
1352
|
export function isKeychainLockedError(err) {
|
|
1093
|
-
|
|
1094
|
-
const msg = err.message;
|
|
1095
|
-
return msg.includes("errSecInteractionNotAllowed") || msg.includes("UserNotAuthenticatedException") || msg.includes("KeyStoreException") || msg.includes("KeyPermanentlyInvalidatedException") || msg.includes("InvalidKeyException") || msg.includes("android.security.keystore");
|
|
1353
|
+
return isLockedStorageErrorCode(getStorageErrorCode(err));
|
|
1096
1354
|
}
|
|
1097
1355
|
export function createSecureAuthStorage(config, options) {
|
|
1098
1356
|
const ns = options?.namespace ?? "auth";
|