react-native-nitro-storage 0.3.1 → 0.4.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 +334 -34
- package/android/CMakeLists.txt +2 -0
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +26 -2
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +4 -0
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +90 -18
- package/cpp/bindings/HybridStorage.cpp +214 -23
- package/cpp/bindings/HybridStorage.hpp +31 -3
- package/cpp/core/NativeStorageAdapter.hpp +4 -0
- package/ios/IOSStorageAdapterCpp.hpp +17 -0
- package/ios/IOSStorageAdapterCpp.mm +140 -10
- package/lib/commonjs/index.js +555 -288
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +750 -309
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/internal.js +25 -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/index.js +537 -287
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +732 -308
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/internal.js +24 -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 +4 -0
- package/lib/typescript/Storage.nitro.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +41 -4
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +45 -4
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts +1 -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 +4 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +4 -0
- package/package.json +5 -3
- package/src/Storage.nitro.ts +4 -0
- package/src/index.ts +704 -324
- package/src/index.web.ts +929 -346
- package/src/internal.ts +28 -0
- package/src/storage-hooks.ts +48 -0
|
@@ -24,6 +24,7 @@ Object.defineProperty(exports, "StorageScope", {
|
|
|
24
24
|
exports.createSecureAuthStorage = createSecureAuthStorage;
|
|
25
25
|
exports.createStorageItem = createStorageItem;
|
|
26
26
|
exports.getBatch = getBatch;
|
|
27
|
+
exports.getWebSecureStorageBackend = getWebSecureStorageBackend;
|
|
27
28
|
Object.defineProperty(exports, "migrateFromMMKV", {
|
|
28
29
|
enumerable: true,
|
|
29
30
|
get: function () {
|
|
@@ -35,17 +36,39 @@ exports.registerMigration = registerMigration;
|
|
|
35
36
|
exports.removeBatch = removeBatch;
|
|
36
37
|
exports.runTransaction = runTransaction;
|
|
37
38
|
exports.setBatch = setBatch;
|
|
39
|
+
exports.setWebSecureStorageBackend = setWebSecureStorageBackend;
|
|
38
40
|
exports.storage = void 0;
|
|
39
|
-
exports
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
Object.defineProperty(exports, "useSetStorage", {
|
|
42
|
+
enumerable: true,
|
|
43
|
+
get: function () {
|
|
44
|
+
return _storageHooks.useSetStorage;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
Object.defineProperty(exports, "useStorage", {
|
|
48
|
+
enumerable: true,
|
|
49
|
+
get: function () {
|
|
50
|
+
return _storageHooks.useStorage;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
Object.defineProperty(exports, "useStorageSelector", {
|
|
54
|
+
enumerable: true,
|
|
55
|
+
get: function () {
|
|
56
|
+
return _storageHooks.useStorageSelector;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
43
59
|
var _Storage = require("./Storage.types");
|
|
44
60
|
var _internal = require("./internal");
|
|
45
61
|
var _migration = require("./migration");
|
|
62
|
+
var _storageHooks = require("./storage-hooks");
|
|
46
63
|
function asInternal(item) {
|
|
47
64
|
return item;
|
|
48
65
|
}
|
|
66
|
+
function isUpdater(valueOrFn) {
|
|
67
|
+
return typeof valueOrFn === "function";
|
|
68
|
+
}
|
|
69
|
+
function typedKeys(record) {
|
|
70
|
+
return Object.keys(record);
|
|
71
|
+
}
|
|
49
72
|
const registeredMigrations = new Map();
|
|
50
73
|
const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
|
|
51
74
|
Promise.resolve().then(task);
|
|
@@ -54,17 +77,83 @@ const memoryStore = new Map();
|
|
|
54
77
|
const memoryListeners = new Map();
|
|
55
78
|
const webScopeListeners = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
|
|
56
79
|
const scopedRawCache = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
|
|
80
|
+
const webScopeKeyIndex = new Map([[_Storage.StorageScope.Disk, new Set()], [_Storage.StorageScope.Secure, new Set()]]);
|
|
81
|
+
const hydratedWebScopeKeyIndex = new Set();
|
|
57
82
|
const pendingSecureWrites = new Map();
|
|
58
83
|
let secureFlushScheduled = false;
|
|
59
84
|
const SECURE_WEB_PREFIX = "__secure_";
|
|
60
85
|
const BIOMETRIC_WEB_PREFIX = "__bio_";
|
|
61
86
|
let hasWarnedAboutWebBiometricFallback = false;
|
|
87
|
+
let hasWebStorageEventSubscription = false;
|
|
88
|
+
let metricsObserver;
|
|
89
|
+
const metricsCounters = new Map();
|
|
90
|
+
function recordMetric(operation, scope, durationMs, keysCount = 1) {
|
|
91
|
+
const existing = metricsCounters.get(operation);
|
|
92
|
+
if (!existing) {
|
|
93
|
+
metricsCounters.set(operation, {
|
|
94
|
+
count: 1,
|
|
95
|
+
totalDurationMs: durationMs,
|
|
96
|
+
maxDurationMs: durationMs
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
existing.count += 1;
|
|
100
|
+
existing.totalDurationMs += durationMs;
|
|
101
|
+
existing.maxDurationMs = Math.max(existing.maxDurationMs, durationMs);
|
|
102
|
+
}
|
|
103
|
+
metricsObserver?.({
|
|
104
|
+
operation,
|
|
105
|
+
scope,
|
|
106
|
+
durationMs,
|
|
107
|
+
keysCount
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function measureOperation(operation, scope, fn, keysCount = 1) {
|
|
111
|
+
const start = Date.now();
|
|
112
|
+
try {
|
|
113
|
+
return fn();
|
|
114
|
+
} finally {
|
|
115
|
+
recordMetric(operation, scope, Date.now() - start, keysCount);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function createLocalStorageWebSecureBackend() {
|
|
119
|
+
return {
|
|
120
|
+
getItem: key => globalThis.localStorage?.getItem(key) ?? null,
|
|
121
|
+
setItem: (key, value) => globalThis.localStorage?.setItem(key, value),
|
|
122
|
+
removeItem: key => globalThis.localStorage?.removeItem(key),
|
|
123
|
+
clear: () => globalThis.localStorage?.clear(),
|
|
124
|
+
getAllKeys: () => {
|
|
125
|
+
const storage = globalThis.localStorage;
|
|
126
|
+
if (!storage) return [];
|
|
127
|
+
const keys = [];
|
|
128
|
+
for (let index = 0; index < storage.length; index += 1) {
|
|
129
|
+
const key = storage.key(index);
|
|
130
|
+
if (key) {
|
|
131
|
+
keys.push(key);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return keys;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
let webSecureStorageBackend = createLocalStorageWebSecureBackend();
|
|
62
139
|
function getBrowserStorage(scope) {
|
|
63
140
|
if (scope === _Storage.StorageScope.Disk) {
|
|
64
141
|
return globalThis.localStorage;
|
|
65
142
|
}
|
|
66
143
|
if (scope === _Storage.StorageScope.Secure) {
|
|
67
|
-
|
|
144
|
+
if (!webSecureStorageBackend) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
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,
|
|
153
|
+
get length() {
|
|
154
|
+
return webSecureStorageBackend?.getAllKeys().length ?? 0;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
68
157
|
}
|
|
69
158
|
return undefined;
|
|
70
159
|
}
|
|
@@ -80,6 +169,99 @@ function toBiometricStorageKey(key) {
|
|
|
80
169
|
function fromBiometricStorageKey(key) {
|
|
81
170
|
return key.slice(BIOMETRIC_WEB_PREFIX.length);
|
|
82
171
|
}
|
|
172
|
+
function getWebScopeKeyIndex(scope) {
|
|
173
|
+
return webScopeKeyIndex.get(scope);
|
|
174
|
+
}
|
|
175
|
+
function hydrateWebScopeKeyIndex(scope) {
|
|
176
|
+
if (hydratedWebScopeKeyIndex.has(scope)) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const storage = getBrowserStorage(scope);
|
|
180
|
+
const keyIndex = getWebScopeKeyIndex(scope);
|
|
181
|
+
keyIndex.clear();
|
|
182
|
+
if (storage) {
|
|
183
|
+
for (let index = 0; index < storage.length; index += 1) {
|
|
184
|
+
const key = storage.key(index);
|
|
185
|
+
if (!key) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
189
|
+
if (!key.startsWith(SECURE_WEB_PREFIX) && !key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
190
|
+
keyIndex.add(key);
|
|
191
|
+
}
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (key.startsWith(SECURE_WEB_PREFIX)) {
|
|
195
|
+
keyIndex.add(fromSecureStorageKey(key));
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
199
|
+
keyIndex.add(fromBiometricStorageKey(key));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
hydratedWebScopeKeyIndex.add(scope);
|
|
204
|
+
}
|
|
205
|
+
function ensureWebScopeKeyIndex(scope) {
|
|
206
|
+
hydrateWebScopeKeyIndex(scope);
|
|
207
|
+
return getWebScopeKeyIndex(scope);
|
|
208
|
+
}
|
|
209
|
+
function handleWebStorageEvent(event) {
|
|
210
|
+
const key = event.key;
|
|
211
|
+
if (key === null) {
|
|
212
|
+
clearScopeRawCache(_Storage.StorageScope.Disk);
|
|
213
|
+
clearScopeRawCache(_Storage.StorageScope.Secure);
|
|
214
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Disk).clear();
|
|
215
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).clear();
|
|
216
|
+
notifyAllListeners(getScopedListeners(_Storage.StorageScope.Disk));
|
|
217
|
+
notifyAllListeners(getScopedListeners(_Storage.StorageScope.Secure));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (key.startsWith(SECURE_WEB_PREFIX)) {
|
|
221
|
+
const plainKey = fromSecureStorageKey(key);
|
|
222
|
+
if (event.newValue === null) {
|
|
223
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(plainKey);
|
|
224
|
+
cacheRawValue(_Storage.StorageScope.Secure, plainKey, undefined);
|
|
225
|
+
} else {
|
|
226
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(plainKey);
|
|
227
|
+
cacheRawValue(_Storage.StorageScope.Secure, plainKey, event.newValue);
|
|
228
|
+
}
|
|
229
|
+
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), plainKey);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
233
|
+
const plainKey = fromBiometricStorageKey(key);
|
|
234
|
+
if (event.newValue === null) {
|
|
235
|
+
if (getBrowserStorage(_Storage.StorageScope.Secure)?.getItem(toSecureStorageKey(plainKey)) === null) {
|
|
236
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(plainKey);
|
|
237
|
+
}
|
|
238
|
+
cacheRawValue(_Storage.StorageScope.Secure, plainKey, undefined);
|
|
239
|
+
} else {
|
|
240
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(plainKey);
|
|
241
|
+
cacheRawValue(_Storage.StorageScope.Secure, plainKey, event.newValue);
|
|
242
|
+
}
|
|
243
|
+
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), plainKey);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (event.newValue === null) {
|
|
247
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Disk).delete(key);
|
|
248
|
+
cacheRawValue(_Storage.StorageScope.Disk, key, undefined);
|
|
249
|
+
} else {
|
|
250
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Disk).add(key);
|
|
251
|
+
cacheRawValue(_Storage.StorageScope.Disk, key, event.newValue);
|
|
252
|
+
}
|
|
253
|
+
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Disk), key);
|
|
254
|
+
}
|
|
255
|
+
function ensureWebStorageEventSubscription() {
|
|
256
|
+
if (hasWebStorageEventSubscription) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (typeof window === "undefined" || typeof window.addEventListener !== "function") {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
window.addEventListener("storage", handleWebStorageEvent);
|
|
263
|
+
hasWebStorageEventSubscription = true;
|
|
264
|
+
}
|
|
83
265
|
function getScopedListeners(scope) {
|
|
84
266
|
return webScopeListeners.get(scope);
|
|
85
267
|
}
|
|
@@ -140,32 +322,46 @@ function flushSecureWrites() {
|
|
|
140
322
|
}
|
|
141
323
|
const writes = Array.from(pendingSecureWrites.values());
|
|
142
324
|
pendingSecureWrites.clear();
|
|
143
|
-
const
|
|
144
|
-
const valuesToSet = [];
|
|
325
|
+
const groupedSetWrites = new Map();
|
|
145
326
|
const keysToRemove = [];
|
|
146
327
|
writes.forEach(({
|
|
147
328
|
key,
|
|
148
|
-
value
|
|
329
|
+
value,
|
|
330
|
+
accessControl
|
|
149
331
|
}) => {
|
|
150
332
|
if (value === undefined) {
|
|
151
333
|
keysToRemove.push(key);
|
|
152
334
|
} else {
|
|
153
|
-
|
|
154
|
-
|
|
335
|
+
const resolvedAccessControl = accessControl ?? _Storage.AccessControl.WhenUnlocked;
|
|
336
|
+
const existingGroup = groupedSetWrites.get(resolvedAccessControl);
|
|
337
|
+
const group = existingGroup ?? {
|
|
338
|
+
keys: [],
|
|
339
|
+
values: []
|
|
340
|
+
};
|
|
341
|
+
group.keys.push(key);
|
|
342
|
+
group.values.push(value);
|
|
343
|
+
if (!existingGroup) {
|
|
344
|
+
groupedSetWrites.set(resolvedAccessControl, group);
|
|
345
|
+
}
|
|
155
346
|
}
|
|
156
347
|
});
|
|
157
|
-
|
|
158
|
-
WebStorage.
|
|
159
|
-
|
|
348
|
+
groupedSetWrites.forEach((group, accessControl) => {
|
|
349
|
+
WebStorage.setSecureAccessControl(accessControl);
|
|
350
|
+
WebStorage.setBatch(group.keys, group.values, _Storage.StorageScope.Secure);
|
|
351
|
+
});
|
|
160
352
|
if (keysToRemove.length > 0) {
|
|
161
353
|
WebStorage.removeBatch(keysToRemove, _Storage.StorageScope.Secure);
|
|
162
354
|
}
|
|
163
355
|
}
|
|
164
|
-
function scheduleSecureWrite(key, value) {
|
|
165
|
-
|
|
356
|
+
function scheduleSecureWrite(key, value, accessControl) {
|
|
357
|
+
const pendingWrite = {
|
|
166
358
|
key,
|
|
167
359
|
value
|
|
168
|
-
}
|
|
360
|
+
};
|
|
361
|
+
if (accessControl !== undefined) {
|
|
362
|
+
pendingWrite.accessControl = accessControl;
|
|
363
|
+
}
|
|
364
|
+
pendingSecureWrites.set(key, pendingWrite);
|
|
169
365
|
if (secureFlushScheduled) {
|
|
170
366
|
return;
|
|
171
367
|
}
|
|
@@ -184,6 +380,7 @@ const WebStorage = {
|
|
|
184
380
|
const storageKey = scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key;
|
|
185
381
|
storage.setItem(storageKey, value);
|
|
186
382
|
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
383
|
+
ensureWebScopeKeyIndex(scope).add(key);
|
|
187
384
|
notifyKeyListeners(getScopedListeners(scope), key);
|
|
188
385
|
}
|
|
189
386
|
},
|
|
@@ -204,6 +401,7 @@ const WebStorage = {
|
|
|
204
401
|
storage.removeItem(key);
|
|
205
402
|
}
|
|
206
403
|
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
404
|
+
ensureWebScopeKeyIndex(scope).delete(key);
|
|
207
405
|
notifyKeyListeners(getScopedListeners(scope), key);
|
|
208
406
|
}
|
|
209
407
|
},
|
|
@@ -234,6 +432,7 @@ const WebStorage = {
|
|
|
234
432
|
storage.clear();
|
|
235
433
|
}
|
|
236
434
|
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
435
|
+
ensureWebScopeKeyIndex(scope).clear();
|
|
237
436
|
notifyAllListeners(getScopedListeners(scope));
|
|
238
437
|
}
|
|
239
438
|
},
|
|
@@ -243,10 +442,16 @@ const WebStorage = {
|
|
|
243
442
|
return;
|
|
244
443
|
}
|
|
245
444
|
keys.forEach((key, index) => {
|
|
445
|
+
const value = values[index];
|
|
446
|
+
if (value === undefined) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
246
449
|
const storageKey = scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key;
|
|
247
|
-
storage.setItem(storageKey,
|
|
450
|
+
storage.setItem(storageKey, value);
|
|
248
451
|
});
|
|
249
452
|
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
453
|
+
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
454
|
+
keys.forEach(key => keyIndex.add(key));
|
|
250
455
|
const listeners = getScopedListeners(scope);
|
|
251
456
|
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
252
457
|
}
|
|
@@ -259,9 +464,37 @@ const WebStorage = {
|
|
|
259
464
|
});
|
|
260
465
|
},
|
|
261
466
|
removeBatch: (keys, scope) => {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
467
|
+
const storage = getBrowserStorage(scope);
|
|
468
|
+
if (!storage) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
472
|
+
keys.forEach(key => {
|
|
473
|
+
storage.removeItem(toSecureStorageKey(key));
|
|
474
|
+
storage.removeItem(toBiometricStorageKey(key));
|
|
475
|
+
});
|
|
476
|
+
} else {
|
|
477
|
+
keys.forEach(key => {
|
|
478
|
+
storage.removeItem(key);
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
482
|
+
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
483
|
+
keys.forEach(key => keyIndex.delete(key));
|
|
484
|
+
const listeners = getScopedListeners(scope);
|
|
485
|
+
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
removeByPrefix: (prefix, scope) => {
|
|
489
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
493
|
+
const keys = Array.from(keyIndex).filter(key => key.startsWith(prefix));
|
|
494
|
+
if (keys.length === 0) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
WebStorage.removeBatch(keys, scope);
|
|
265
498
|
},
|
|
266
499
|
addOnChange: (_scope, _callback) => {
|
|
267
500
|
return () => {};
|
|
@@ -274,54 +507,54 @@ const WebStorage = {
|
|
|
274
507
|
return storage?.getItem(key) !== null;
|
|
275
508
|
},
|
|
276
509
|
getAllKeys: scope => {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (scope === _Storage.StorageScope.Secure) {
|
|
286
|
-
if (k.startsWith(SECURE_WEB_PREFIX)) {
|
|
287
|
-
keys.add(fromSecureStorageKey(k));
|
|
288
|
-
} else if (k.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
289
|
-
keys.add(fromBiometricStorageKey(k));
|
|
290
|
-
}
|
|
291
|
-
continue;
|
|
292
|
-
}
|
|
293
|
-
if (k.startsWith(SECURE_WEB_PREFIX) || k.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
keys.add(k);
|
|
510
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
511
|
+
return [];
|
|
512
|
+
}
|
|
513
|
+
return Array.from(ensureWebScopeKeyIndex(scope));
|
|
514
|
+
},
|
|
515
|
+
getKeysByPrefix: (prefix, scope) => {
|
|
516
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
517
|
+
return [];
|
|
297
518
|
}
|
|
298
|
-
return Array.from(
|
|
519
|
+
return Array.from(ensureWebScopeKeyIndex(scope)).filter(key => key.startsWith(prefix));
|
|
299
520
|
},
|
|
300
521
|
size: scope => {
|
|
301
|
-
|
|
522
|
+
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
523
|
+
return ensureWebScopeKeyIndex(scope).size;
|
|
524
|
+
}
|
|
525
|
+
return 0;
|
|
302
526
|
},
|
|
303
527
|
setSecureAccessControl: () => {},
|
|
528
|
+
setSecureWritesAsync: _enabled => {},
|
|
304
529
|
setKeychainAccessGroup: () => {},
|
|
305
530
|
setSecureBiometric: (key, value) => {
|
|
531
|
+
WebStorage.setSecureBiometricWithLevel(key, value, _Storage.BiometricLevel.BiometryOnly);
|
|
532
|
+
},
|
|
533
|
+
setSecureBiometricWithLevel: (key, value, _level) => {
|
|
306
534
|
if (typeof __DEV__ !== "undefined" && __DEV__ && !hasWarnedAboutWebBiometricFallback) {
|
|
307
535
|
hasWarnedAboutWebBiometricFallback = true;
|
|
308
536
|
console.warn("[NitroStorage] Biometric storage is not supported on web. Using localStorage.");
|
|
309
537
|
}
|
|
310
|
-
|
|
538
|
+
getBrowserStorage(_Storage.StorageScope.Secure)?.setItem(toBiometricStorageKey(key), value);
|
|
539
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(key);
|
|
311
540
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), key);
|
|
312
541
|
},
|
|
313
542
|
getSecureBiometric: key => {
|
|
314
|
-
return
|
|
543
|
+
return getBrowserStorage(_Storage.StorageScope.Secure)?.getItem(toBiometricStorageKey(key)) ?? undefined;
|
|
315
544
|
},
|
|
316
545
|
deleteSecureBiometric: key => {
|
|
317
|
-
|
|
546
|
+
const storage = getBrowserStorage(_Storage.StorageScope.Secure);
|
|
547
|
+
storage?.removeItem(toBiometricStorageKey(key));
|
|
548
|
+
if (storage?.getItem(toSecureStorageKey(key)) === null) {
|
|
549
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(key);
|
|
550
|
+
}
|
|
318
551
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), key);
|
|
319
552
|
},
|
|
320
553
|
hasSecureBiometric: key => {
|
|
321
|
-
return
|
|
554
|
+
return getBrowserStorage(_Storage.StorageScope.Secure)?.getItem(toBiometricStorageKey(key)) !== null;
|
|
322
555
|
},
|
|
323
556
|
clearSecureBiometric: () => {
|
|
324
|
-
const storage =
|
|
557
|
+
const storage = getBrowserStorage(_Storage.StorageScope.Secure);
|
|
325
558
|
if (!storage) return;
|
|
326
559
|
const keysToNotify = [];
|
|
327
560
|
const toRemove = [];
|
|
@@ -333,6 +566,12 @@ const WebStorage = {
|
|
|
333
566
|
}
|
|
334
567
|
}
|
|
335
568
|
toRemove.forEach(k => storage.removeItem(k));
|
|
569
|
+
const keyIndex = ensureWebScopeKeyIndex(_Storage.StorageScope.Secure);
|
|
570
|
+
keysToNotify.forEach(key => {
|
|
571
|
+
if (storage.getItem(toSecureStorageKey(key)) === null) {
|
|
572
|
+
keyIndex.delete(key);
|
|
573
|
+
}
|
|
574
|
+
});
|
|
336
575
|
const listeners = getScopedListeners(_Storage.StorageScope.Secure);
|
|
337
576
|
keysToNotify.forEach(key => notifyKeyListeners(listeners, key));
|
|
338
577
|
}
|
|
@@ -389,90 +628,173 @@ function writeMigrationVersion(scope, version) {
|
|
|
389
628
|
}
|
|
390
629
|
const storage = exports.storage = {
|
|
391
630
|
clear: scope => {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
631
|
+
measureOperation("storage:clear", scope, () => {
|
|
632
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
633
|
+
memoryStore.clear();
|
|
634
|
+
notifyAllListeners(memoryListeners);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
638
|
+
flushSecureWrites();
|
|
639
|
+
pendingSecureWrites.clear();
|
|
640
|
+
}
|
|
641
|
+
clearScopeRawCache(scope);
|
|
642
|
+
WebStorage.clear(scope);
|
|
643
|
+
});
|
|
406
644
|
},
|
|
407
645
|
clearAll: () => {
|
|
408
|
-
storage
|
|
409
|
-
|
|
410
|
-
|
|
646
|
+
measureOperation("storage:clearAll", _Storage.StorageScope.Memory, () => {
|
|
647
|
+
storage.clear(_Storage.StorageScope.Memory);
|
|
648
|
+
storage.clear(_Storage.StorageScope.Disk);
|
|
649
|
+
storage.clear(_Storage.StorageScope.Secure);
|
|
650
|
+
}, 3);
|
|
411
651
|
},
|
|
412
652
|
clearNamespace: (namespace, scope) => {
|
|
413
|
-
(
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
653
|
+
measureOperation("storage:clearNamespace", scope, () => {
|
|
654
|
+
(0, _internal.assertValidScope)(scope);
|
|
655
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
656
|
+
for (const key of memoryStore.keys()) {
|
|
657
|
+
if ((0, _internal.isNamespaced)(key, namespace)) {
|
|
658
|
+
memoryStore.delete(key);
|
|
659
|
+
}
|
|
418
660
|
}
|
|
661
|
+
notifyAllListeners(memoryListeners);
|
|
662
|
+
return;
|
|
419
663
|
}
|
|
420
|
-
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
if (scope === _Storage.StorageScope.Secure) {
|
|
424
|
-
flushSecureWrites();
|
|
425
|
-
}
|
|
426
|
-
const keys = WebStorage.getAllKeys(scope);
|
|
427
|
-
const namespacedKeys = keys.filter(k => (0, _internal.isNamespaced)(k, namespace));
|
|
428
|
-
if (namespacedKeys.length > 0) {
|
|
429
|
-
WebStorage.removeBatch(namespacedKeys, scope);
|
|
430
|
-
namespacedKeys.forEach(k => cacheRawValue(scope, k, undefined));
|
|
664
|
+
const keyPrefix = (0, _internal.prefixKey)(namespace, "");
|
|
431
665
|
if (scope === _Storage.StorageScope.Secure) {
|
|
432
|
-
|
|
666
|
+
flushSecureWrites();
|
|
433
667
|
}
|
|
434
|
-
|
|
668
|
+
clearScopeRawCache(scope);
|
|
669
|
+
WebStorage.removeByPrefix(keyPrefix, scope);
|
|
670
|
+
});
|
|
435
671
|
},
|
|
436
672
|
clearBiometric: () => {
|
|
437
|
-
|
|
673
|
+
measureOperation("storage:clearBiometric", _Storage.StorageScope.Secure, () => {
|
|
674
|
+
WebStorage.clearSecureBiometric();
|
|
675
|
+
});
|
|
438
676
|
},
|
|
439
677
|
has: (key, scope) => {
|
|
440
|
-
(
|
|
441
|
-
|
|
442
|
-
|
|
678
|
+
return measureOperation("storage:has", scope, () => {
|
|
679
|
+
(0, _internal.assertValidScope)(scope);
|
|
680
|
+
if (scope === _Storage.StorageScope.Memory) return memoryStore.has(key);
|
|
681
|
+
return WebStorage.has(key, scope);
|
|
682
|
+
});
|
|
443
683
|
},
|
|
444
684
|
getAllKeys: scope => {
|
|
445
|
-
(
|
|
446
|
-
|
|
447
|
-
|
|
685
|
+
return measureOperation("storage:getAllKeys", scope, () => {
|
|
686
|
+
(0, _internal.assertValidScope)(scope);
|
|
687
|
+
if (scope === _Storage.StorageScope.Memory) return Array.from(memoryStore.keys());
|
|
688
|
+
return WebStorage.getAllKeys(scope);
|
|
689
|
+
});
|
|
690
|
+
},
|
|
691
|
+
getKeysByPrefix: (prefix, scope) => {
|
|
692
|
+
return measureOperation("storage:getKeysByPrefix", scope, () => {
|
|
693
|
+
(0, _internal.assertValidScope)(scope);
|
|
694
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
695
|
+
return Array.from(memoryStore.keys()).filter(key => key.startsWith(prefix));
|
|
696
|
+
}
|
|
697
|
+
return WebStorage.getKeysByPrefix(prefix, scope);
|
|
698
|
+
});
|
|
699
|
+
},
|
|
700
|
+
getByPrefix: (prefix, scope) => {
|
|
701
|
+
return measureOperation("storage:getByPrefix", scope, () => {
|
|
702
|
+
const result = {};
|
|
703
|
+
const keys = storage.getKeysByPrefix(prefix, scope);
|
|
704
|
+
if (keys.length === 0) {
|
|
705
|
+
return result;
|
|
706
|
+
}
|
|
707
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
708
|
+
keys.forEach(key => {
|
|
709
|
+
const value = memoryStore.get(key);
|
|
710
|
+
if (typeof value === "string") {
|
|
711
|
+
result[key] = value;
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
return result;
|
|
715
|
+
}
|
|
716
|
+
const values = WebStorage.getBatch(keys, scope);
|
|
717
|
+
keys.forEach((key, index) => {
|
|
718
|
+
const value = values[index];
|
|
719
|
+
if (value !== undefined) {
|
|
720
|
+
result[key] = value;
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
return result;
|
|
724
|
+
});
|
|
448
725
|
},
|
|
449
726
|
getAll: scope => {
|
|
450
|
-
(
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
727
|
+
return measureOperation("storage:getAll", scope, () => {
|
|
728
|
+
(0, _internal.assertValidScope)(scope);
|
|
729
|
+
const result = {};
|
|
730
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
731
|
+
memoryStore.forEach((value, key) => {
|
|
732
|
+
if (typeof value === "string") result[key] = value;
|
|
733
|
+
});
|
|
734
|
+
return result;
|
|
735
|
+
}
|
|
736
|
+
const keys = WebStorage.getAllKeys(scope);
|
|
737
|
+
keys.forEach(key => {
|
|
738
|
+
const val = WebStorage.get(key, scope);
|
|
739
|
+
if (val !== undefined) result[key] = val;
|
|
455
740
|
});
|
|
456
741
|
return result;
|
|
457
|
-
}
|
|
458
|
-
const keys = WebStorage.getAllKeys(scope);
|
|
459
|
-
keys.forEach(key => {
|
|
460
|
-
const val = WebStorage.get(key, scope);
|
|
461
|
-
if (val !== undefined) result[key] = val;
|
|
462
742
|
});
|
|
463
|
-
return result;
|
|
464
743
|
},
|
|
465
744
|
size: scope => {
|
|
466
|
-
(
|
|
467
|
-
|
|
468
|
-
|
|
745
|
+
return measureOperation("storage:size", scope, () => {
|
|
746
|
+
(0, _internal.assertValidScope)(scope);
|
|
747
|
+
if (scope === _Storage.StorageScope.Memory) return memoryStore.size;
|
|
748
|
+
return WebStorage.size(scope);
|
|
749
|
+
});
|
|
750
|
+
},
|
|
751
|
+
setAccessControl: _level => {
|
|
752
|
+
recordMetric("storage:setAccessControl", _Storage.StorageScope.Secure, 0);
|
|
753
|
+
},
|
|
754
|
+
setSecureWritesAsync: _enabled => {
|
|
755
|
+
recordMetric("storage:setSecureWritesAsync", _Storage.StorageScope.Secure, 0);
|
|
469
756
|
},
|
|
470
|
-
|
|
471
|
-
|
|
757
|
+
flushSecureWrites: () => {
|
|
758
|
+
measureOperation("storage:flushSecureWrites", _Storage.StorageScope.Secure, () => {
|
|
759
|
+
flushSecureWrites();
|
|
760
|
+
});
|
|
761
|
+
},
|
|
762
|
+
setKeychainAccessGroup: _group => {
|
|
763
|
+
recordMetric("storage:setKeychainAccessGroup", _Storage.StorageScope.Secure, 0);
|
|
764
|
+
},
|
|
765
|
+
setMetricsObserver: observer => {
|
|
766
|
+
metricsObserver = observer;
|
|
767
|
+
},
|
|
768
|
+
getMetricsSnapshot: () => {
|
|
769
|
+
const snapshot = {};
|
|
770
|
+
metricsCounters.forEach((value, key) => {
|
|
771
|
+
snapshot[key] = {
|
|
772
|
+
count: value.count,
|
|
773
|
+
totalDurationMs: value.totalDurationMs,
|
|
774
|
+
avgDurationMs: value.count === 0 ? 0 : value.totalDurationMs / value.count,
|
|
775
|
+
maxDurationMs: value.maxDurationMs
|
|
776
|
+
};
|
|
777
|
+
});
|
|
778
|
+
return snapshot;
|
|
779
|
+
},
|
|
780
|
+
resetMetrics: () => {
|
|
781
|
+
metricsCounters.clear();
|
|
782
|
+
}
|
|
472
783
|
};
|
|
784
|
+
function setWebSecureStorageBackend(backend) {
|
|
785
|
+
webSecureStorageBackend = backend ?? createLocalStorageWebSecureBackend();
|
|
786
|
+
hydratedWebScopeKeyIndex.delete(_Storage.StorageScope.Secure);
|
|
787
|
+
clearScopeRawCache(_Storage.StorageScope.Secure);
|
|
788
|
+
}
|
|
789
|
+
function getWebSecureStorageBackend() {
|
|
790
|
+
return webSecureStorageBackend;
|
|
791
|
+
}
|
|
473
792
|
function canUseRawBatchPath(item) {
|
|
474
793
|
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
|
|
475
794
|
}
|
|
795
|
+
function canUseSecureRawBatchPath(item) {
|
|
796
|
+
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true;
|
|
797
|
+
}
|
|
476
798
|
function defaultSerialize(value) {
|
|
477
799
|
return (0, _internal.serializeWithPrimitiveFastPath)(value);
|
|
478
800
|
}
|
|
@@ -484,7 +806,8 @@ function createStorageItem(config) {
|
|
|
484
806
|
const serialize = config.serialize ?? defaultSerialize;
|
|
485
807
|
const deserialize = config.deserialize ?? defaultDeserialize;
|
|
486
808
|
const isMemory = config.scope === _Storage.StorageScope.Memory;
|
|
487
|
-
const
|
|
809
|
+
const resolvedBiometricLevel = config.scope === _Storage.StorageScope.Secure ? config.biometricLevel ?? (config.biometric === true ? _Storage.BiometricLevel.BiometryOnly : _Storage.BiometricLevel.None) : _Storage.BiometricLevel.None;
|
|
810
|
+
const isBiometric = resolvedBiometricLevel !== _Storage.BiometricLevel.None;
|
|
488
811
|
const secureAccessControl = config.accessControl;
|
|
489
812
|
const validate = config.validate;
|
|
490
813
|
const onValidationError = config.onValidationError;
|
|
@@ -493,7 +816,8 @@ function createStorageItem(config) {
|
|
|
493
816
|
const expirationTtlMs = expiration?.ttlMs;
|
|
494
817
|
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
495
818
|
const readCache = !isMemory && config.readCache === true;
|
|
496
|
-
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric
|
|
819
|
+
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
|
|
820
|
+
const defaultValue = config.defaultValue;
|
|
497
821
|
const nonMemoryScope = config.scope === _Storage.StorageScope.Disk ? _Storage.StorageScope.Disk : config.scope === _Storage.StorageScope.Secure ? _Storage.StorageScope.Secure : null;
|
|
498
822
|
if (expiration && expiration.ttlMs <= 0) {
|
|
499
823
|
throw new Error("expiration.ttlMs must be greater than 0.");
|
|
@@ -503,10 +827,12 @@ function createStorageItem(config) {
|
|
|
503
827
|
let lastRaw = undefined;
|
|
504
828
|
let lastValue;
|
|
505
829
|
let hasLastValue = false;
|
|
830
|
+
let lastExpiresAt = undefined;
|
|
506
831
|
const invalidateParsedCache = () => {
|
|
507
832
|
lastRaw = undefined;
|
|
508
833
|
lastValue = undefined;
|
|
509
834
|
hasLastValue = false;
|
|
835
|
+
lastExpiresAt = undefined;
|
|
510
836
|
};
|
|
511
837
|
const ensureSubscription = () => {
|
|
512
838
|
if (unsubscribe) {
|
|
@@ -520,6 +846,7 @@ function createStorageItem(config) {
|
|
|
520
846
|
unsubscribe = addKeyListener(memoryListeners, storageKey, listener);
|
|
521
847
|
return;
|
|
522
848
|
}
|
|
849
|
+
ensureWebStorageEventSubscription();
|
|
523
850
|
unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), storageKey, listener);
|
|
524
851
|
};
|
|
525
852
|
const readStoredRaw = () => {
|
|
@@ -553,12 +880,12 @@ function createStorageItem(config) {
|
|
|
553
880
|
};
|
|
554
881
|
const writeStoredRaw = rawValue => {
|
|
555
882
|
if (isBiometric) {
|
|
556
|
-
WebStorage.
|
|
883
|
+
WebStorage.setSecureBiometricWithLevel(storageKey, rawValue, resolvedBiometricLevel);
|
|
557
884
|
return;
|
|
558
885
|
}
|
|
559
886
|
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
560
887
|
if (coalesceSecureWrites) {
|
|
561
|
-
scheduleSecureWrite(storageKey, rawValue);
|
|
888
|
+
scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? _Storage.AccessControl.WhenUnlocked);
|
|
562
889
|
return;
|
|
563
890
|
}
|
|
564
891
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
@@ -573,7 +900,7 @@ function createStorageItem(config) {
|
|
|
573
900
|
}
|
|
574
901
|
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
575
902
|
if (coalesceSecureWrites) {
|
|
576
|
-
scheduleSecureWrite(storageKey, undefined);
|
|
903
|
+
scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? _Storage.AccessControl.WhenUnlocked);
|
|
577
904
|
return;
|
|
578
905
|
}
|
|
579
906
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
@@ -606,7 +933,7 @@ function createStorageItem(config) {
|
|
|
606
933
|
if (onValidationError) {
|
|
607
934
|
return onValidationError(invalidValue);
|
|
608
935
|
}
|
|
609
|
-
return
|
|
936
|
+
return defaultValue;
|
|
610
937
|
};
|
|
611
938
|
const ensureValidatedValue = (candidate, hadStoredValue) => {
|
|
612
939
|
if (!validate || validate(candidate)) {
|
|
@@ -614,40 +941,62 @@ function createStorageItem(config) {
|
|
|
614
941
|
}
|
|
615
942
|
const resolved = resolveInvalidValue(candidate);
|
|
616
943
|
if (validate && !validate(resolved)) {
|
|
617
|
-
return
|
|
944
|
+
return defaultValue;
|
|
618
945
|
}
|
|
619
946
|
if (hadStoredValue) {
|
|
620
947
|
writeValueWithoutValidation(resolved);
|
|
621
948
|
}
|
|
622
949
|
return resolved;
|
|
623
950
|
};
|
|
624
|
-
const
|
|
951
|
+
const getInternal = () => {
|
|
625
952
|
const raw = readStoredRaw();
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
953
|
+
if (!memoryExpiration && raw === lastRaw && hasLastValue) {
|
|
954
|
+
if (!expiration || lastExpiresAt === null) {
|
|
955
|
+
return lastValue;
|
|
956
|
+
}
|
|
957
|
+
if (typeof lastExpiresAt === "number") {
|
|
958
|
+
if (lastExpiresAt > Date.now()) {
|
|
959
|
+
return lastValue;
|
|
960
|
+
}
|
|
961
|
+
removeStoredRaw();
|
|
962
|
+
invalidateParsedCache();
|
|
963
|
+
onExpired?.(storageKey);
|
|
964
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
965
|
+
hasLastValue = true;
|
|
966
|
+
return lastValue;
|
|
967
|
+
}
|
|
629
968
|
}
|
|
630
969
|
lastRaw = raw;
|
|
631
970
|
if (raw === undefined) {
|
|
632
|
-
|
|
971
|
+
lastExpiresAt = undefined;
|
|
972
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
633
973
|
hasLastValue = true;
|
|
634
974
|
return lastValue;
|
|
635
975
|
}
|
|
636
976
|
if (isMemory) {
|
|
977
|
+
lastExpiresAt = undefined;
|
|
637
978
|
lastValue = ensureValidatedValue(raw, true);
|
|
638
979
|
hasLastValue = true;
|
|
639
980
|
return lastValue;
|
|
640
981
|
}
|
|
982
|
+
if (typeof raw !== "string") {
|
|
983
|
+
lastExpiresAt = undefined;
|
|
984
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
985
|
+
hasLastValue = true;
|
|
986
|
+
return lastValue;
|
|
987
|
+
}
|
|
641
988
|
let deserializableRaw = raw;
|
|
642
989
|
if (expiration) {
|
|
990
|
+
let envelopeExpiresAt = null;
|
|
643
991
|
try {
|
|
644
992
|
const parsed = JSON.parse(raw);
|
|
645
993
|
if ((0, _internal.isStoredEnvelope)(parsed)) {
|
|
994
|
+
envelopeExpiresAt = parsed.expiresAt;
|
|
646
995
|
if (parsed.expiresAt <= Date.now()) {
|
|
647
996
|
removeStoredRaw();
|
|
648
997
|
invalidateParsedCache();
|
|
649
998
|
onExpired?.(storageKey);
|
|
650
|
-
lastValue = ensureValidatedValue(
|
|
999
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
651
1000
|
hasLastValue = true;
|
|
652
1001
|
return lastValue;
|
|
653
1002
|
}
|
|
@@ -656,37 +1005,60 @@ function createStorageItem(config) {
|
|
|
656
1005
|
} catch {
|
|
657
1006
|
// Keep backward compatibility with legacy raw values.
|
|
658
1007
|
}
|
|
1008
|
+
lastExpiresAt = envelopeExpiresAt;
|
|
1009
|
+
} else {
|
|
1010
|
+
lastExpiresAt = undefined;
|
|
659
1011
|
}
|
|
660
1012
|
lastValue = ensureValidatedValue(deserialize(deserializableRaw), true);
|
|
661
1013
|
hasLastValue = true;
|
|
662
1014
|
return lastValue;
|
|
663
1015
|
};
|
|
1016
|
+
const getCurrentVersion = () => {
|
|
1017
|
+
const raw = readStoredRaw();
|
|
1018
|
+
return (0, _internal.toVersionToken)(raw);
|
|
1019
|
+
};
|
|
1020
|
+
const get = () => measureOperation("item:get", config.scope, () => getInternal());
|
|
1021
|
+
const getWithVersion = () => measureOperation("item:getWithVersion", config.scope, () => ({
|
|
1022
|
+
value: getInternal(),
|
|
1023
|
+
version: getCurrentVersion()
|
|
1024
|
+
}));
|
|
664
1025
|
const set = valueOrFn => {
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
1026
|
+
measureOperation("item:set", config.scope, () => {
|
|
1027
|
+
const newValue = isUpdater(valueOrFn) ? valueOrFn(getInternal()) : valueOrFn;
|
|
1028
|
+
invalidateParsedCache();
|
|
1029
|
+
if (validate && !validate(newValue)) {
|
|
1030
|
+
throw new Error(`Validation failed for key "${storageKey}" in scope "${_Storage.StorageScope[config.scope]}".`);
|
|
1031
|
+
}
|
|
1032
|
+
writeValueWithoutValidation(newValue);
|
|
1033
|
+
});
|
|
672
1034
|
};
|
|
1035
|
+
const setIfVersion = (version, valueOrFn) => measureOperation("item:setIfVersion", config.scope, () => {
|
|
1036
|
+
const currentVersion = getCurrentVersion();
|
|
1037
|
+
if (currentVersion !== version) {
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1040
|
+
set(valueOrFn);
|
|
1041
|
+
return true;
|
|
1042
|
+
});
|
|
673
1043
|
const deleteItem = () => {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
if (
|
|
677
|
-
memoryExpiration
|
|
1044
|
+
measureOperation("item:delete", config.scope, () => {
|
|
1045
|
+
invalidateParsedCache();
|
|
1046
|
+
if (isMemory) {
|
|
1047
|
+
if (memoryExpiration) {
|
|
1048
|
+
memoryExpiration.delete(storageKey);
|
|
1049
|
+
}
|
|
1050
|
+
memoryStore.delete(storageKey);
|
|
1051
|
+
notifyKeyListeners(memoryListeners, storageKey);
|
|
1052
|
+
return;
|
|
678
1053
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
return;
|
|
682
|
-
}
|
|
683
|
-
removeStoredRaw();
|
|
1054
|
+
removeStoredRaw();
|
|
1055
|
+
});
|
|
684
1056
|
};
|
|
685
|
-
const hasItem = () => {
|
|
1057
|
+
const hasItem = () => measureOperation("item:has", config.scope, () => {
|
|
686
1058
|
if (isMemory) return memoryStore.has(storageKey);
|
|
687
1059
|
if (isBiometric) return WebStorage.hasSecureBiometric(storageKey);
|
|
688
1060
|
return WebStorage.has(storageKey, config.scope);
|
|
689
|
-
};
|
|
1061
|
+
});
|
|
690
1062
|
const subscribe = callback => {
|
|
691
1063
|
ensureSubscription();
|
|
692
1064
|
listeners.add(callback);
|
|
@@ -700,7 +1072,9 @@ function createStorageItem(config) {
|
|
|
700
1072
|
};
|
|
701
1073
|
const storageItem = {
|
|
702
1074
|
get,
|
|
1075
|
+
getWithVersion,
|
|
703
1076
|
set,
|
|
1077
|
+
setIfVersion,
|
|
704
1078
|
delete: deleteItem,
|
|
705
1079
|
has: hasItem,
|
|
706
1080
|
subscribe,
|
|
@@ -714,123 +1088,150 @@ function createStorageItem(config) {
|
|
|
714
1088
|
_hasExpiration: expiration !== undefined,
|
|
715
1089
|
_readCacheEnabled: readCache,
|
|
716
1090
|
_isBiometric: isBiometric,
|
|
717
|
-
|
|
1091
|
+
_defaultValue: defaultValue,
|
|
1092
|
+
...(secureAccessControl !== undefined ? {
|
|
1093
|
+
_secureAccessControl: secureAccessControl
|
|
1094
|
+
} : {}),
|
|
718
1095
|
scope: config.scope,
|
|
719
1096
|
key: storageKey
|
|
720
1097
|
};
|
|
721
1098
|
return storageItem;
|
|
722
1099
|
}
|
|
723
|
-
function useStorage(item) {
|
|
724
|
-
const value = (0, _react.useSyncExternalStore)(item.subscribe, item.get, item.get);
|
|
725
|
-
return [value, item.set];
|
|
726
|
-
}
|
|
727
|
-
function useStorageSelector(item, selector, isEqual = Object.is) {
|
|
728
|
-
const selectedRef = (0, _react.useRef)({
|
|
729
|
-
hasValue: false
|
|
730
|
-
});
|
|
731
|
-
const getSelectedSnapshot = () => {
|
|
732
|
-
const nextSelected = selector(item.get());
|
|
733
|
-
const current = selectedRef.current;
|
|
734
|
-
if (current.hasValue && isEqual(current.value, nextSelected)) {
|
|
735
|
-
return current.value;
|
|
736
|
-
}
|
|
737
|
-
selectedRef.current = {
|
|
738
|
-
hasValue: true,
|
|
739
|
-
value: nextSelected
|
|
740
|
-
};
|
|
741
|
-
return nextSelected;
|
|
742
|
-
};
|
|
743
|
-
const selectedValue = (0, _react.useSyncExternalStore)(item.subscribe, getSelectedSnapshot, getSelectedSnapshot);
|
|
744
|
-
return [selectedValue, item.set];
|
|
745
|
-
}
|
|
746
|
-
function useSetStorage(item) {
|
|
747
|
-
return item.set;
|
|
748
|
-
}
|
|
749
1100
|
function getBatch(items, scope) {
|
|
750
|
-
(
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
const useRawBatchPath = items.every(item => canUseRawBatchPath(item));
|
|
755
|
-
if (!useRawBatchPath) {
|
|
756
|
-
return items.map(item => item.get());
|
|
757
|
-
}
|
|
758
|
-
const useBatchCache = items.every(item => item._readCacheEnabled === true);
|
|
759
|
-
const rawValues = new Array(items.length);
|
|
760
|
-
const keysToFetch = [];
|
|
761
|
-
const keyIndexes = [];
|
|
762
|
-
items.forEach((item, index) => {
|
|
763
|
-
if (scope === _Storage.StorageScope.Secure) {
|
|
764
|
-
if (hasPendingSecureWrite(item.key)) {
|
|
765
|
-
rawValues[index] = readPendingSecureWrite(item.key);
|
|
766
|
-
return;
|
|
767
|
-
}
|
|
1101
|
+
return measureOperation("batch:get", scope, () => {
|
|
1102
|
+
(0, _internal.assertBatchScope)(items, scope);
|
|
1103
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
1104
|
+
return items.map(item => item.get());
|
|
768
1105
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
return;
|
|
773
|
-
}
|
|
1106
|
+
const useRawBatchPath = items.every(item => scope === _Storage.StorageScope.Secure ? canUseSecureRawBatchPath(item) : canUseRawBatchPath(item));
|
|
1107
|
+
if (!useRawBatchPath) {
|
|
1108
|
+
return items.map(item => item.get());
|
|
774
1109
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
1110
|
+
const rawValues = new Array(items.length);
|
|
1111
|
+
const keysToFetch = [];
|
|
1112
|
+
const keyIndexes = [];
|
|
1113
|
+
items.forEach((item, index) => {
|
|
1114
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
1115
|
+
if (hasPendingSecureWrite(item.key)) {
|
|
1116
|
+
rawValues[index] = readPendingSecureWrite(item.key);
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
if (item._readCacheEnabled === true) {
|
|
1121
|
+
if (hasCachedRawValue(scope, item.key)) {
|
|
1122
|
+
rawValues[index] = readCachedRawValue(scope, item.key);
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
keysToFetch.push(item.key);
|
|
1127
|
+
keyIndexes.push(index);
|
|
785
1128
|
});
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1129
|
+
if (keysToFetch.length > 0) {
|
|
1130
|
+
const fetchedValues = WebStorage.getBatch(keysToFetch, scope);
|
|
1131
|
+
fetchedValues.forEach((value, index) => {
|
|
1132
|
+
const key = keysToFetch[index];
|
|
1133
|
+
const targetIndex = keyIndexes[index];
|
|
1134
|
+
if (key === undefined || targetIndex === undefined) {
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
rawValues[targetIndex] = value;
|
|
1138
|
+
cacheRawValue(scope, key, value);
|
|
1139
|
+
});
|
|
791
1140
|
}
|
|
792
|
-
return
|
|
793
|
-
|
|
1141
|
+
return items.map((item, index) => {
|
|
1142
|
+
const raw = rawValues[index];
|
|
1143
|
+
if (raw === undefined) {
|
|
1144
|
+
return asInternal(item)._defaultValue;
|
|
1145
|
+
}
|
|
1146
|
+
return item.deserialize(raw);
|
|
1147
|
+
});
|
|
1148
|
+
}, items.length);
|
|
794
1149
|
}
|
|
795
1150
|
function setBatch(items, scope) {
|
|
796
|
-
(
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1151
|
+
measureOperation("batch:set", scope, () => {
|
|
1152
|
+
(0, _internal.assertBatchScope)(items.map(batchEntry => batchEntry.item), scope);
|
|
1153
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
1154
|
+
items.forEach(({
|
|
1155
|
+
item,
|
|
1156
|
+
value
|
|
1157
|
+
}) => item.set(value));
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
1161
|
+
const secureEntries = items.map(({
|
|
1162
|
+
item,
|
|
1163
|
+
value
|
|
1164
|
+
}) => ({
|
|
1165
|
+
item,
|
|
1166
|
+
value,
|
|
1167
|
+
internal: asInternal(item)
|
|
1168
|
+
}));
|
|
1169
|
+
const canUseSecureBatchPath = secureEntries.every(({
|
|
1170
|
+
internal
|
|
1171
|
+
}) => canUseSecureRawBatchPath(internal));
|
|
1172
|
+
if (!canUseSecureBatchPath) {
|
|
1173
|
+
items.forEach(({
|
|
1174
|
+
item,
|
|
1175
|
+
value
|
|
1176
|
+
}) => item.set(value));
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
flushSecureWrites();
|
|
1180
|
+
const groupedByAccessControl = new Map();
|
|
1181
|
+
secureEntries.forEach(({
|
|
1182
|
+
item,
|
|
1183
|
+
value,
|
|
1184
|
+
internal
|
|
1185
|
+
}) => {
|
|
1186
|
+
const accessControl = internal._secureAccessControl ?? _Storage.AccessControl.WhenUnlocked;
|
|
1187
|
+
const existingGroup = groupedByAccessControl.get(accessControl);
|
|
1188
|
+
const group = existingGroup ?? {
|
|
1189
|
+
keys: [],
|
|
1190
|
+
values: []
|
|
1191
|
+
};
|
|
1192
|
+
group.keys.push(item.key);
|
|
1193
|
+
group.values.push(item.serialize(value));
|
|
1194
|
+
if (!existingGroup) {
|
|
1195
|
+
groupedByAccessControl.set(accessControl, group);
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
groupedByAccessControl.forEach((group, accessControl) => {
|
|
1199
|
+
WebStorage.setSecureAccessControl(accessControl);
|
|
1200
|
+
WebStorage.setBatch(group.keys, group.values, scope);
|
|
1201
|
+
group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
|
|
1202
|
+
});
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
const useRawBatchPath = items.every(({
|
|
1206
|
+
item
|
|
1207
|
+
}) => canUseRawBatchPath(asInternal(item)));
|
|
1208
|
+
if (!useRawBatchPath) {
|
|
1209
|
+
items.forEach(({
|
|
1210
|
+
item,
|
|
1211
|
+
value
|
|
1212
|
+
}) => item.set(value));
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
const keys = items.map(entry => entry.item.key);
|
|
1216
|
+
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
1217
|
+
WebStorage.setBatch(keys, values, scope);
|
|
1218
|
+
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
1219
|
+
}, items.length);
|
|
821
1220
|
}
|
|
822
1221
|
function removeBatch(items, scope) {
|
|
823
|
-
(
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
1222
|
+
measureOperation("batch:remove", scope, () => {
|
|
1223
|
+
(0, _internal.assertBatchScope)(items, scope);
|
|
1224
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
1225
|
+
items.forEach(item => item.delete());
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
const keys = items.map(item => item.key);
|
|
1229
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
1230
|
+
flushSecureWrites();
|
|
1231
|
+
}
|
|
1232
|
+
WebStorage.removeBatch(keys, scope);
|
|
1233
|
+
keys.forEach(key => cacheRawValue(scope, key, undefined));
|
|
1234
|
+
}, items.length);
|
|
834
1235
|
}
|
|
835
1236
|
function registerMigration(version, migration) {
|
|
836
1237
|
if (!Number.isInteger(version) || version <= 0) {
|
|
@@ -842,93 +1243,133 @@ function registerMigration(version, migration) {
|
|
|
842
1243
|
registeredMigrations.set(version, migration);
|
|
843
1244
|
}
|
|
844
1245
|
function migrateToLatest(scope = _Storage.StorageScope.Disk) {
|
|
845
|
-
(
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1246
|
+
return measureOperation("migration:run", scope, () => {
|
|
1247
|
+
(0, _internal.assertValidScope)(scope);
|
|
1248
|
+
const currentVersion = readMigrationVersion(scope);
|
|
1249
|
+
const versions = Array.from(registeredMigrations.keys()).filter(version => version > currentVersion).sort((a, b) => a - b);
|
|
1250
|
+
let appliedVersion = currentVersion;
|
|
1251
|
+
const context = {
|
|
1252
|
+
scope,
|
|
1253
|
+
getRaw: key => getRawValue(key, scope),
|
|
1254
|
+
setRaw: (key, value) => setRawValue(key, value, scope),
|
|
1255
|
+
removeRaw: key => removeRawValue(key, scope)
|
|
1256
|
+
};
|
|
1257
|
+
versions.forEach(version => {
|
|
1258
|
+
const migration = registeredMigrations.get(version);
|
|
1259
|
+
if (!migration) {
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
migration(context);
|
|
1263
|
+
writeMigrationVersion(scope, version);
|
|
1264
|
+
appliedVersion = version;
|
|
1265
|
+
});
|
|
1266
|
+
return appliedVersion;
|
|
863
1267
|
});
|
|
864
|
-
return appliedVersion;
|
|
865
1268
|
}
|
|
866
1269
|
function runTransaction(scope, transaction) {
|
|
867
|
-
(
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
const rollback = new Map();
|
|
872
|
-
const rememberRollback = key => {
|
|
873
|
-
if (rollback.has(key)) {
|
|
874
|
-
return;
|
|
875
|
-
}
|
|
876
|
-
rollback.set(key, getRawValue(key, scope));
|
|
877
|
-
};
|
|
878
|
-
const tx = {
|
|
879
|
-
scope,
|
|
880
|
-
getRaw: key => getRawValue(key, scope),
|
|
881
|
-
setRaw: (key, value) => {
|
|
882
|
-
rememberRollback(key);
|
|
883
|
-
setRawValue(key, value, scope);
|
|
884
|
-
},
|
|
885
|
-
removeRaw: key => {
|
|
886
|
-
rememberRollback(key);
|
|
887
|
-
removeRawValue(key, scope);
|
|
888
|
-
},
|
|
889
|
-
getItem: item => {
|
|
890
|
-
(0, _internal.assertBatchScope)([item], scope);
|
|
891
|
-
return item.get();
|
|
892
|
-
},
|
|
893
|
-
setItem: (item, value) => {
|
|
894
|
-
(0, _internal.assertBatchScope)([item], scope);
|
|
895
|
-
rememberRollback(item.key);
|
|
896
|
-
item.set(value);
|
|
897
|
-
},
|
|
898
|
-
removeItem: item => {
|
|
899
|
-
(0, _internal.assertBatchScope)([item], scope);
|
|
900
|
-
rememberRollback(item.key);
|
|
901
|
-
item.delete();
|
|
1270
|
+
return measureOperation("transaction:run", scope, () => {
|
|
1271
|
+
(0, _internal.assertValidScope)(scope);
|
|
1272
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
1273
|
+
flushSecureWrites();
|
|
902
1274
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1275
|
+
const rollback = new Map();
|
|
1276
|
+
const rememberRollback = key => {
|
|
1277
|
+
if (rollback.has(key)) {
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
rollback.set(key, getRawValue(key, scope));
|
|
1281
|
+
};
|
|
1282
|
+
const tx = {
|
|
1283
|
+
scope,
|
|
1284
|
+
getRaw: key => getRawValue(key, scope),
|
|
1285
|
+
setRaw: (key, value) => {
|
|
1286
|
+
rememberRollback(key);
|
|
1287
|
+
setRawValue(key, value, scope);
|
|
1288
|
+
},
|
|
1289
|
+
removeRaw: key => {
|
|
1290
|
+
rememberRollback(key);
|
|
909
1291
|
removeRawValue(key, scope);
|
|
1292
|
+
},
|
|
1293
|
+
getItem: item => {
|
|
1294
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
1295
|
+
return item.get();
|
|
1296
|
+
},
|
|
1297
|
+
setItem: (item, value) => {
|
|
1298
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
1299
|
+
rememberRollback(item.key);
|
|
1300
|
+
item.set(value);
|
|
1301
|
+
},
|
|
1302
|
+
removeItem: item => {
|
|
1303
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
1304
|
+
rememberRollback(item.key);
|
|
1305
|
+
item.delete();
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
try {
|
|
1309
|
+
return transaction(tx);
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
const rollbackEntries = Array.from(rollback.entries()).reverse();
|
|
1312
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
1313
|
+
rollbackEntries.forEach(([key, previousValue]) => {
|
|
1314
|
+
if (previousValue === undefined) {
|
|
1315
|
+
removeRawValue(key, scope);
|
|
1316
|
+
} else {
|
|
1317
|
+
setRawValue(key, previousValue, scope);
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
910
1320
|
} else {
|
|
911
|
-
|
|
1321
|
+
const keysToSet = [];
|
|
1322
|
+
const valuesToSet = [];
|
|
1323
|
+
const keysToRemove = [];
|
|
1324
|
+
rollbackEntries.forEach(([key, previousValue]) => {
|
|
1325
|
+
if (previousValue === undefined) {
|
|
1326
|
+
keysToRemove.push(key);
|
|
1327
|
+
} else {
|
|
1328
|
+
keysToSet.push(key);
|
|
1329
|
+
valuesToSet.push(previousValue);
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1332
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
1333
|
+
flushSecureWrites();
|
|
1334
|
+
}
|
|
1335
|
+
if (keysToSet.length > 0) {
|
|
1336
|
+
WebStorage.setBatch(keysToSet, valuesToSet, scope);
|
|
1337
|
+
keysToSet.forEach((key, index) => cacheRawValue(scope, key, valuesToSet[index]));
|
|
1338
|
+
}
|
|
1339
|
+
if (keysToRemove.length > 0) {
|
|
1340
|
+
WebStorage.removeBatch(keysToRemove, scope);
|
|
1341
|
+
keysToRemove.forEach(key => cacheRawValue(scope, key, undefined));
|
|
1342
|
+
}
|
|
912
1343
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
}
|
|
1344
|
+
throw error;
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
916
1347
|
}
|
|
917
1348
|
function createSecureAuthStorage(config, options) {
|
|
918
1349
|
const ns = options?.namespace ?? "auth";
|
|
919
1350
|
const result = {};
|
|
920
|
-
for (const key of
|
|
1351
|
+
for (const key of typedKeys(config)) {
|
|
921
1352
|
const itemConfig = config[key];
|
|
1353
|
+
const expirationConfig = itemConfig.ttlMs !== undefined ? {
|
|
1354
|
+
ttlMs: itemConfig.ttlMs
|
|
1355
|
+
} : undefined;
|
|
922
1356
|
result[key] = createStorageItem({
|
|
923
1357
|
key,
|
|
924
1358
|
scope: _Storage.StorageScope.Secure,
|
|
925
1359
|
defaultValue: "",
|
|
926
1360
|
namespace: ns,
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1361
|
+
...(itemConfig.biometric !== undefined ? {
|
|
1362
|
+
biometric: itemConfig.biometric
|
|
1363
|
+
} : {}),
|
|
1364
|
+
...(itemConfig.biometricLevel !== undefined ? {
|
|
1365
|
+
biometricLevel: itemConfig.biometricLevel
|
|
1366
|
+
} : {}),
|
|
1367
|
+
...(itemConfig.accessControl !== undefined ? {
|
|
1368
|
+
accessControl: itemConfig.accessControl
|
|
1369
|
+
} : {}),
|
|
1370
|
+
...(expirationConfig !== undefined ? {
|
|
1371
|
+
expiration: expirationConfig
|
|
1372
|
+
} : {})
|
|
932
1373
|
});
|
|
933
1374
|
}
|
|
934
1375
|
return result;
|