react-native-nitro-storage 0.4.3 → 0.4.5
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 +108 -8
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +61 -10
- package/ios/IOSStorageAdapterCpp.mm +44 -14
- package/lib/commonjs/index.js +221 -5
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +444 -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 +213 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +436 -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 +11 -7
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +12 -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 +16 -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/nitro.json +8 -2
- package/nitrogen/generated/ios/NitroStorage+autolinking.rb +2 -0
- package/package.json +2 -2
- package/src/index.ts +268 -21
- package/src/index.web.ts +601 -246
- package/src/indexeddb-backend.ts +147 -6
- package/src/storage-runtime.ts +94 -0
- package/src/web-storage-backend.ts +129 -0
package/lib/module/index.web.js
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
|
|
4
4
|
import { MIGRATION_VERSION_KEY, isStoredEnvelope, assertBatchScope, assertValidScope, serializeWithPrimitiveFastPath, deserializeWithPrimitiveFastPath, toVersionToken, prefixKey, isNamespaced } from "./internal";
|
|
5
|
+
import { createLocalStorageWebBackend } from "./web-storage-backend";
|
|
6
|
+
import { getStorageErrorCode, isLockedStorageErrorCode } from "./storage-runtime";
|
|
5
7
|
export { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
|
|
6
8
|
export { migrateFromMMKV } from "./migration";
|
|
9
|
+
export { getStorageErrorCode } from "./storage-runtime";
|
|
7
10
|
function asInternal(item) {
|
|
8
11
|
return item;
|
|
9
12
|
}
|
|
@@ -17,20 +20,23 @@ const registeredMigrations = new Map();
|
|
|
17
20
|
const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
|
|
18
21
|
Promise.resolve().then(task);
|
|
19
22
|
};
|
|
23
|
+
const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
|
|
20
24
|
const memoryStore = new Map();
|
|
21
25
|
const memoryListeners = new Map();
|
|
22
26
|
const webScopeListeners = new Map([[StorageScope.Disk, new Map()], [StorageScope.Secure, new Map()]]);
|
|
23
27
|
const scopedRawCache = new Map([[StorageScope.Disk, new Map()], [StorageScope.Secure, new Map()]]);
|
|
24
28
|
const webScopeKeyIndex = new Map([[StorageScope.Disk, new Set()], [StorageScope.Secure, new Set()]]);
|
|
25
29
|
const hydratedWebScopeKeyIndex = new Set();
|
|
30
|
+
const pendingDiskWrites = new Map();
|
|
31
|
+
let diskFlushScheduled = false;
|
|
32
|
+
let diskWritesAsync = false;
|
|
26
33
|
const pendingSecureWrites = new Map();
|
|
27
34
|
let secureFlushScheduled = false;
|
|
28
35
|
let secureDefaultAccessControl = AccessControl.WhenUnlocked;
|
|
29
36
|
const SECURE_WEB_PREFIX = "__secure_";
|
|
30
37
|
const BIOMETRIC_WEB_PREFIX = "__bio_";
|
|
31
38
|
let hasWarnedAboutWebBiometricFallback = false;
|
|
32
|
-
let
|
|
33
|
-
let webStorageSubscriberCount = 0;
|
|
39
|
+
let hasWindowStorageEventSubscription = false;
|
|
34
40
|
let metricsObserver;
|
|
35
41
|
const metricsCounters = new Map();
|
|
36
42
|
function recordMetric(operation, scope, durationMs, keysCount = 1) {
|
|
@@ -57,61 +63,51 @@ function measureOperation(operation, scope, fn, keysCount = 1) {
|
|
|
57
63
|
if (!metricsObserver) {
|
|
58
64
|
return fn();
|
|
59
65
|
}
|
|
60
|
-
const start =
|
|
66
|
+
const start = now();
|
|
61
67
|
try {
|
|
62
68
|
return fn();
|
|
63
69
|
} finally {
|
|
64
|
-
recordMetric(operation, scope,
|
|
70
|
+
recordMetric(operation, scope, now() - start, keysCount);
|
|
65
71
|
}
|
|
66
72
|
}
|
|
67
|
-
function
|
|
68
|
-
return {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
clear: () => globalThis.localStorage?.clear(),
|
|
73
|
-
getAllKeys: () => {
|
|
74
|
-
const storage = globalThis.localStorage;
|
|
75
|
-
if (!storage) return [];
|
|
76
|
-
const keys = [];
|
|
77
|
-
for (let index = 0; index < storage.length; index += 1) {
|
|
78
|
-
const key = storage.key(index);
|
|
79
|
-
if (key) {
|
|
80
|
-
keys.push(key);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return keys;
|
|
84
|
-
}
|
|
85
|
-
};
|
|
73
|
+
function createDefaultDiskBackend() {
|
|
74
|
+
return createLocalStorageWebBackend({
|
|
75
|
+
name: "localStorage:disk",
|
|
76
|
+
includeKey: key => !key.startsWith(SECURE_WEB_PREFIX) && !key.startsWith(BIOMETRIC_WEB_PREFIX)
|
|
77
|
+
});
|
|
86
78
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
79
|
+
function createDefaultSecureBackend() {
|
|
80
|
+
return createLocalStorageWebBackend({
|
|
81
|
+
name: "localStorage:secure",
|
|
82
|
+
includeKey: key => key.startsWith(SECURE_WEB_PREFIX) || key.startsWith(BIOMETRIC_WEB_PREFIX)
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
let webDiskStorageBackend = createDefaultDiskBackend();
|
|
86
|
+
let webSecureStorageBackend = createDefaultSecureBackend();
|
|
87
|
+
const externalSyncUnsubscribers = new Map();
|
|
88
|
+
function getBackendName(scope, backend) {
|
|
89
|
+
const scopeName = scope === StorageScope.Disk ? "disk" : "secure";
|
|
90
|
+
return backend?.name ?? `web:${scopeName}`;
|
|
91
|
+
}
|
|
92
|
+
function createWebStorageError(scope, operation, error, backend) {
|
|
93
|
+
const backendName = getBackendName(scope, backend);
|
|
94
|
+
const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
|
|
95
|
+
return new Error(`NitroStorage(web): ${operation} failed for ${backendName}: ${message}`);
|
|
96
|
+
}
|
|
97
|
+
function withWebBackendOperation(scope, operation, fn) {
|
|
98
|
+
const backend = scope === StorageScope.Disk ? webDiskStorageBackend : webSecureStorageBackend;
|
|
99
|
+
if (!backend) {
|
|
100
|
+
throw new Error(`NitroStorage(web): ${operation} failed because no ${scope === StorageScope.Disk ? "disk" : "secure"} backend is configured.`);
|
|
93
101
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return cachedSecureBrowserStorage;
|
|
100
|
-
}
|
|
101
|
-
cachedSecureBackendRef = webSecureStorageBackend;
|
|
102
|
-
cachedSecureBrowserStorage = {
|
|
103
|
-
setItem: (key, value) => webSecureStorageBackend.setItem(key, value),
|
|
104
|
-
getItem: key => webSecureStorageBackend.getItem(key) ?? null,
|
|
105
|
-
removeItem: key => webSecureStorageBackend.removeItem(key),
|
|
106
|
-
clear: () => webSecureStorageBackend.clear(),
|
|
107
|
-
key: index => webSecureStorageBackend.getAllKeys()[index] ?? null,
|
|
108
|
-
get length() {
|
|
109
|
-
return webSecureStorageBackend.getAllKeys().length;
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
return cachedSecureBrowserStorage;
|
|
102
|
+
try {
|
|
103
|
+
ensureExternalSyncSubscriptions();
|
|
104
|
+
return fn(backend);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
throw createWebStorageError(scope, operation, error, backend);
|
|
113
107
|
}
|
|
114
|
-
|
|
108
|
+
}
|
|
109
|
+
function getWebBackend(scope) {
|
|
110
|
+
return scope === StorageScope.Disk ? webDiskStorageBackend : webSecureStorageBackend;
|
|
115
111
|
}
|
|
116
112
|
function toSecureStorageKey(key) {
|
|
117
113
|
return `${SECURE_WEB_PREFIX}${key}`;
|
|
@@ -132,28 +128,21 @@ function hydrateWebScopeKeyIndex(scope) {
|
|
|
132
128
|
if (hydratedWebScopeKeyIndex.has(scope)) {
|
|
133
129
|
return;
|
|
134
130
|
}
|
|
135
|
-
const
|
|
131
|
+
const backend = getWebBackend(scope);
|
|
136
132
|
const keyIndex = getWebScopeKeyIndex(scope);
|
|
137
133
|
keyIndex.clear();
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (key.startsWith(SECURE_WEB_PREFIX)) {
|
|
151
|
-
keyIndex.add(fromSecureStorageKey(key));
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
if (key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
155
|
-
keyIndex.add(fromBiometricStorageKey(key));
|
|
156
|
-
}
|
|
134
|
+
const keys = backend?.getAllKeys() ?? [];
|
|
135
|
+
for (const key of keys) {
|
|
136
|
+
if (scope === StorageScope.Disk) {
|
|
137
|
+
keyIndex.add(key);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (key.startsWith(SECURE_WEB_PREFIX)) {
|
|
141
|
+
keyIndex.add(fromSecureStorageKey(key));
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
145
|
+
keyIndex.add(fromBiometricStorageKey(key));
|
|
157
146
|
}
|
|
158
147
|
}
|
|
159
148
|
hydratedWebScopeKeyIndex.add(scope);
|
|
@@ -162,65 +151,85 @@ function ensureWebScopeKeyIndex(scope) {
|
|
|
162
151
|
hydrateWebScopeKeyIndex(scope);
|
|
163
152
|
return getWebScopeKeyIndex(scope);
|
|
164
153
|
}
|
|
165
|
-
function
|
|
166
|
-
const key = event.key;
|
|
154
|
+
function applyExternalChangeEvent(scope, key, newValue) {
|
|
167
155
|
if (key === null) {
|
|
168
|
-
clearScopeRawCache(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
ensureWebScopeKeyIndex(StorageScope.Secure).clear();
|
|
172
|
-
notifyAllListeners(getScopedListeners(StorageScope.Disk));
|
|
173
|
-
notifyAllListeners(getScopedListeners(StorageScope.Secure));
|
|
156
|
+
clearScopeRawCache(scope);
|
|
157
|
+
ensureWebScopeKeyIndex(scope).clear();
|
|
158
|
+
notifyAllListeners(getScopedListeners(scope));
|
|
174
159
|
return;
|
|
175
160
|
}
|
|
176
|
-
if (key.startsWith(SECURE_WEB_PREFIX)) {
|
|
161
|
+
if (scope === StorageScope.Secure && key.startsWith(SECURE_WEB_PREFIX)) {
|
|
177
162
|
const plainKey = fromSecureStorageKey(key);
|
|
178
|
-
if (
|
|
163
|
+
if (newValue === null) {
|
|
179
164
|
ensureWebScopeKeyIndex(StorageScope.Secure).delete(plainKey);
|
|
180
165
|
cacheRawValue(StorageScope.Secure, plainKey, undefined);
|
|
181
166
|
} else {
|
|
182
167
|
ensureWebScopeKeyIndex(StorageScope.Secure).add(plainKey);
|
|
183
|
-
cacheRawValue(StorageScope.Secure, plainKey,
|
|
168
|
+
cacheRawValue(StorageScope.Secure, plainKey, newValue);
|
|
184
169
|
}
|
|
185
170
|
notifyKeyListeners(getScopedListeners(StorageScope.Secure), plainKey);
|
|
186
171
|
return;
|
|
187
172
|
}
|
|
188
|
-
if (key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
173
|
+
if (scope === StorageScope.Secure && key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
189
174
|
const plainKey = fromBiometricStorageKey(key);
|
|
190
|
-
if (
|
|
191
|
-
if (
|
|
175
|
+
if (newValue === null) {
|
|
176
|
+
if (withWebBackendOperation(StorageScope.Secure, "external-sync:getItem", backend => backend.getItem(toSecureStorageKey(plainKey))) === null) {
|
|
192
177
|
ensureWebScopeKeyIndex(StorageScope.Secure).delete(plainKey);
|
|
193
178
|
}
|
|
194
179
|
cacheRawValue(StorageScope.Secure, plainKey, undefined);
|
|
195
180
|
} else {
|
|
196
181
|
ensureWebScopeKeyIndex(StorageScope.Secure).add(plainKey);
|
|
197
|
-
cacheRawValue(StorageScope.Secure, plainKey,
|
|
182
|
+
cacheRawValue(StorageScope.Secure, plainKey, newValue);
|
|
198
183
|
}
|
|
199
184
|
notifyKeyListeners(getScopedListeners(StorageScope.Secure), plainKey);
|
|
200
185
|
return;
|
|
201
186
|
}
|
|
202
|
-
if (
|
|
203
|
-
ensureWebScopeKeyIndex(
|
|
204
|
-
cacheRawValue(
|
|
187
|
+
if (newValue === null) {
|
|
188
|
+
ensureWebScopeKeyIndex(scope).delete(key);
|
|
189
|
+
cacheRawValue(scope, key, undefined);
|
|
205
190
|
} else {
|
|
206
|
-
ensureWebScopeKeyIndex(
|
|
207
|
-
cacheRawValue(
|
|
191
|
+
ensureWebScopeKeyIndex(scope).add(key);
|
|
192
|
+
cacheRawValue(scope, key, newValue);
|
|
208
193
|
}
|
|
209
|
-
notifyKeyListeners(getScopedListeners(
|
|
194
|
+
notifyKeyListeners(getScopedListeners(scope), key);
|
|
210
195
|
}
|
|
211
|
-
function
|
|
212
|
-
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
|
|
196
|
+
function handleWebStorageEvent(event) {
|
|
197
|
+
const key = event.key;
|
|
198
|
+
if (key === null) {
|
|
199
|
+
applyExternalChangeEvent(StorageScope.Disk, null, null);
|
|
200
|
+
applyExternalChangeEvent(StorageScope.Secure, null, null);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (key.startsWith(SECURE_WEB_PREFIX) || key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
204
|
+
applyExternalChangeEvent(StorageScope.Secure, key, event.newValue);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
applyExternalChangeEvent(StorageScope.Disk, key, event.newValue);
|
|
208
|
+
}
|
|
209
|
+
function subscribeToBackendChanges(scope) {
|
|
210
|
+
if (externalSyncUnsubscribers.has(scope)) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const backend = getWebBackend(scope);
|
|
214
|
+
if (!backend?.subscribe) {
|
|
215
|
+
return;
|
|
216
216
|
}
|
|
217
|
+
const unsubscribe = backend.subscribe(event => {
|
|
218
|
+
applyExternalChangeEvent(scope, event.key, event.newValue);
|
|
219
|
+
});
|
|
220
|
+
externalSyncUnsubscribers.set(scope, unsubscribe);
|
|
221
|
+
}
|
|
222
|
+
function resetBackendChangeSubscription(scope) {
|
|
223
|
+
externalSyncUnsubscribers.get(scope)?.();
|
|
224
|
+
externalSyncUnsubscribers.delete(scope);
|
|
217
225
|
}
|
|
218
|
-
function
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
hasWebStorageEventSubscription = false;
|
|
226
|
+
function ensureExternalSyncSubscriptions() {
|
|
227
|
+
if (!hasWindowStorageEventSubscription && typeof window !== "undefined" && typeof window.addEventListener === "function") {
|
|
228
|
+
window.addEventListener("storage", handleWebStorageEvent);
|
|
229
|
+
hasWindowStorageEventSubscription = true;
|
|
223
230
|
}
|
|
231
|
+
subscribeToBackendChanges(StorageScope.Disk);
|
|
232
|
+
subscribeToBackendChanges(StorageScope.Secure);
|
|
224
233
|
}
|
|
225
234
|
function getScopedListeners(scope) {
|
|
226
235
|
return webScopeListeners.get(scope);
|
|
@@ -276,12 +285,49 @@ function addKeyListener(registry, key, listener) {
|
|
|
276
285
|
function readPendingSecureWrite(key) {
|
|
277
286
|
return pendingSecureWrites.get(key)?.value;
|
|
278
287
|
}
|
|
288
|
+
function readPendingDiskWrite(key) {
|
|
289
|
+
return pendingDiskWrites.get(key)?.value;
|
|
290
|
+
}
|
|
291
|
+
function hasPendingDiskWrite(key) {
|
|
292
|
+
return pendingDiskWrites.has(key);
|
|
293
|
+
}
|
|
279
294
|
function hasPendingSecureWrite(key) {
|
|
280
295
|
return pendingSecureWrites.has(key);
|
|
281
296
|
}
|
|
297
|
+
function clearPendingDiskWrite(key) {
|
|
298
|
+
pendingDiskWrites.delete(key);
|
|
299
|
+
}
|
|
282
300
|
function clearPendingSecureWrite(key) {
|
|
283
301
|
pendingSecureWrites.delete(key);
|
|
284
302
|
}
|
|
303
|
+
function flushDiskWrites() {
|
|
304
|
+
diskFlushScheduled = false;
|
|
305
|
+
if (pendingDiskWrites.size === 0) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const writes = Array.from(pendingDiskWrites.values());
|
|
309
|
+
pendingDiskWrites.clear();
|
|
310
|
+
const keysToSet = [];
|
|
311
|
+
const valuesToSet = [];
|
|
312
|
+
const keysToRemove = [];
|
|
313
|
+
writes.forEach(({
|
|
314
|
+
key,
|
|
315
|
+
value
|
|
316
|
+
}) => {
|
|
317
|
+
if (value === undefined) {
|
|
318
|
+
keysToRemove.push(key);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
keysToSet.push(key);
|
|
322
|
+
valuesToSet.push(value);
|
|
323
|
+
});
|
|
324
|
+
if (keysToSet.length > 0) {
|
|
325
|
+
WebStorage.setBatch(keysToSet, valuesToSet, StorageScope.Disk);
|
|
326
|
+
}
|
|
327
|
+
if (keysToRemove.length > 0) {
|
|
328
|
+
WebStorage.removeBatch(keysToRemove, StorageScope.Disk);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
285
331
|
function flushSecureWrites() {
|
|
286
332
|
secureFlushScheduled = false;
|
|
287
333
|
if (pendingSecureWrites.size === 0) {
|
|
@@ -320,6 +366,17 @@ function flushSecureWrites() {
|
|
|
320
366
|
WebStorage.removeBatch(keysToRemove, StorageScope.Secure);
|
|
321
367
|
}
|
|
322
368
|
}
|
|
369
|
+
function scheduleDiskWrite(key, value) {
|
|
370
|
+
pendingDiskWrites.set(key, {
|
|
371
|
+
key,
|
|
372
|
+
value
|
|
373
|
+
});
|
|
374
|
+
if (diskFlushScheduled) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
diskFlushScheduled = true;
|
|
378
|
+
runMicrotask(flushDiskWrites);
|
|
379
|
+
}
|
|
323
380
|
function scheduleSecureWrite(key, value, accessControl) {
|
|
324
381
|
const pendingWrite = {
|
|
325
382
|
key,
|
|
@@ -340,117 +397,124 @@ const WebStorage = {
|
|
|
340
397
|
equals: other => other === WebStorage,
|
|
341
398
|
dispose: () => {},
|
|
342
399
|
set: (key, value, scope) => {
|
|
343
|
-
|
|
344
|
-
if (!storage) {
|
|
400
|
+
if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
|
|
345
401
|
return;
|
|
346
402
|
}
|
|
347
403
|
const storageKey = scope === StorageScope.Secure ? toSecureStorageKey(key) : key;
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
404
|
+
withWebBackendOperation(scope, "set", backend => {
|
|
405
|
+
backend.setItem(storageKey, value);
|
|
406
|
+
});
|
|
407
|
+
ensureWebScopeKeyIndex(scope).add(key);
|
|
408
|
+
notifyKeyListeners(getScopedListeners(scope), key);
|
|
353
409
|
},
|
|
354
410
|
get: (key, scope) => {
|
|
355
|
-
|
|
411
|
+
if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
|
|
412
|
+
return undefined;
|
|
413
|
+
}
|
|
356
414
|
const storageKey = scope === StorageScope.Secure ? toSecureStorageKey(key) : key;
|
|
357
|
-
|
|
415
|
+
const value = withWebBackendOperation(scope, "get", backend => backend.getItem(storageKey));
|
|
416
|
+
return value ?? undefined;
|
|
358
417
|
},
|
|
359
418
|
remove: (key, scope) => {
|
|
360
|
-
|
|
361
|
-
if (!storage) {
|
|
419
|
+
if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
|
|
362
420
|
return;
|
|
363
421
|
}
|
|
364
422
|
if (scope === StorageScope.Secure) {
|
|
365
|
-
|
|
366
|
-
|
|
423
|
+
withWebBackendOperation(scope, "remove", backend => {
|
|
424
|
+
if (backend.removeMany) {
|
|
425
|
+
backend.removeMany([toSecureStorageKey(key), toBiometricStorageKey(key)]);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
backend.removeItem(toSecureStorageKey(key));
|
|
429
|
+
backend.removeItem(toBiometricStorageKey(key));
|
|
430
|
+
});
|
|
367
431
|
} else {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
ensureWebScopeKeyIndex(scope).delete(key);
|
|
372
|
-
notifyKeyListeners(getScopedListeners(scope), key);
|
|
432
|
+
withWebBackendOperation(scope, "remove", backend => {
|
|
433
|
+
backend.removeItem(key);
|
|
434
|
+
});
|
|
373
435
|
}
|
|
436
|
+
ensureWebScopeKeyIndex(scope).delete(key);
|
|
437
|
+
notifyKeyListeners(getScopedListeners(scope), key);
|
|
374
438
|
},
|
|
375
439
|
clear: scope => {
|
|
376
|
-
|
|
377
|
-
if (!storage) {
|
|
440
|
+
if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
|
|
378
441
|
return;
|
|
379
442
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
keysToRemove.push(key);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
keysToRemove.forEach(key => storage.removeItem(key));
|
|
389
|
-
} else if (scope === StorageScope.Disk) {
|
|
390
|
-
const keysToRemove = [];
|
|
391
|
-
for (let i = 0; i < storage.length; i++) {
|
|
392
|
-
const key = storage.key(i);
|
|
393
|
-
if (key && !key.startsWith(SECURE_WEB_PREFIX) && !key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
394
|
-
keysToRemove.push(key);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
keysToRemove.forEach(key => storage.removeItem(key));
|
|
398
|
-
} else {
|
|
399
|
-
storage.clear();
|
|
400
|
-
}
|
|
401
|
-
if (scope === StorageScope.Disk || scope === StorageScope.Secure) {
|
|
402
|
-
ensureWebScopeKeyIndex(scope).clear();
|
|
403
|
-
notifyAllListeners(getScopedListeners(scope));
|
|
404
|
-
}
|
|
443
|
+
withWebBackendOperation(scope, "clear", backend => {
|
|
444
|
+
backend.clear();
|
|
445
|
+
});
|
|
446
|
+
ensureWebScopeKeyIndex(scope).clear();
|
|
447
|
+
notifyAllListeners(getScopedListeners(scope));
|
|
405
448
|
},
|
|
406
449
|
setBatch: (keys, values, scope) => {
|
|
407
|
-
|
|
408
|
-
if (!storage) {
|
|
450
|
+
if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
|
|
409
451
|
return;
|
|
410
452
|
}
|
|
453
|
+
const entries = [];
|
|
411
454
|
keys.forEach((key, index) => {
|
|
412
455
|
const value = values[index];
|
|
413
456
|
if (value === undefined) {
|
|
414
457
|
return;
|
|
415
458
|
}
|
|
416
|
-
|
|
417
|
-
storage.setItem(storageKey, value);
|
|
459
|
+
entries.push([scope === StorageScope.Secure ? toSecureStorageKey(key) : key, value]);
|
|
418
460
|
});
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
461
|
+
withWebBackendOperation(scope, "setBatch", backend => {
|
|
462
|
+
if (backend.setMany) {
|
|
463
|
+
backend.setMany(entries);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
entries.forEach(([storageKey, value]) => {
|
|
467
|
+
backend.setItem(storageKey, value);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
471
|
+
keys.forEach(key => keyIndex.add(key));
|
|
472
|
+
const listeners = getScopedListeners(scope);
|
|
473
|
+
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
425
474
|
},
|
|
426
475
|
getBatch: (keys, scope) => {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
476
|
+
if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
|
|
477
|
+
return keys.map(() => undefined);
|
|
478
|
+
}
|
|
479
|
+
const storageKeys = keys.map(key => scope === StorageScope.Secure ? toSecureStorageKey(key) : key);
|
|
480
|
+
const values = withWebBackendOperation(scope, "getBatch", backend => {
|
|
481
|
+
if (backend.getMany) {
|
|
482
|
+
return backend.getMany(storageKeys);
|
|
483
|
+
}
|
|
484
|
+
return storageKeys.map(storageKey => backend.getItem(storageKey));
|
|
431
485
|
});
|
|
486
|
+
return values.map(value => value ?? undefined);
|
|
432
487
|
},
|
|
433
488
|
removeBatch: (keys, scope) => {
|
|
434
|
-
|
|
435
|
-
if (!storage) {
|
|
489
|
+
if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
|
|
436
490
|
return;
|
|
437
491
|
}
|
|
438
492
|
if (scope === StorageScope.Secure) {
|
|
439
|
-
keys.
|
|
440
|
-
|
|
441
|
-
|
|
493
|
+
const storageKeys = keys.flatMap(key => [toSecureStorageKey(key), toBiometricStorageKey(key)]);
|
|
494
|
+
withWebBackendOperation(scope, "removeBatch", backend => {
|
|
495
|
+
if (backend.removeMany) {
|
|
496
|
+
backend.removeMany(storageKeys);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
storageKeys.forEach(storageKey => {
|
|
500
|
+
backend.removeItem(storageKey);
|
|
501
|
+
});
|
|
442
502
|
});
|
|
443
503
|
} else {
|
|
444
|
-
|
|
445
|
-
|
|
504
|
+
withWebBackendOperation(scope, "removeBatch", backend => {
|
|
505
|
+
if (backend.removeMany) {
|
|
506
|
+
backend.removeMany(keys);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
keys.forEach(key => {
|
|
510
|
+
backend.removeItem(key);
|
|
511
|
+
});
|
|
446
512
|
});
|
|
447
513
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
453
|
-
}
|
|
514
|
+
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
515
|
+
keys.forEach(key => keyIndex.delete(key));
|
|
516
|
+
const listeners = getScopedListeners(scope);
|
|
517
|
+
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
454
518
|
},
|
|
455
519
|
removeByPrefix: (prefix, scope) => {
|
|
456
520
|
if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
|
|
@@ -467,11 +531,13 @@ const WebStorage = {
|
|
|
467
531
|
return () => {};
|
|
468
532
|
},
|
|
469
533
|
has: (key, scope) => {
|
|
470
|
-
const storage = getBrowserStorage(scope);
|
|
471
534
|
if (scope === StorageScope.Secure) {
|
|
472
|
-
return
|
|
535
|
+
return withWebBackendOperation(scope, "has", backend => backend.getItem(toSecureStorageKey(key))) !== null || withWebBackendOperation(scope, "has", backend => backend.getItem(toBiometricStorageKey(key))) !== null;
|
|
473
536
|
}
|
|
474
|
-
|
|
537
|
+
if (scope !== StorageScope.Disk) {
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
return withWebBackendOperation(scope, "has", backend => backend.getItem(key)) !== null;
|
|
475
541
|
},
|
|
476
542
|
getAllKeys: scope => {
|
|
477
543
|
if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
|
|
@@ -502,40 +568,43 @@ const WebStorage = {
|
|
|
502
568
|
hasWarnedAboutWebBiometricFallback = true;
|
|
503
569
|
console.warn("[NitroStorage] Biometric storage is not supported on web. Using localStorage.");
|
|
504
570
|
}
|
|
505
|
-
|
|
571
|
+
withWebBackendOperation(StorageScope.Secure, "setSecureBiometric", backend => backend.setItem(toBiometricStorageKey(key), value));
|
|
506
572
|
ensureWebScopeKeyIndex(StorageScope.Secure).add(key);
|
|
507
573
|
notifyKeyListeners(getScopedListeners(StorageScope.Secure), key);
|
|
508
574
|
},
|
|
509
575
|
getSecureBiometric: key => {
|
|
510
|
-
|
|
576
|
+
const value = withWebBackendOperation(StorageScope.Secure, "getSecureBiometric", backend => backend.getItem(toBiometricStorageKey(key)));
|
|
577
|
+
return value ?? undefined;
|
|
511
578
|
},
|
|
512
579
|
deleteSecureBiometric: key => {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
if (storage?.getItem(toSecureStorageKey(key)) === null) {
|
|
580
|
+
withWebBackendOperation(StorageScope.Secure, "deleteSecureBiometric", backend => backend.removeItem(toBiometricStorageKey(key)));
|
|
581
|
+
if (withWebBackendOperation(StorageScope.Secure, "deleteSecureBiometric:getItem", backend => backend.getItem(toSecureStorageKey(key))) === null) {
|
|
516
582
|
ensureWebScopeKeyIndex(StorageScope.Secure).delete(key);
|
|
517
583
|
}
|
|
518
584
|
notifyKeyListeners(getScopedListeners(StorageScope.Secure), key);
|
|
519
585
|
},
|
|
520
586
|
hasSecureBiometric: key => {
|
|
521
|
-
return
|
|
587
|
+
return withWebBackendOperation(StorageScope.Secure, "hasSecureBiometric", backend => backend.getItem(toBiometricStorageKey(key))) !== null;
|
|
522
588
|
},
|
|
523
589
|
clearSecureBiometric: () => {
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
for (let i = 0; i < storage.length; i++) {
|
|
529
|
-
const k = storage.key(i);
|
|
530
|
-
if (k?.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
531
|
-
toRemove.push(k);
|
|
532
|
-
keysToNotify.push(fromBiometricStorageKey(k));
|
|
533
|
-
}
|
|
590
|
+
const storageKeys = withWebBackendOperation(StorageScope.Secure, "clearSecureBiometric:getAllKeys", backend => backend.getAllKeys());
|
|
591
|
+
const keysToNotify = storageKeys.filter(key => key.startsWith(BIOMETRIC_WEB_PREFIX)).map(key => fromBiometricStorageKey(key));
|
|
592
|
+
if (keysToNotify.length === 0) {
|
|
593
|
+
return;
|
|
534
594
|
}
|
|
535
|
-
|
|
595
|
+
withWebBackendOperation(StorageScope.Secure, "clearSecureBiometric", backend => {
|
|
596
|
+
const biometricKeys = keysToNotify.map(key => toBiometricStorageKey(key));
|
|
597
|
+
if (backend.removeMany) {
|
|
598
|
+
backend.removeMany(biometricKeys);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
biometricKeys.forEach(storageKey => {
|
|
602
|
+
backend.removeItem(storageKey);
|
|
603
|
+
});
|
|
604
|
+
});
|
|
536
605
|
const keyIndex = ensureWebScopeKeyIndex(StorageScope.Secure);
|
|
537
606
|
keysToNotify.forEach(key => {
|
|
538
|
-
if (
|
|
607
|
+
if (withWebBackendOperation(StorageScope.Secure, "clearSecureBiometric:getItem", backend => backend.getItem(toSecureStorageKey(key))) === null) {
|
|
539
608
|
keyIndex.delete(key);
|
|
540
609
|
}
|
|
541
610
|
});
|
|
@@ -549,6 +618,9 @@ function getRawValue(key, scope) {
|
|
|
549
618
|
const value = memoryStore.get(key);
|
|
550
619
|
return typeof value === "string" ? value : undefined;
|
|
551
620
|
}
|
|
621
|
+
if (scope === StorageScope.Disk && hasPendingDiskWrite(key)) {
|
|
622
|
+
return readPendingDiskWrite(key);
|
|
623
|
+
}
|
|
552
624
|
if (scope === StorageScope.Secure && hasPendingSecureWrite(key)) {
|
|
553
625
|
return readPendingSecureWrite(key);
|
|
554
626
|
}
|
|
@@ -561,6 +633,15 @@ function setRawValue(key, value, scope) {
|
|
|
561
633
|
notifyKeyListeners(memoryListeners, key);
|
|
562
634
|
return;
|
|
563
635
|
}
|
|
636
|
+
if (scope === StorageScope.Disk) {
|
|
637
|
+
cacheRawValue(scope, key, value);
|
|
638
|
+
if (diskWritesAsync) {
|
|
639
|
+
scheduleDiskWrite(key, value);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
flushDiskWrites();
|
|
643
|
+
clearPendingDiskWrite(key);
|
|
644
|
+
}
|
|
564
645
|
if (scope === StorageScope.Secure) {
|
|
565
646
|
flushSecureWrites();
|
|
566
647
|
clearPendingSecureWrite(key);
|
|
@@ -575,6 +656,15 @@ function removeRawValue(key, scope) {
|
|
|
575
656
|
notifyKeyListeners(memoryListeners, key);
|
|
576
657
|
return;
|
|
577
658
|
}
|
|
659
|
+
if (scope === StorageScope.Disk) {
|
|
660
|
+
cacheRawValue(scope, key, undefined);
|
|
661
|
+
if (diskWritesAsync) {
|
|
662
|
+
scheduleDiskWrite(key, undefined);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
flushDiskWrites();
|
|
666
|
+
clearPendingDiskWrite(key);
|
|
667
|
+
}
|
|
578
668
|
if (scope === StorageScope.Secure) {
|
|
579
669
|
flushSecureWrites();
|
|
580
670
|
clearPendingSecureWrite(key);
|
|
@@ -601,6 +691,10 @@ export const storage = {
|
|
|
601
691
|
notifyAllListeners(memoryListeners);
|
|
602
692
|
return;
|
|
603
693
|
}
|
|
694
|
+
if (scope === StorageScope.Disk) {
|
|
695
|
+
flushDiskWrites();
|
|
696
|
+
pendingDiskWrites.clear();
|
|
697
|
+
}
|
|
604
698
|
if (scope === StorageScope.Secure) {
|
|
605
699
|
flushSecureWrites();
|
|
606
700
|
pendingSecureWrites.clear();
|
|
@@ -629,6 +723,9 @@ export const storage = {
|
|
|
629
723
|
return;
|
|
630
724
|
}
|
|
631
725
|
const keyPrefix = prefixKey(namespace, "");
|
|
726
|
+
if (scope === StorageScope.Disk) {
|
|
727
|
+
flushDiskWrites();
|
|
728
|
+
}
|
|
632
729
|
if (scope === StorageScope.Secure) {
|
|
633
730
|
flushSecureWrites();
|
|
634
731
|
}
|
|
@@ -650,6 +747,12 @@ export const storage = {
|
|
|
650
747
|
return measureOperation("storage:has", scope, () => {
|
|
651
748
|
assertValidScope(scope);
|
|
652
749
|
if (scope === StorageScope.Memory) return memoryStore.has(key);
|
|
750
|
+
if (scope === StorageScope.Disk) {
|
|
751
|
+
flushDiskWrites();
|
|
752
|
+
}
|
|
753
|
+
if (scope === StorageScope.Secure) {
|
|
754
|
+
flushSecureWrites();
|
|
755
|
+
}
|
|
653
756
|
return WebStorage.has(key, scope);
|
|
654
757
|
});
|
|
655
758
|
},
|
|
@@ -657,6 +760,12 @@ export const storage = {
|
|
|
657
760
|
return measureOperation("storage:getAllKeys", scope, () => {
|
|
658
761
|
assertValidScope(scope);
|
|
659
762
|
if (scope === StorageScope.Memory) return Array.from(memoryStore.keys());
|
|
763
|
+
if (scope === StorageScope.Disk) {
|
|
764
|
+
flushDiskWrites();
|
|
765
|
+
}
|
|
766
|
+
if (scope === StorageScope.Secure) {
|
|
767
|
+
flushSecureWrites();
|
|
768
|
+
}
|
|
660
769
|
return WebStorage.getAllKeys(scope);
|
|
661
770
|
});
|
|
662
771
|
},
|
|
@@ -666,6 +775,12 @@ export const storage = {
|
|
|
666
775
|
if (scope === StorageScope.Memory) {
|
|
667
776
|
return Array.from(memoryStore.keys()).filter(key => key.startsWith(prefix));
|
|
668
777
|
}
|
|
778
|
+
if (scope === StorageScope.Disk) {
|
|
779
|
+
flushDiskWrites();
|
|
780
|
+
}
|
|
781
|
+
if (scope === StorageScope.Secure) {
|
|
782
|
+
flushSecureWrites();
|
|
783
|
+
}
|
|
669
784
|
return WebStorage.getKeysByPrefix(prefix, scope);
|
|
670
785
|
});
|
|
671
786
|
},
|
|
@@ -685,6 +800,12 @@ export const storage = {
|
|
|
685
800
|
});
|
|
686
801
|
return result;
|
|
687
802
|
}
|
|
803
|
+
if (scope === StorageScope.Disk) {
|
|
804
|
+
flushDiskWrites();
|
|
805
|
+
}
|
|
806
|
+
if (scope === StorageScope.Secure) {
|
|
807
|
+
flushSecureWrites();
|
|
808
|
+
}
|
|
688
809
|
const values = WebStorage.getBatch(keys, scope);
|
|
689
810
|
keys.forEach((key, index) => {
|
|
690
811
|
const value = values[index];
|
|
@@ -705,6 +826,12 @@ export const storage = {
|
|
|
705
826
|
});
|
|
706
827
|
return result;
|
|
707
828
|
}
|
|
829
|
+
if (scope === StorageScope.Disk) {
|
|
830
|
+
flushDiskWrites();
|
|
831
|
+
}
|
|
832
|
+
if (scope === StorageScope.Secure) {
|
|
833
|
+
flushSecureWrites();
|
|
834
|
+
}
|
|
708
835
|
const keys = WebStorage.getAllKeys(scope);
|
|
709
836
|
if (keys.length === 0) return {};
|
|
710
837
|
const values = WebStorage.getBatch(keys, scope);
|
|
@@ -721,6 +848,12 @@ export const storage = {
|
|
|
721
848
|
return measureOperation("storage:size", scope, () => {
|
|
722
849
|
assertValidScope(scope);
|
|
723
850
|
if (scope === StorageScope.Memory) return memoryStore.size;
|
|
851
|
+
if (scope === StorageScope.Disk) {
|
|
852
|
+
flushDiskWrites();
|
|
853
|
+
}
|
|
854
|
+
if (scope === StorageScope.Secure) {
|
|
855
|
+
flushSecureWrites();
|
|
856
|
+
}
|
|
724
857
|
return WebStorage.size(scope);
|
|
725
858
|
});
|
|
726
859
|
},
|
|
@@ -731,6 +864,19 @@ export const storage = {
|
|
|
731
864
|
setSecureWritesAsync: _enabled => {
|
|
732
865
|
recordMetric("storage:setSecureWritesAsync", StorageScope.Secure, 0);
|
|
733
866
|
},
|
|
867
|
+
setDiskWritesAsync: enabled => {
|
|
868
|
+
measureOperation("storage:setDiskWritesAsync", StorageScope.Disk, () => {
|
|
869
|
+
diskWritesAsync = enabled;
|
|
870
|
+
if (!enabled) {
|
|
871
|
+
flushDiskWrites();
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
},
|
|
875
|
+
flushDiskWrites: () => {
|
|
876
|
+
measureOperation("storage:flushDiskWrites", StorageScope.Disk, () => {
|
|
877
|
+
flushDiskWrites();
|
|
878
|
+
});
|
|
879
|
+
},
|
|
734
880
|
flushSecureWrites: () => {
|
|
735
881
|
measureOperation("storage:flushSecureWrites", StorageScope.Secure, () => {
|
|
736
882
|
flushSecureWrites();
|
|
@@ -757,6 +903,18 @@ export const storage = {
|
|
|
757
903
|
resetMetrics: () => {
|
|
758
904
|
metricsCounters.clear();
|
|
759
905
|
},
|
|
906
|
+
getCapabilities: () => ({
|
|
907
|
+
platform: "web",
|
|
908
|
+
backend: {
|
|
909
|
+
disk: getBackendName(StorageScope.Disk, webDiskStorageBackend),
|
|
910
|
+
secure: getBackendName(StorageScope.Secure, webSecureStorageBackend)
|
|
911
|
+
},
|
|
912
|
+
writeBuffering: {
|
|
913
|
+
disk: true,
|
|
914
|
+
secure: true
|
|
915
|
+
},
|
|
916
|
+
errorClassification: true
|
|
917
|
+
}),
|
|
760
918
|
getString: (key, scope) => {
|
|
761
919
|
return measureOperation("storage:getString", scope, () => {
|
|
762
920
|
return getRawValue(key, scope);
|
|
@@ -789,21 +947,50 @@ export const storage = {
|
|
|
789
947
|
flushSecureWrites();
|
|
790
948
|
WebStorage.setSecureAccessControl(secureDefaultAccessControl);
|
|
791
949
|
}
|
|
950
|
+
if (scope === StorageScope.Disk) {
|
|
951
|
+
flushDiskWrites();
|
|
952
|
+
}
|
|
792
953
|
WebStorage.setBatch(keys, values, scope);
|
|
793
954
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
794
955
|
}, keys.length);
|
|
795
956
|
}
|
|
796
957
|
};
|
|
797
958
|
export function setWebSecureStorageBackend(backend) {
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
959
|
+
pendingSecureWrites.clear();
|
|
960
|
+
webSecureStorageBackend = backend ?? createDefaultSecureBackend();
|
|
961
|
+
resetBackendChangeSubscription(StorageScope.Secure);
|
|
801
962
|
hydratedWebScopeKeyIndex.delete(StorageScope.Secure);
|
|
802
963
|
clearScopeRawCache(StorageScope.Secure);
|
|
964
|
+
ensureExternalSyncSubscriptions();
|
|
803
965
|
}
|
|
804
966
|
export function getWebSecureStorageBackend() {
|
|
805
967
|
return webSecureStorageBackend;
|
|
806
968
|
}
|
|
969
|
+
export function setWebDiskStorageBackend(backend) {
|
|
970
|
+
pendingDiskWrites.clear();
|
|
971
|
+
webDiskStorageBackend = backend ?? createDefaultDiskBackend();
|
|
972
|
+
resetBackendChangeSubscription(StorageScope.Disk);
|
|
973
|
+
hydratedWebScopeKeyIndex.delete(StorageScope.Disk);
|
|
974
|
+
clearScopeRawCache(StorageScope.Disk);
|
|
975
|
+
ensureExternalSyncSubscriptions();
|
|
976
|
+
}
|
|
977
|
+
export function getWebDiskStorageBackend() {
|
|
978
|
+
return webDiskStorageBackend;
|
|
979
|
+
}
|
|
980
|
+
export async function flushWebStorageBackends() {
|
|
981
|
+
flushDiskWrites();
|
|
982
|
+
flushSecureWrites();
|
|
983
|
+
const flushes = [];
|
|
984
|
+
const diskFlush = webDiskStorageBackend?.flush;
|
|
985
|
+
const secureFlush = webSecureStorageBackend?.flush;
|
|
986
|
+
if (diskFlush) {
|
|
987
|
+
flushes.push(diskFlush());
|
|
988
|
+
}
|
|
989
|
+
if (secureFlush) {
|
|
990
|
+
flushes.push(secureFlush());
|
|
991
|
+
}
|
|
992
|
+
await Promise.all(flushes);
|
|
993
|
+
}
|
|
807
994
|
function canUseRawBatchPath(item) {
|
|
808
995
|
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
|
|
809
996
|
}
|
|
@@ -831,6 +1018,7 @@ export function createStorageItem(config) {
|
|
|
831
1018
|
const expirationTtlMs = expiration?.ttlMs;
|
|
832
1019
|
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
833
1020
|
const readCache = !isMemory && config.readCache === true;
|
|
1021
|
+
const coalesceDiskWrites = config.scope === StorageScope.Disk && config.coalesceDiskWrites === true;
|
|
834
1022
|
const coalesceSecureWrites = config.scope === StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
|
|
835
1023
|
const defaultValue = config.defaultValue;
|
|
836
1024
|
const nonMemoryScope = config.scope === StorageScope.Disk ? StorageScope.Disk : config.scope === StorageScope.Secure ? StorageScope.Secure : null;
|
|
@@ -861,7 +1049,7 @@ export function createStorageItem(config) {
|
|
|
861
1049
|
unsubscribe = addKeyListener(memoryListeners, storageKey, listener);
|
|
862
1050
|
return;
|
|
863
1051
|
}
|
|
864
|
-
|
|
1052
|
+
ensureExternalSyncSubscriptions();
|
|
865
1053
|
unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), storageKey, listener);
|
|
866
1054
|
};
|
|
867
1055
|
const readStoredRaw = () => {
|
|
@@ -878,6 +1066,12 @@ export function createStorageItem(config) {
|
|
|
878
1066
|
}
|
|
879
1067
|
return memoryStore.get(storageKey);
|
|
880
1068
|
}
|
|
1069
|
+
if (nonMemoryScope === StorageScope.Disk) {
|
|
1070
|
+
const pending = pendingDiskWrites.get(storageKey);
|
|
1071
|
+
if (pending !== undefined) {
|
|
1072
|
+
return pending.value;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
881
1075
|
if (nonMemoryScope === StorageScope.Secure && !isBiometric) {
|
|
882
1076
|
const pending = pendingSecureWrites.get(storageKey);
|
|
883
1077
|
if (pending !== undefined) {
|
|
@@ -904,6 +1098,13 @@ export function createStorageItem(config) {
|
|
|
904
1098
|
return;
|
|
905
1099
|
}
|
|
906
1100
|
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
1101
|
+
if (nonMemoryScope === StorageScope.Disk) {
|
|
1102
|
+
if (coalesceDiskWrites || diskWritesAsync) {
|
|
1103
|
+
scheduleDiskWrite(storageKey, rawValue);
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
clearPendingDiskWrite(storageKey);
|
|
1107
|
+
}
|
|
907
1108
|
if (coalesceSecureWrites) {
|
|
908
1109
|
scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
|
|
909
1110
|
return;
|
|
@@ -919,6 +1120,13 @@ export function createStorageItem(config) {
|
|
|
919
1120
|
return;
|
|
920
1121
|
}
|
|
921
1122
|
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
1123
|
+
if (nonMemoryScope === StorageScope.Disk) {
|
|
1124
|
+
if (coalesceDiskWrites || diskWritesAsync) {
|
|
1125
|
+
scheduleDiskWrite(storageKey, undefined);
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
clearPendingDiskWrite(storageKey);
|
|
1129
|
+
}
|
|
922
1130
|
if (coalesceSecureWrites) {
|
|
923
1131
|
scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
|
|
924
1132
|
return;
|
|
@@ -1079,6 +1287,18 @@ export function createStorageItem(config) {
|
|
|
1079
1287
|
const hasItem = () => measureOperation("item:has", config.scope, () => {
|
|
1080
1288
|
if (isMemory) return memoryStore.has(storageKey);
|
|
1081
1289
|
if (isBiometric) return WebStorage.hasSecureBiometric(storageKey);
|
|
1290
|
+
if (nonMemoryScope === StorageScope.Disk) {
|
|
1291
|
+
const pending = pendingDiskWrites.get(storageKey);
|
|
1292
|
+
if (pending !== undefined) {
|
|
1293
|
+
return pending.value !== undefined;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if (nonMemoryScope === StorageScope.Secure) {
|
|
1297
|
+
const pending = pendingSecureWrites.get(storageKey);
|
|
1298
|
+
if (pending !== undefined) {
|
|
1299
|
+
return pending.value !== undefined;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1082
1302
|
return WebStorage.has(storageKey, config.scope);
|
|
1083
1303
|
});
|
|
1084
1304
|
const subscribe = callback => {
|
|
@@ -1089,9 +1309,6 @@ export function createStorageItem(config) {
|
|
|
1089
1309
|
if (listeners.size === 0 && unsubscribe) {
|
|
1090
1310
|
unsubscribe();
|
|
1091
1311
|
unsubscribe = null;
|
|
1092
|
-
if (!isMemory) {
|
|
1093
|
-
maybeCleanupWebStorageSubscription();
|
|
1094
|
-
}
|
|
1095
1312
|
}
|
|
1096
1313
|
};
|
|
1097
1314
|
};
|
|
@@ -1141,6 +1358,13 @@ export function getBatch(items, scope) {
|
|
|
1141
1358
|
const keysToFetch = [];
|
|
1142
1359
|
const keyIndexes = [];
|
|
1143
1360
|
items.forEach((item, index) => {
|
|
1361
|
+
if (scope === StorageScope.Disk) {
|
|
1362
|
+
const pending = pendingDiskWrites.get(item.key);
|
|
1363
|
+
if (pending !== undefined) {
|
|
1364
|
+
rawValues[index] = pending.value;
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1144
1368
|
if (scope === StorageScope.Secure) {
|
|
1145
1369
|
const pending = pendingSecureWrites.get(item.key);
|
|
1146
1370
|
if (pending !== undefined) {
|
|
@@ -1257,6 +1481,7 @@ export function setBatch(items, scope) {
|
|
|
1257
1481
|
});
|
|
1258
1482
|
return;
|
|
1259
1483
|
}
|
|
1484
|
+
flushDiskWrites();
|
|
1260
1485
|
const useRawBatchPath = items.every(({
|
|
1261
1486
|
item
|
|
1262
1487
|
}) => canUseRawBatchPath(asInternal(item)));
|
|
@@ -1281,6 +1506,9 @@ export function removeBatch(items, scope) {
|
|
|
1281
1506
|
return;
|
|
1282
1507
|
}
|
|
1283
1508
|
const keys = items.map(item => item.key);
|
|
1509
|
+
if (scope === StorageScope.Disk) {
|
|
1510
|
+
flushDiskWrites();
|
|
1511
|
+
}
|
|
1284
1512
|
if (scope === StorageScope.Secure) {
|
|
1285
1513
|
flushSecureWrites();
|
|
1286
1514
|
}
|
|
@@ -1326,6 +1554,9 @@ export function migrateToLatest(scope = StorageScope.Disk) {
|
|
|
1326
1554
|
export function runTransaction(scope, transaction) {
|
|
1327
1555
|
return measureOperation("transaction:run", scope, () => {
|
|
1328
1556
|
assertValidScope(scope);
|
|
1557
|
+
if (scope === StorageScope.Disk) {
|
|
1558
|
+
flushDiskWrites();
|
|
1559
|
+
}
|
|
1329
1560
|
if (scope === StorageScope.Secure) {
|
|
1330
1561
|
flushSecureWrites();
|
|
1331
1562
|
}
|
|
@@ -1392,6 +1623,9 @@ export function runTransaction(scope, transaction) {
|
|
|
1392
1623
|
valuesToSet.push(previousValue);
|
|
1393
1624
|
}
|
|
1394
1625
|
});
|
|
1626
|
+
if (scope === StorageScope.Disk) {
|
|
1627
|
+
flushDiskWrites();
|
|
1628
|
+
}
|
|
1395
1629
|
if (scope === StorageScope.Secure) {
|
|
1396
1630
|
flushSecureWrites();
|
|
1397
1631
|
}
|
|
@@ -1437,7 +1671,7 @@ export function createSecureAuthStorage(config, options) {
|
|
|
1437
1671
|
}
|
|
1438
1672
|
return result;
|
|
1439
1673
|
}
|
|
1440
|
-
export function isKeychainLockedError(
|
|
1441
|
-
return
|
|
1674
|
+
export function isKeychainLockedError(err) {
|
|
1675
|
+
return isLockedStorageErrorCode(getStorageErrorCode(err));
|
|
1442
1676
|
}
|
|
1443
1677
|
//# sourceMappingURL=index.web.js.map
|