react-native-nitro-storage 0.4.4 → 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 +107 -7
- 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/package.json +1 -1
- package/src/index.ts +264 -20
- package/src/index.web.ts +597 -245
- package/src/indexeddb-backend.ts +147 -6
- package/src/storage-runtime.ts +94 -0
- package/src/web-storage-backend.ts +129 -0
|
@@ -29,7 +29,15 @@ Object.defineProperty(exports, "createIndexedDBBackend", {
|
|
|
29
29
|
});
|
|
30
30
|
exports.createSecureAuthStorage = createSecureAuthStorage;
|
|
31
31
|
exports.createStorageItem = createStorageItem;
|
|
32
|
+
exports.flushWebStorageBackends = flushWebStorageBackends;
|
|
32
33
|
exports.getBatch = getBatch;
|
|
34
|
+
Object.defineProperty(exports, "getStorageErrorCode", {
|
|
35
|
+
enumerable: true,
|
|
36
|
+
get: function () {
|
|
37
|
+
return _storageRuntime.getStorageErrorCode;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
exports.getWebDiskStorageBackend = getWebDiskStorageBackend;
|
|
33
41
|
exports.getWebSecureStorageBackend = getWebSecureStorageBackend;
|
|
34
42
|
exports.isKeychainLockedError = isKeychainLockedError;
|
|
35
43
|
Object.defineProperty(exports, "migrateFromMMKV", {
|
|
@@ -43,6 +51,7 @@ exports.registerMigration = registerMigration;
|
|
|
43
51
|
exports.removeBatch = removeBatch;
|
|
44
52
|
exports.runTransaction = runTransaction;
|
|
45
53
|
exports.setBatch = setBatch;
|
|
54
|
+
exports.setWebDiskStorageBackend = setWebDiskStorageBackend;
|
|
46
55
|
exports.setWebSecureStorageBackend = setWebSecureStorageBackend;
|
|
47
56
|
exports.storage = void 0;
|
|
48
57
|
Object.defineProperty(exports, "useSetStorage", {
|
|
@@ -65,6 +74,8 @@ Object.defineProperty(exports, "useStorageSelector", {
|
|
|
65
74
|
});
|
|
66
75
|
var _Storage = require("./Storage.types");
|
|
67
76
|
var _internal = require("./internal");
|
|
77
|
+
var _webStorageBackend = require("./web-storage-backend");
|
|
78
|
+
var _storageRuntime = require("./storage-runtime");
|
|
68
79
|
var _migration = require("./migration");
|
|
69
80
|
var _storageHooks = require("./storage-hooks");
|
|
70
81
|
var _indexeddbBackend = require("./indexeddb-backend");
|
|
@@ -81,20 +92,23 @@ const registeredMigrations = new Map();
|
|
|
81
92
|
const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
|
|
82
93
|
Promise.resolve().then(task);
|
|
83
94
|
};
|
|
95
|
+
const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
|
|
84
96
|
const memoryStore = new Map();
|
|
85
97
|
const memoryListeners = new Map();
|
|
86
98
|
const webScopeListeners = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
|
|
87
99
|
const scopedRawCache = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
|
|
88
100
|
const webScopeKeyIndex = new Map([[_Storage.StorageScope.Disk, new Set()], [_Storage.StorageScope.Secure, new Set()]]);
|
|
89
101
|
const hydratedWebScopeKeyIndex = new Set();
|
|
102
|
+
const pendingDiskWrites = new Map();
|
|
103
|
+
let diskFlushScheduled = false;
|
|
104
|
+
let diskWritesAsync = false;
|
|
90
105
|
const pendingSecureWrites = new Map();
|
|
91
106
|
let secureFlushScheduled = false;
|
|
92
107
|
let secureDefaultAccessControl = _Storage.AccessControl.WhenUnlocked;
|
|
93
108
|
const SECURE_WEB_PREFIX = "__secure_";
|
|
94
109
|
const BIOMETRIC_WEB_PREFIX = "__bio_";
|
|
95
110
|
let hasWarnedAboutWebBiometricFallback = false;
|
|
96
|
-
let
|
|
97
|
-
let webStorageSubscriberCount = 0;
|
|
111
|
+
let hasWindowStorageEventSubscription = false;
|
|
98
112
|
let metricsObserver;
|
|
99
113
|
const metricsCounters = new Map();
|
|
100
114
|
function recordMetric(operation, scope, durationMs, keysCount = 1) {
|
|
@@ -121,61 +135,51 @@ function measureOperation(operation, scope, fn, keysCount = 1) {
|
|
|
121
135
|
if (!metricsObserver) {
|
|
122
136
|
return fn();
|
|
123
137
|
}
|
|
124
|
-
const start =
|
|
138
|
+
const start = now();
|
|
125
139
|
try {
|
|
126
140
|
return fn();
|
|
127
141
|
} finally {
|
|
128
|
-
recordMetric(operation, scope,
|
|
142
|
+
recordMetric(operation, scope, now() - start, keysCount);
|
|
129
143
|
}
|
|
130
144
|
}
|
|
131
|
-
function
|
|
132
|
-
return {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
clear: () => globalThis.localStorage?.clear(),
|
|
137
|
-
getAllKeys: () => {
|
|
138
|
-
const storage = globalThis.localStorage;
|
|
139
|
-
if (!storage) return [];
|
|
140
|
-
const keys = [];
|
|
141
|
-
for (let index = 0; index < storage.length; index += 1) {
|
|
142
|
-
const key = storage.key(index);
|
|
143
|
-
if (key) {
|
|
144
|
-
keys.push(key);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return keys;
|
|
148
|
-
}
|
|
149
|
-
};
|
|
145
|
+
function createDefaultDiskBackend() {
|
|
146
|
+
return (0, _webStorageBackend.createLocalStorageWebBackend)({
|
|
147
|
+
name: "localStorage:disk",
|
|
148
|
+
includeKey: key => !key.startsWith(SECURE_WEB_PREFIX) && !key.startsWith(BIOMETRIC_WEB_PREFIX)
|
|
149
|
+
});
|
|
150
150
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
151
|
+
function createDefaultSecureBackend() {
|
|
152
|
+
return (0, _webStorageBackend.createLocalStorageWebBackend)({
|
|
153
|
+
name: "localStorage:secure",
|
|
154
|
+
includeKey: key => key.startsWith(SECURE_WEB_PREFIX) || key.startsWith(BIOMETRIC_WEB_PREFIX)
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
let webDiskStorageBackend = createDefaultDiskBackend();
|
|
158
|
+
let webSecureStorageBackend = createDefaultSecureBackend();
|
|
159
|
+
const externalSyncUnsubscribers = new Map();
|
|
160
|
+
function getBackendName(scope, backend) {
|
|
161
|
+
const scopeName = scope === _Storage.StorageScope.Disk ? "disk" : "secure";
|
|
162
|
+
return backend?.name ?? `web:${scopeName}`;
|
|
163
|
+
}
|
|
164
|
+
function createWebStorageError(scope, operation, error, backend) {
|
|
165
|
+
const backendName = getBackendName(scope, backend);
|
|
166
|
+
const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
|
|
167
|
+
return new Error(`NitroStorage(web): ${operation} failed for ${backendName}: ${message}`);
|
|
168
|
+
}
|
|
169
|
+
function withWebBackendOperation(scope, operation, fn) {
|
|
170
|
+
const backend = scope === _Storage.StorageScope.Disk ? webDiskStorageBackend : webSecureStorageBackend;
|
|
171
|
+
if (!backend) {
|
|
172
|
+
throw new Error(`NitroStorage(web): ${operation} failed because no ${scope === _Storage.StorageScope.Disk ? "disk" : "secure"} backend is configured.`);
|
|
157
173
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return cachedSecureBrowserStorage;
|
|
164
|
-
}
|
|
165
|
-
cachedSecureBackendRef = webSecureStorageBackend;
|
|
166
|
-
cachedSecureBrowserStorage = {
|
|
167
|
-
setItem: (key, value) => webSecureStorageBackend.setItem(key, value),
|
|
168
|
-
getItem: key => webSecureStorageBackend.getItem(key) ?? null,
|
|
169
|
-
removeItem: key => webSecureStorageBackend.removeItem(key),
|
|
170
|
-
clear: () => webSecureStorageBackend.clear(),
|
|
171
|
-
key: index => webSecureStorageBackend.getAllKeys()[index] ?? null,
|
|
172
|
-
get length() {
|
|
173
|
-
return webSecureStorageBackend.getAllKeys().length;
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
return cachedSecureBrowserStorage;
|
|
174
|
+
try {
|
|
175
|
+
ensureExternalSyncSubscriptions();
|
|
176
|
+
return fn(backend);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
throw createWebStorageError(scope, operation, error, backend);
|
|
177
179
|
}
|
|
178
|
-
|
|
180
|
+
}
|
|
181
|
+
function getWebBackend(scope) {
|
|
182
|
+
return scope === _Storage.StorageScope.Disk ? webDiskStorageBackend : webSecureStorageBackend;
|
|
179
183
|
}
|
|
180
184
|
function toSecureStorageKey(key) {
|
|
181
185
|
return `${SECURE_WEB_PREFIX}${key}`;
|
|
@@ -196,28 +200,21 @@ function hydrateWebScopeKeyIndex(scope) {
|
|
|
196
200
|
if (hydratedWebScopeKeyIndex.has(scope)) {
|
|
197
201
|
return;
|
|
198
202
|
}
|
|
199
|
-
const
|
|
203
|
+
const backend = getWebBackend(scope);
|
|
200
204
|
const keyIndex = getWebScopeKeyIndex(scope);
|
|
201
205
|
keyIndex.clear();
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (key.startsWith(SECURE_WEB_PREFIX)) {
|
|
215
|
-
keyIndex.add(fromSecureStorageKey(key));
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
if (key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
219
|
-
keyIndex.add(fromBiometricStorageKey(key));
|
|
220
|
-
}
|
|
206
|
+
const keys = backend?.getAllKeys() ?? [];
|
|
207
|
+
for (const key of keys) {
|
|
208
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
209
|
+
keyIndex.add(key);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (key.startsWith(SECURE_WEB_PREFIX)) {
|
|
213
|
+
keyIndex.add(fromSecureStorageKey(key));
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
217
|
+
keyIndex.add(fromBiometricStorageKey(key));
|
|
221
218
|
}
|
|
222
219
|
}
|
|
223
220
|
hydratedWebScopeKeyIndex.add(scope);
|
|
@@ -226,65 +223,85 @@ function ensureWebScopeKeyIndex(scope) {
|
|
|
226
223
|
hydrateWebScopeKeyIndex(scope);
|
|
227
224
|
return getWebScopeKeyIndex(scope);
|
|
228
225
|
}
|
|
229
|
-
function
|
|
230
|
-
const key = event.key;
|
|
226
|
+
function applyExternalChangeEvent(scope, key, newValue) {
|
|
231
227
|
if (key === null) {
|
|
232
|
-
clearScopeRawCache(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).clear();
|
|
236
|
-
notifyAllListeners(getScopedListeners(_Storage.StorageScope.Disk));
|
|
237
|
-
notifyAllListeners(getScopedListeners(_Storage.StorageScope.Secure));
|
|
228
|
+
clearScopeRawCache(scope);
|
|
229
|
+
ensureWebScopeKeyIndex(scope).clear();
|
|
230
|
+
notifyAllListeners(getScopedListeners(scope));
|
|
238
231
|
return;
|
|
239
232
|
}
|
|
240
|
-
if (key.startsWith(SECURE_WEB_PREFIX)) {
|
|
233
|
+
if (scope === _Storage.StorageScope.Secure && key.startsWith(SECURE_WEB_PREFIX)) {
|
|
241
234
|
const plainKey = fromSecureStorageKey(key);
|
|
242
|
-
if (
|
|
235
|
+
if (newValue === null) {
|
|
243
236
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(plainKey);
|
|
244
237
|
cacheRawValue(_Storage.StorageScope.Secure, plainKey, undefined);
|
|
245
238
|
} else {
|
|
246
239
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(plainKey);
|
|
247
|
-
cacheRawValue(_Storage.StorageScope.Secure, plainKey,
|
|
240
|
+
cacheRawValue(_Storage.StorageScope.Secure, plainKey, newValue);
|
|
248
241
|
}
|
|
249
242
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), plainKey);
|
|
250
243
|
return;
|
|
251
244
|
}
|
|
252
|
-
if (key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
245
|
+
if (scope === _Storage.StorageScope.Secure && key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
253
246
|
const plainKey = fromBiometricStorageKey(key);
|
|
254
|
-
if (
|
|
255
|
-
if (
|
|
247
|
+
if (newValue === null) {
|
|
248
|
+
if (withWebBackendOperation(_Storage.StorageScope.Secure, "external-sync:getItem", backend => backend.getItem(toSecureStorageKey(plainKey))) === null) {
|
|
256
249
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(plainKey);
|
|
257
250
|
}
|
|
258
251
|
cacheRawValue(_Storage.StorageScope.Secure, plainKey, undefined);
|
|
259
252
|
} else {
|
|
260
253
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(plainKey);
|
|
261
|
-
cacheRawValue(_Storage.StorageScope.Secure, plainKey,
|
|
254
|
+
cacheRawValue(_Storage.StorageScope.Secure, plainKey, newValue);
|
|
262
255
|
}
|
|
263
256
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), plainKey);
|
|
264
257
|
return;
|
|
265
258
|
}
|
|
266
|
-
if (
|
|
267
|
-
ensureWebScopeKeyIndex(
|
|
268
|
-
cacheRawValue(
|
|
259
|
+
if (newValue === null) {
|
|
260
|
+
ensureWebScopeKeyIndex(scope).delete(key);
|
|
261
|
+
cacheRawValue(scope, key, undefined);
|
|
269
262
|
} else {
|
|
270
|
-
ensureWebScopeKeyIndex(
|
|
271
|
-
cacheRawValue(
|
|
263
|
+
ensureWebScopeKeyIndex(scope).add(key);
|
|
264
|
+
cacheRawValue(scope, key, newValue);
|
|
272
265
|
}
|
|
273
|
-
notifyKeyListeners(getScopedListeners(
|
|
266
|
+
notifyKeyListeners(getScopedListeners(scope), key);
|
|
274
267
|
}
|
|
275
|
-
function
|
|
276
|
-
|
|
277
|
-
if (
|
|
278
|
-
|
|
279
|
-
|
|
268
|
+
function handleWebStorageEvent(event) {
|
|
269
|
+
const key = event.key;
|
|
270
|
+
if (key === null) {
|
|
271
|
+
applyExternalChangeEvent(_Storage.StorageScope.Disk, null, null);
|
|
272
|
+
applyExternalChangeEvent(_Storage.StorageScope.Secure, null, null);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (key.startsWith(SECURE_WEB_PREFIX) || key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
276
|
+
applyExternalChangeEvent(_Storage.StorageScope.Secure, key, event.newValue);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
applyExternalChangeEvent(_Storage.StorageScope.Disk, key, event.newValue);
|
|
280
|
+
}
|
|
281
|
+
function subscribeToBackendChanges(scope) {
|
|
282
|
+
if (externalSyncUnsubscribers.has(scope)) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const backend = getWebBackend(scope);
|
|
286
|
+
if (!backend?.subscribe) {
|
|
287
|
+
return;
|
|
280
288
|
}
|
|
289
|
+
const unsubscribe = backend.subscribe(event => {
|
|
290
|
+
applyExternalChangeEvent(scope, event.key, event.newValue);
|
|
291
|
+
});
|
|
292
|
+
externalSyncUnsubscribers.set(scope, unsubscribe);
|
|
281
293
|
}
|
|
282
|
-
function
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
294
|
+
function resetBackendChangeSubscription(scope) {
|
|
295
|
+
externalSyncUnsubscribers.get(scope)?.();
|
|
296
|
+
externalSyncUnsubscribers.delete(scope);
|
|
297
|
+
}
|
|
298
|
+
function ensureExternalSyncSubscriptions() {
|
|
299
|
+
if (!hasWindowStorageEventSubscription && typeof window !== "undefined" && typeof window.addEventListener === "function") {
|
|
300
|
+
window.addEventListener("storage", handleWebStorageEvent);
|
|
301
|
+
hasWindowStorageEventSubscription = true;
|
|
287
302
|
}
|
|
303
|
+
subscribeToBackendChanges(_Storage.StorageScope.Disk);
|
|
304
|
+
subscribeToBackendChanges(_Storage.StorageScope.Secure);
|
|
288
305
|
}
|
|
289
306
|
function getScopedListeners(scope) {
|
|
290
307
|
return webScopeListeners.get(scope);
|
|
@@ -340,12 +357,49 @@ function addKeyListener(registry, key, listener) {
|
|
|
340
357
|
function readPendingSecureWrite(key) {
|
|
341
358
|
return pendingSecureWrites.get(key)?.value;
|
|
342
359
|
}
|
|
360
|
+
function readPendingDiskWrite(key) {
|
|
361
|
+
return pendingDiskWrites.get(key)?.value;
|
|
362
|
+
}
|
|
363
|
+
function hasPendingDiskWrite(key) {
|
|
364
|
+
return pendingDiskWrites.has(key);
|
|
365
|
+
}
|
|
343
366
|
function hasPendingSecureWrite(key) {
|
|
344
367
|
return pendingSecureWrites.has(key);
|
|
345
368
|
}
|
|
369
|
+
function clearPendingDiskWrite(key) {
|
|
370
|
+
pendingDiskWrites.delete(key);
|
|
371
|
+
}
|
|
346
372
|
function clearPendingSecureWrite(key) {
|
|
347
373
|
pendingSecureWrites.delete(key);
|
|
348
374
|
}
|
|
375
|
+
function flushDiskWrites() {
|
|
376
|
+
diskFlushScheduled = false;
|
|
377
|
+
if (pendingDiskWrites.size === 0) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const writes = Array.from(pendingDiskWrites.values());
|
|
381
|
+
pendingDiskWrites.clear();
|
|
382
|
+
const keysToSet = [];
|
|
383
|
+
const valuesToSet = [];
|
|
384
|
+
const keysToRemove = [];
|
|
385
|
+
writes.forEach(({
|
|
386
|
+
key,
|
|
387
|
+
value
|
|
388
|
+
}) => {
|
|
389
|
+
if (value === undefined) {
|
|
390
|
+
keysToRemove.push(key);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
keysToSet.push(key);
|
|
394
|
+
valuesToSet.push(value);
|
|
395
|
+
});
|
|
396
|
+
if (keysToSet.length > 0) {
|
|
397
|
+
WebStorage.setBatch(keysToSet, valuesToSet, _Storage.StorageScope.Disk);
|
|
398
|
+
}
|
|
399
|
+
if (keysToRemove.length > 0) {
|
|
400
|
+
WebStorage.removeBatch(keysToRemove, _Storage.StorageScope.Disk);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
349
403
|
function flushSecureWrites() {
|
|
350
404
|
secureFlushScheduled = false;
|
|
351
405
|
if (pendingSecureWrites.size === 0) {
|
|
@@ -384,6 +438,17 @@ function flushSecureWrites() {
|
|
|
384
438
|
WebStorage.removeBatch(keysToRemove, _Storage.StorageScope.Secure);
|
|
385
439
|
}
|
|
386
440
|
}
|
|
441
|
+
function scheduleDiskWrite(key, value) {
|
|
442
|
+
pendingDiskWrites.set(key, {
|
|
443
|
+
key,
|
|
444
|
+
value
|
|
445
|
+
});
|
|
446
|
+
if (diskFlushScheduled) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
diskFlushScheduled = true;
|
|
450
|
+
runMicrotask(flushDiskWrites);
|
|
451
|
+
}
|
|
387
452
|
function scheduleSecureWrite(key, value, accessControl) {
|
|
388
453
|
const pendingWrite = {
|
|
389
454
|
key,
|
|
@@ -404,117 +469,124 @@ const WebStorage = {
|
|
|
404
469
|
equals: other => other === WebStorage,
|
|
405
470
|
dispose: () => {},
|
|
406
471
|
set: (key, value, scope) => {
|
|
407
|
-
|
|
408
|
-
if (!storage) {
|
|
472
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
409
473
|
return;
|
|
410
474
|
}
|
|
411
475
|
const storageKey = scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key;
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
476
|
+
withWebBackendOperation(scope, "set", backend => {
|
|
477
|
+
backend.setItem(storageKey, value);
|
|
478
|
+
});
|
|
479
|
+
ensureWebScopeKeyIndex(scope).add(key);
|
|
480
|
+
notifyKeyListeners(getScopedListeners(scope), key);
|
|
417
481
|
},
|
|
418
482
|
get: (key, scope) => {
|
|
419
|
-
|
|
483
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
484
|
+
return undefined;
|
|
485
|
+
}
|
|
420
486
|
const storageKey = scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key;
|
|
421
|
-
|
|
487
|
+
const value = withWebBackendOperation(scope, "get", backend => backend.getItem(storageKey));
|
|
488
|
+
return value ?? undefined;
|
|
422
489
|
},
|
|
423
490
|
remove: (key, scope) => {
|
|
424
|
-
|
|
425
|
-
if (!storage) {
|
|
491
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
426
492
|
return;
|
|
427
493
|
}
|
|
428
494
|
if (scope === _Storage.StorageScope.Secure) {
|
|
429
|
-
|
|
430
|
-
|
|
495
|
+
withWebBackendOperation(scope, "remove", backend => {
|
|
496
|
+
if (backend.removeMany) {
|
|
497
|
+
backend.removeMany([toSecureStorageKey(key), toBiometricStorageKey(key)]);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
backend.removeItem(toSecureStorageKey(key));
|
|
501
|
+
backend.removeItem(toBiometricStorageKey(key));
|
|
502
|
+
});
|
|
431
503
|
} else {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
ensureWebScopeKeyIndex(scope).delete(key);
|
|
436
|
-
notifyKeyListeners(getScopedListeners(scope), key);
|
|
504
|
+
withWebBackendOperation(scope, "remove", backend => {
|
|
505
|
+
backend.removeItem(key);
|
|
506
|
+
});
|
|
437
507
|
}
|
|
508
|
+
ensureWebScopeKeyIndex(scope).delete(key);
|
|
509
|
+
notifyKeyListeners(getScopedListeners(scope), key);
|
|
438
510
|
},
|
|
439
511
|
clear: scope => {
|
|
440
|
-
|
|
441
|
-
if (!storage) {
|
|
512
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
442
513
|
return;
|
|
443
514
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
keysToRemove.push(key);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
keysToRemove.forEach(key => storage.removeItem(key));
|
|
453
|
-
} else if (scope === _Storage.StorageScope.Disk) {
|
|
454
|
-
const keysToRemove = [];
|
|
455
|
-
for (let i = 0; i < storage.length; i++) {
|
|
456
|
-
const key = storage.key(i);
|
|
457
|
-
if (key && !key.startsWith(SECURE_WEB_PREFIX) && !key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
458
|
-
keysToRemove.push(key);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
keysToRemove.forEach(key => storage.removeItem(key));
|
|
462
|
-
} else {
|
|
463
|
-
storage.clear();
|
|
464
|
-
}
|
|
465
|
-
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
466
|
-
ensureWebScopeKeyIndex(scope).clear();
|
|
467
|
-
notifyAllListeners(getScopedListeners(scope));
|
|
468
|
-
}
|
|
515
|
+
withWebBackendOperation(scope, "clear", backend => {
|
|
516
|
+
backend.clear();
|
|
517
|
+
});
|
|
518
|
+
ensureWebScopeKeyIndex(scope).clear();
|
|
519
|
+
notifyAllListeners(getScopedListeners(scope));
|
|
469
520
|
},
|
|
470
521
|
setBatch: (keys, values, scope) => {
|
|
471
|
-
|
|
472
|
-
if (!storage) {
|
|
522
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
473
523
|
return;
|
|
474
524
|
}
|
|
525
|
+
const entries = [];
|
|
475
526
|
keys.forEach((key, index) => {
|
|
476
527
|
const value = values[index];
|
|
477
528
|
if (value === undefined) {
|
|
478
529
|
return;
|
|
479
530
|
}
|
|
480
|
-
|
|
481
|
-
storage.setItem(storageKey, value);
|
|
531
|
+
entries.push([scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key, value]);
|
|
482
532
|
});
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
533
|
+
withWebBackendOperation(scope, "setBatch", backend => {
|
|
534
|
+
if (backend.setMany) {
|
|
535
|
+
backend.setMany(entries);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
entries.forEach(([storageKey, value]) => {
|
|
539
|
+
backend.setItem(storageKey, value);
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
543
|
+
keys.forEach(key => keyIndex.add(key));
|
|
544
|
+
const listeners = getScopedListeners(scope);
|
|
545
|
+
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
489
546
|
},
|
|
490
547
|
getBatch: (keys, scope) => {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
548
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
549
|
+
return keys.map(() => undefined);
|
|
550
|
+
}
|
|
551
|
+
const storageKeys = keys.map(key => scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key);
|
|
552
|
+
const values = withWebBackendOperation(scope, "getBatch", backend => {
|
|
553
|
+
if (backend.getMany) {
|
|
554
|
+
return backend.getMany(storageKeys);
|
|
555
|
+
}
|
|
556
|
+
return storageKeys.map(storageKey => backend.getItem(storageKey));
|
|
495
557
|
});
|
|
558
|
+
return values.map(value => value ?? undefined);
|
|
496
559
|
},
|
|
497
560
|
removeBatch: (keys, scope) => {
|
|
498
|
-
|
|
499
|
-
if (!storage) {
|
|
561
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
500
562
|
return;
|
|
501
563
|
}
|
|
502
564
|
if (scope === _Storage.StorageScope.Secure) {
|
|
503
|
-
keys.
|
|
504
|
-
|
|
505
|
-
|
|
565
|
+
const storageKeys = keys.flatMap(key => [toSecureStorageKey(key), toBiometricStorageKey(key)]);
|
|
566
|
+
withWebBackendOperation(scope, "removeBatch", backend => {
|
|
567
|
+
if (backend.removeMany) {
|
|
568
|
+
backend.removeMany(storageKeys);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
storageKeys.forEach(storageKey => {
|
|
572
|
+
backend.removeItem(storageKey);
|
|
573
|
+
});
|
|
506
574
|
});
|
|
507
575
|
} else {
|
|
508
|
-
|
|
509
|
-
|
|
576
|
+
withWebBackendOperation(scope, "removeBatch", backend => {
|
|
577
|
+
if (backend.removeMany) {
|
|
578
|
+
backend.removeMany(keys);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
keys.forEach(key => {
|
|
582
|
+
backend.removeItem(key);
|
|
583
|
+
});
|
|
510
584
|
});
|
|
511
585
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
517
|
-
}
|
|
586
|
+
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
587
|
+
keys.forEach(key => keyIndex.delete(key));
|
|
588
|
+
const listeners = getScopedListeners(scope);
|
|
589
|
+
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
518
590
|
},
|
|
519
591
|
removeByPrefix: (prefix, scope) => {
|
|
520
592
|
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
@@ -531,11 +603,13 @@ const WebStorage = {
|
|
|
531
603
|
return () => {};
|
|
532
604
|
},
|
|
533
605
|
has: (key, scope) => {
|
|
534
|
-
const storage = getBrowserStorage(scope);
|
|
535
606
|
if (scope === _Storage.StorageScope.Secure) {
|
|
536
|
-
return
|
|
607
|
+
return withWebBackendOperation(scope, "has", backend => backend.getItem(toSecureStorageKey(key))) !== null || withWebBackendOperation(scope, "has", backend => backend.getItem(toBiometricStorageKey(key))) !== null;
|
|
608
|
+
}
|
|
609
|
+
if (scope !== _Storage.StorageScope.Disk) {
|
|
610
|
+
return false;
|
|
537
611
|
}
|
|
538
|
-
return
|
|
612
|
+
return withWebBackendOperation(scope, "has", backend => backend.getItem(key)) !== null;
|
|
539
613
|
},
|
|
540
614
|
getAllKeys: scope => {
|
|
541
615
|
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
@@ -566,40 +640,43 @@ const WebStorage = {
|
|
|
566
640
|
hasWarnedAboutWebBiometricFallback = true;
|
|
567
641
|
console.warn("[NitroStorage] Biometric storage is not supported on web. Using localStorage.");
|
|
568
642
|
}
|
|
569
|
-
|
|
643
|
+
withWebBackendOperation(_Storage.StorageScope.Secure, "setSecureBiometric", backend => backend.setItem(toBiometricStorageKey(key), value));
|
|
570
644
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(key);
|
|
571
645
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), key);
|
|
572
646
|
},
|
|
573
647
|
getSecureBiometric: key => {
|
|
574
|
-
|
|
648
|
+
const value = withWebBackendOperation(_Storage.StorageScope.Secure, "getSecureBiometric", backend => backend.getItem(toBiometricStorageKey(key)));
|
|
649
|
+
return value ?? undefined;
|
|
575
650
|
},
|
|
576
651
|
deleteSecureBiometric: key => {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
if (storage?.getItem(toSecureStorageKey(key)) === null) {
|
|
652
|
+
withWebBackendOperation(_Storage.StorageScope.Secure, "deleteSecureBiometric", backend => backend.removeItem(toBiometricStorageKey(key)));
|
|
653
|
+
if (withWebBackendOperation(_Storage.StorageScope.Secure, "deleteSecureBiometric:getItem", backend => backend.getItem(toSecureStorageKey(key))) === null) {
|
|
580
654
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(key);
|
|
581
655
|
}
|
|
582
656
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), key);
|
|
583
657
|
},
|
|
584
658
|
hasSecureBiometric: key => {
|
|
585
|
-
return
|
|
659
|
+
return withWebBackendOperation(_Storage.StorageScope.Secure, "hasSecureBiometric", backend => backend.getItem(toBiometricStorageKey(key))) !== null;
|
|
586
660
|
},
|
|
587
661
|
clearSecureBiometric: () => {
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
for (let i = 0; i < storage.length; i++) {
|
|
593
|
-
const k = storage.key(i);
|
|
594
|
-
if (k?.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
595
|
-
toRemove.push(k);
|
|
596
|
-
keysToNotify.push(fromBiometricStorageKey(k));
|
|
597
|
-
}
|
|
662
|
+
const storageKeys = withWebBackendOperation(_Storage.StorageScope.Secure, "clearSecureBiometric:getAllKeys", backend => backend.getAllKeys());
|
|
663
|
+
const keysToNotify = storageKeys.filter(key => key.startsWith(BIOMETRIC_WEB_PREFIX)).map(key => fromBiometricStorageKey(key));
|
|
664
|
+
if (keysToNotify.length === 0) {
|
|
665
|
+
return;
|
|
598
666
|
}
|
|
599
|
-
|
|
667
|
+
withWebBackendOperation(_Storage.StorageScope.Secure, "clearSecureBiometric", backend => {
|
|
668
|
+
const biometricKeys = keysToNotify.map(key => toBiometricStorageKey(key));
|
|
669
|
+
if (backend.removeMany) {
|
|
670
|
+
backend.removeMany(biometricKeys);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
biometricKeys.forEach(storageKey => {
|
|
674
|
+
backend.removeItem(storageKey);
|
|
675
|
+
});
|
|
676
|
+
});
|
|
600
677
|
const keyIndex = ensureWebScopeKeyIndex(_Storage.StorageScope.Secure);
|
|
601
678
|
keysToNotify.forEach(key => {
|
|
602
|
-
if (
|
|
679
|
+
if (withWebBackendOperation(_Storage.StorageScope.Secure, "clearSecureBiometric:getItem", backend => backend.getItem(toSecureStorageKey(key))) === null) {
|
|
603
680
|
keyIndex.delete(key);
|
|
604
681
|
}
|
|
605
682
|
});
|
|
@@ -613,6 +690,9 @@ function getRawValue(key, scope) {
|
|
|
613
690
|
const value = memoryStore.get(key);
|
|
614
691
|
return typeof value === "string" ? value : undefined;
|
|
615
692
|
}
|
|
693
|
+
if (scope === _Storage.StorageScope.Disk && hasPendingDiskWrite(key)) {
|
|
694
|
+
return readPendingDiskWrite(key);
|
|
695
|
+
}
|
|
616
696
|
if (scope === _Storage.StorageScope.Secure && hasPendingSecureWrite(key)) {
|
|
617
697
|
return readPendingSecureWrite(key);
|
|
618
698
|
}
|
|
@@ -625,6 +705,15 @@ function setRawValue(key, value, scope) {
|
|
|
625
705
|
notifyKeyListeners(memoryListeners, key);
|
|
626
706
|
return;
|
|
627
707
|
}
|
|
708
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
709
|
+
cacheRawValue(scope, key, value);
|
|
710
|
+
if (diskWritesAsync) {
|
|
711
|
+
scheduleDiskWrite(key, value);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
flushDiskWrites();
|
|
715
|
+
clearPendingDiskWrite(key);
|
|
716
|
+
}
|
|
628
717
|
if (scope === _Storage.StorageScope.Secure) {
|
|
629
718
|
flushSecureWrites();
|
|
630
719
|
clearPendingSecureWrite(key);
|
|
@@ -639,6 +728,15 @@ function removeRawValue(key, scope) {
|
|
|
639
728
|
notifyKeyListeners(memoryListeners, key);
|
|
640
729
|
return;
|
|
641
730
|
}
|
|
731
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
732
|
+
cacheRawValue(scope, key, undefined);
|
|
733
|
+
if (diskWritesAsync) {
|
|
734
|
+
scheduleDiskWrite(key, undefined);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
flushDiskWrites();
|
|
738
|
+
clearPendingDiskWrite(key);
|
|
739
|
+
}
|
|
642
740
|
if (scope === _Storage.StorageScope.Secure) {
|
|
643
741
|
flushSecureWrites();
|
|
644
742
|
clearPendingSecureWrite(key);
|
|
@@ -665,6 +763,10 @@ const storage = exports.storage = {
|
|
|
665
763
|
notifyAllListeners(memoryListeners);
|
|
666
764
|
return;
|
|
667
765
|
}
|
|
766
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
767
|
+
flushDiskWrites();
|
|
768
|
+
pendingDiskWrites.clear();
|
|
769
|
+
}
|
|
668
770
|
if (scope === _Storage.StorageScope.Secure) {
|
|
669
771
|
flushSecureWrites();
|
|
670
772
|
pendingSecureWrites.clear();
|
|
@@ -693,6 +795,9 @@ const storage = exports.storage = {
|
|
|
693
795
|
return;
|
|
694
796
|
}
|
|
695
797
|
const keyPrefix = (0, _internal.prefixKey)(namespace, "");
|
|
798
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
799
|
+
flushDiskWrites();
|
|
800
|
+
}
|
|
696
801
|
if (scope === _Storage.StorageScope.Secure) {
|
|
697
802
|
flushSecureWrites();
|
|
698
803
|
}
|
|
@@ -714,6 +819,12 @@ const storage = exports.storage = {
|
|
|
714
819
|
return measureOperation("storage:has", scope, () => {
|
|
715
820
|
(0, _internal.assertValidScope)(scope);
|
|
716
821
|
if (scope === _Storage.StorageScope.Memory) return memoryStore.has(key);
|
|
822
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
823
|
+
flushDiskWrites();
|
|
824
|
+
}
|
|
825
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
826
|
+
flushSecureWrites();
|
|
827
|
+
}
|
|
717
828
|
return WebStorage.has(key, scope);
|
|
718
829
|
});
|
|
719
830
|
},
|
|
@@ -721,6 +832,12 @@ const storage = exports.storage = {
|
|
|
721
832
|
return measureOperation("storage:getAllKeys", scope, () => {
|
|
722
833
|
(0, _internal.assertValidScope)(scope);
|
|
723
834
|
if (scope === _Storage.StorageScope.Memory) return Array.from(memoryStore.keys());
|
|
835
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
836
|
+
flushDiskWrites();
|
|
837
|
+
}
|
|
838
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
839
|
+
flushSecureWrites();
|
|
840
|
+
}
|
|
724
841
|
return WebStorage.getAllKeys(scope);
|
|
725
842
|
});
|
|
726
843
|
},
|
|
@@ -730,6 +847,12 @@ const storage = exports.storage = {
|
|
|
730
847
|
if (scope === _Storage.StorageScope.Memory) {
|
|
731
848
|
return Array.from(memoryStore.keys()).filter(key => key.startsWith(prefix));
|
|
732
849
|
}
|
|
850
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
851
|
+
flushDiskWrites();
|
|
852
|
+
}
|
|
853
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
854
|
+
flushSecureWrites();
|
|
855
|
+
}
|
|
733
856
|
return WebStorage.getKeysByPrefix(prefix, scope);
|
|
734
857
|
});
|
|
735
858
|
},
|
|
@@ -749,6 +872,12 @@ const storage = exports.storage = {
|
|
|
749
872
|
});
|
|
750
873
|
return result;
|
|
751
874
|
}
|
|
875
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
876
|
+
flushDiskWrites();
|
|
877
|
+
}
|
|
878
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
879
|
+
flushSecureWrites();
|
|
880
|
+
}
|
|
752
881
|
const values = WebStorage.getBatch(keys, scope);
|
|
753
882
|
keys.forEach((key, index) => {
|
|
754
883
|
const value = values[index];
|
|
@@ -769,6 +898,12 @@ const storage = exports.storage = {
|
|
|
769
898
|
});
|
|
770
899
|
return result;
|
|
771
900
|
}
|
|
901
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
902
|
+
flushDiskWrites();
|
|
903
|
+
}
|
|
904
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
905
|
+
flushSecureWrites();
|
|
906
|
+
}
|
|
772
907
|
const keys = WebStorage.getAllKeys(scope);
|
|
773
908
|
if (keys.length === 0) return {};
|
|
774
909
|
const values = WebStorage.getBatch(keys, scope);
|
|
@@ -785,6 +920,12 @@ const storage = exports.storage = {
|
|
|
785
920
|
return measureOperation("storage:size", scope, () => {
|
|
786
921
|
(0, _internal.assertValidScope)(scope);
|
|
787
922
|
if (scope === _Storage.StorageScope.Memory) return memoryStore.size;
|
|
923
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
924
|
+
flushDiskWrites();
|
|
925
|
+
}
|
|
926
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
927
|
+
flushSecureWrites();
|
|
928
|
+
}
|
|
788
929
|
return WebStorage.size(scope);
|
|
789
930
|
});
|
|
790
931
|
},
|
|
@@ -795,6 +936,19 @@ const storage = exports.storage = {
|
|
|
795
936
|
setSecureWritesAsync: _enabled => {
|
|
796
937
|
recordMetric("storage:setSecureWritesAsync", _Storage.StorageScope.Secure, 0);
|
|
797
938
|
},
|
|
939
|
+
setDiskWritesAsync: enabled => {
|
|
940
|
+
measureOperation("storage:setDiskWritesAsync", _Storage.StorageScope.Disk, () => {
|
|
941
|
+
diskWritesAsync = enabled;
|
|
942
|
+
if (!enabled) {
|
|
943
|
+
flushDiskWrites();
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
},
|
|
947
|
+
flushDiskWrites: () => {
|
|
948
|
+
measureOperation("storage:flushDiskWrites", _Storage.StorageScope.Disk, () => {
|
|
949
|
+
flushDiskWrites();
|
|
950
|
+
});
|
|
951
|
+
},
|
|
798
952
|
flushSecureWrites: () => {
|
|
799
953
|
measureOperation("storage:flushSecureWrites", _Storage.StorageScope.Secure, () => {
|
|
800
954
|
flushSecureWrites();
|
|
@@ -821,6 +975,18 @@ const storage = exports.storage = {
|
|
|
821
975
|
resetMetrics: () => {
|
|
822
976
|
metricsCounters.clear();
|
|
823
977
|
},
|
|
978
|
+
getCapabilities: () => ({
|
|
979
|
+
platform: "web",
|
|
980
|
+
backend: {
|
|
981
|
+
disk: getBackendName(_Storage.StorageScope.Disk, webDiskStorageBackend),
|
|
982
|
+
secure: getBackendName(_Storage.StorageScope.Secure, webSecureStorageBackend)
|
|
983
|
+
},
|
|
984
|
+
writeBuffering: {
|
|
985
|
+
disk: true,
|
|
986
|
+
secure: true
|
|
987
|
+
},
|
|
988
|
+
errorClassification: true
|
|
989
|
+
}),
|
|
824
990
|
getString: (key, scope) => {
|
|
825
991
|
return measureOperation("storage:getString", scope, () => {
|
|
826
992
|
return getRawValue(key, scope);
|
|
@@ -853,21 +1019,50 @@ const storage = exports.storage = {
|
|
|
853
1019
|
flushSecureWrites();
|
|
854
1020
|
WebStorage.setSecureAccessControl(secureDefaultAccessControl);
|
|
855
1021
|
}
|
|
1022
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
1023
|
+
flushDiskWrites();
|
|
1024
|
+
}
|
|
856
1025
|
WebStorage.setBatch(keys, values, scope);
|
|
857
1026
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
858
1027
|
}, keys.length);
|
|
859
1028
|
}
|
|
860
1029
|
};
|
|
861
1030
|
function setWebSecureStorageBackend(backend) {
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1031
|
+
pendingSecureWrites.clear();
|
|
1032
|
+
webSecureStorageBackend = backend ?? createDefaultSecureBackend();
|
|
1033
|
+
resetBackendChangeSubscription(_Storage.StorageScope.Secure);
|
|
865
1034
|
hydratedWebScopeKeyIndex.delete(_Storage.StorageScope.Secure);
|
|
866
1035
|
clearScopeRawCache(_Storage.StorageScope.Secure);
|
|
1036
|
+
ensureExternalSyncSubscriptions();
|
|
867
1037
|
}
|
|
868
1038
|
function getWebSecureStorageBackend() {
|
|
869
1039
|
return webSecureStorageBackend;
|
|
870
1040
|
}
|
|
1041
|
+
function setWebDiskStorageBackend(backend) {
|
|
1042
|
+
pendingDiskWrites.clear();
|
|
1043
|
+
webDiskStorageBackend = backend ?? createDefaultDiskBackend();
|
|
1044
|
+
resetBackendChangeSubscription(_Storage.StorageScope.Disk);
|
|
1045
|
+
hydratedWebScopeKeyIndex.delete(_Storage.StorageScope.Disk);
|
|
1046
|
+
clearScopeRawCache(_Storage.StorageScope.Disk);
|
|
1047
|
+
ensureExternalSyncSubscriptions();
|
|
1048
|
+
}
|
|
1049
|
+
function getWebDiskStorageBackend() {
|
|
1050
|
+
return webDiskStorageBackend;
|
|
1051
|
+
}
|
|
1052
|
+
async function flushWebStorageBackends() {
|
|
1053
|
+
flushDiskWrites();
|
|
1054
|
+
flushSecureWrites();
|
|
1055
|
+
const flushes = [];
|
|
1056
|
+
const diskFlush = webDiskStorageBackend?.flush;
|
|
1057
|
+
const secureFlush = webSecureStorageBackend?.flush;
|
|
1058
|
+
if (diskFlush) {
|
|
1059
|
+
flushes.push(diskFlush());
|
|
1060
|
+
}
|
|
1061
|
+
if (secureFlush) {
|
|
1062
|
+
flushes.push(secureFlush());
|
|
1063
|
+
}
|
|
1064
|
+
await Promise.all(flushes);
|
|
1065
|
+
}
|
|
871
1066
|
function canUseRawBatchPath(item) {
|
|
872
1067
|
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
|
|
873
1068
|
}
|
|
@@ -895,6 +1090,7 @@ function createStorageItem(config) {
|
|
|
895
1090
|
const expirationTtlMs = expiration?.ttlMs;
|
|
896
1091
|
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
897
1092
|
const readCache = !isMemory && config.readCache === true;
|
|
1093
|
+
const coalesceDiskWrites = config.scope === _Storage.StorageScope.Disk && config.coalesceDiskWrites === true;
|
|
898
1094
|
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
|
|
899
1095
|
const defaultValue = config.defaultValue;
|
|
900
1096
|
const nonMemoryScope = config.scope === _Storage.StorageScope.Disk ? _Storage.StorageScope.Disk : config.scope === _Storage.StorageScope.Secure ? _Storage.StorageScope.Secure : null;
|
|
@@ -925,7 +1121,7 @@ function createStorageItem(config) {
|
|
|
925
1121
|
unsubscribe = addKeyListener(memoryListeners, storageKey, listener);
|
|
926
1122
|
return;
|
|
927
1123
|
}
|
|
928
|
-
|
|
1124
|
+
ensureExternalSyncSubscriptions();
|
|
929
1125
|
unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), storageKey, listener);
|
|
930
1126
|
};
|
|
931
1127
|
const readStoredRaw = () => {
|
|
@@ -942,6 +1138,12 @@ function createStorageItem(config) {
|
|
|
942
1138
|
}
|
|
943
1139
|
return memoryStore.get(storageKey);
|
|
944
1140
|
}
|
|
1141
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1142
|
+
const pending = pendingDiskWrites.get(storageKey);
|
|
1143
|
+
if (pending !== undefined) {
|
|
1144
|
+
return pending.value;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
945
1147
|
if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
|
|
946
1148
|
const pending = pendingSecureWrites.get(storageKey);
|
|
947
1149
|
if (pending !== undefined) {
|
|
@@ -968,6 +1170,13 @@ function createStorageItem(config) {
|
|
|
968
1170
|
return;
|
|
969
1171
|
}
|
|
970
1172
|
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
1173
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1174
|
+
if (coalesceDiskWrites || diskWritesAsync) {
|
|
1175
|
+
scheduleDiskWrite(storageKey, rawValue);
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
clearPendingDiskWrite(storageKey);
|
|
1179
|
+
}
|
|
971
1180
|
if (coalesceSecureWrites) {
|
|
972
1181
|
scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
|
|
973
1182
|
return;
|
|
@@ -983,6 +1192,13 @@ function createStorageItem(config) {
|
|
|
983
1192
|
return;
|
|
984
1193
|
}
|
|
985
1194
|
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
1195
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1196
|
+
if (coalesceDiskWrites || diskWritesAsync) {
|
|
1197
|
+
scheduleDiskWrite(storageKey, undefined);
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
clearPendingDiskWrite(storageKey);
|
|
1201
|
+
}
|
|
986
1202
|
if (coalesceSecureWrites) {
|
|
987
1203
|
scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
|
|
988
1204
|
return;
|
|
@@ -1143,6 +1359,18 @@ function createStorageItem(config) {
|
|
|
1143
1359
|
const hasItem = () => measureOperation("item:has", config.scope, () => {
|
|
1144
1360
|
if (isMemory) return memoryStore.has(storageKey);
|
|
1145
1361
|
if (isBiometric) return WebStorage.hasSecureBiometric(storageKey);
|
|
1362
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1363
|
+
const pending = pendingDiskWrites.get(storageKey);
|
|
1364
|
+
if (pending !== undefined) {
|
|
1365
|
+
return pending.value !== undefined;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
1369
|
+
const pending = pendingSecureWrites.get(storageKey);
|
|
1370
|
+
if (pending !== undefined) {
|
|
1371
|
+
return pending.value !== undefined;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1146
1374
|
return WebStorage.has(storageKey, config.scope);
|
|
1147
1375
|
});
|
|
1148
1376
|
const subscribe = callback => {
|
|
@@ -1153,9 +1381,6 @@ function createStorageItem(config) {
|
|
|
1153
1381
|
if (listeners.size === 0 && unsubscribe) {
|
|
1154
1382
|
unsubscribe();
|
|
1155
1383
|
unsubscribe = null;
|
|
1156
|
-
if (!isMemory) {
|
|
1157
|
-
maybeCleanupWebStorageSubscription();
|
|
1158
|
-
}
|
|
1159
1384
|
}
|
|
1160
1385
|
};
|
|
1161
1386
|
};
|
|
@@ -1203,6 +1428,13 @@ function getBatch(items, scope) {
|
|
|
1203
1428
|
const keysToFetch = [];
|
|
1204
1429
|
const keyIndexes = [];
|
|
1205
1430
|
items.forEach((item, index) => {
|
|
1431
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
1432
|
+
const pending = pendingDiskWrites.get(item.key);
|
|
1433
|
+
if (pending !== undefined) {
|
|
1434
|
+
rawValues[index] = pending.value;
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1206
1438
|
if (scope === _Storage.StorageScope.Secure) {
|
|
1207
1439
|
const pending = pendingSecureWrites.get(item.key);
|
|
1208
1440
|
if (pending !== undefined) {
|
|
@@ -1319,6 +1551,7 @@ function setBatch(items, scope) {
|
|
|
1319
1551
|
});
|
|
1320
1552
|
return;
|
|
1321
1553
|
}
|
|
1554
|
+
flushDiskWrites();
|
|
1322
1555
|
const useRawBatchPath = items.every(({
|
|
1323
1556
|
item
|
|
1324
1557
|
}) => canUseRawBatchPath(asInternal(item)));
|
|
@@ -1343,6 +1576,9 @@ function removeBatch(items, scope) {
|
|
|
1343
1576
|
return;
|
|
1344
1577
|
}
|
|
1345
1578
|
const keys = items.map(item => item.key);
|
|
1579
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
1580
|
+
flushDiskWrites();
|
|
1581
|
+
}
|
|
1346
1582
|
if (scope === _Storage.StorageScope.Secure) {
|
|
1347
1583
|
flushSecureWrites();
|
|
1348
1584
|
}
|
|
@@ -1388,6 +1624,9 @@ function migrateToLatest(scope = _Storage.StorageScope.Disk) {
|
|
|
1388
1624
|
function runTransaction(scope, transaction) {
|
|
1389
1625
|
return measureOperation("transaction:run", scope, () => {
|
|
1390
1626
|
(0, _internal.assertValidScope)(scope);
|
|
1627
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
1628
|
+
flushDiskWrites();
|
|
1629
|
+
}
|
|
1391
1630
|
if (scope === _Storage.StorageScope.Secure) {
|
|
1392
1631
|
flushSecureWrites();
|
|
1393
1632
|
}
|
|
@@ -1454,6 +1693,9 @@ function runTransaction(scope, transaction) {
|
|
|
1454
1693
|
valuesToSet.push(previousValue);
|
|
1455
1694
|
}
|
|
1456
1695
|
});
|
|
1696
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
1697
|
+
flushDiskWrites();
|
|
1698
|
+
}
|
|
1457
1699
|
if (scope === _Storage.StorageScope.Secure) {
|
|
1458
1700
|
flushSecureWrites();
|
|
1459
1701
|
}
|
|
@@ -1499,7 +1741,7 @@ function createSecureAuthStorage(config, options) {
|
|
|
1499
1741
|
}
|
|
1500
1742
|
return result;
|
|
1501
1743
|
}
|
|
1502
|
-
function isKeychainLockedError(
|
|
1503
|
-
return
|
|
1744
|
+
function isKeychainLockedError(err) {
|
|
1745
|
+
return (0, _storageRuntime.isLockedStorageErrorCode)((0, _storageRuntime.getStorageErrorCode)(err));
|
|
1504
1746
|
}
|
|
1505
1747
|
//# sourceMappingURL=index.web.js.map
|