react-native-nitro-storage 0.4.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +237 -862
- package/SECURITY.md +26 -0
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +61 -10
- package/docs/api-reference.md +217 -0
- package/docs/batch-transactions-migrations.md +186 -0
- package/docs/benchmarks.md +37 -0
- package/docs/mmkv-migration.md +80 -0
- package/docs/react-hooks.md +113 -0
- package/docs/recipes.md +281 -0
- package/docs/secure-storage.md +171 -0
- package/docs/web-backends.md +141 -0
- package/ios/IOSStorageAdapterCpp.mm +44 -14
- package/lib/commonjs/index.js +271 -5
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +498 -202
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/indexeddb-backend.js +129 -7
- package/lib/commonjs/indexeddb-backend.js.map +1 -1
- package/lib/commonjs/storage-runtime.js +41 -0
- package/lib/commonjs/storage-runtime.js.map +1 -0
- package/lib/commonjs/web-storage-backend.js +90 -0
- package/lib/commonjs/web-storage-backend.js.map +1 -0
- package/lib/module/index.js +263 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +490 -202
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/indexeddb-backend.js +129 -7
- package/lib/module/indexeddb-backend.js.map +1 -1
- package/lib/module/storage-runtime.js +36 -0
- package/lib/module/storage-runtime.js.map +1 -0
- package/lib/module/web-storage-backend.js +86 -0
- package/lib/module/web-storage-backend.js.map +1 -0
- package/lib/typescript/index.d.ts +14 -7
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +15 -8
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/indexeddb-backend.d.ts +6 -2
- package/lib/typescript/indexeddb-backend.d.ts.map +1 -1
- package/lib/typescript/storage-runtime.d.ts +48 -0
- package/lib/typescript/storage-runtime.d.ts.map +1 -0
- package/lib/typescript/web-storage-backend.d.ts +30 -0
- package/lib/typescript/web-storage-backend.d.ts.map +1 -0
- package/package.json +21 -8
- package/src/index.ts +330 -20
- package/src/index.web.ts +673 -245
- package/src/indexeddb-backend.ts +147 -6
- package/src/storage-runtime.ts +129 -0
- package/src/web-storage-backend.ts +129 -0
|
@@ -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,54 @@ 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 getWebSecureEncryptionStatus(backend) {
|
|
165
|
+
return backend?.name === "localStorage:secure" ? "unavailable" : "unknown";
|
|
166
|
+
}
|
|
167
|
+
function createWebStorageError(scope, operation, error, backend) {
|
|
168
|
+
const backendName = getBackendName(scope, backend);
|
|
169
|
+
const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
|
|
170
|
+
return new Error(`NitroStorage(web): ${operation} failed for ${backendName}: ${message}`);
|
|
171
|
+
}
|
|
172
|
+
function withWebBackendOperation(scope, operation, fn) {
|
|
173
|
+
const backend = scope === _Storage.StorageScope.Disk ? webDiskStorageBackend : webSecureStorageBackend;
|
|
174
|
+
if (!backend) {
|
|
175
|
+
throw new Error(`NitroStorage(web): ${operation} failed because no ${scope === _Storage.StorageScope.Disk ? "disk" : "secure"} backend is configured.`);
|
|
157
176
|
}
|
|
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;
|
|
177
|
+
try {
|
|
178
|
+
ensureExternalSyncSubscriptions();
|
|
179
|
+
return fn(backend);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
throw createWebStorageError(scope, operation, error, backend);
|
|
177
182
|
}
|
|
178
|
-
|
|
183
|
+
}
|
|
184
|
+
function getWebBackend(scope) {
|
|
185
|
+
return scope === _Storage.StorageScope.Disk ? webDiskStorageBackend : webSecureStorageBackend;
|
|
179
186
|
}
|
|
180
187
|
function toSecureStorageKey(key) {
|
|
181
188
|
return `${SECURE_WEB_PREFIX}${key}`;
|
|
@@ -196,28 +203,21 @@ function hydrateWebScopeKeyIndex(scope) {
|
|
|
196
203
|
if (hydratedWebScopeKeyIndex.has(scope)) {
|
|
197
204
|
return;
|
|
198
205
|
}
|
|
199
|
-
const
|
|
206
|
+
const backend = getWebBackend(scope);
|
|
200
207
|
const keyIndex = getWebScopeKeyIndex(scope);
|
|
201
208
|
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
|
-
}
|
|
209
|
+
const keys = backend?.getAllKeys() ?? [];
|
|
210
|
+
for (const key of keys) {
|
|
211
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
212
|
+
keyIndex.add(key);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (key.startsWith(SECURE_WEB_PREFIX)) {
|
|
216
|
+
keyIndex.add(fromSecureStorageKey(key));
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
220
|
+
keyIndex.add(fromBiometricStorageKey(key));
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
223
|
hydratedWebScopeKeyIndex.add(scope);
|
|
@@ -226,65 +226,85 @@ function ensureWebScopeKeyIndex(scope) {
|
|
|
226
226
|
hydrateWebScopeKeyIndex(scope);
|
|
227
227
|
return getWebScopeKeyIndex(scope);
|
|
228
228
|
}
|
|
229
|
-
function
|
|
230
|
-
const key = event.key;
|
|
229
|
+
function applyExternalChangeEvent(scope, key, newValue) {
|
|
231
230
|
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));
|
|
231
|
+
clearScopeRawCache(scope);
|
|
232
|
+
ensureWebScopeKeyIndex(scope).clear();
|
|
233
|
+
notifyAllListeners(getScopedListeners(scope));
|
|
238
234
|
return;
|
|
239
235
|
}
|
|
240
|
-
if (key.startsWith(SECURE_WEB_PREFIX)) {
|
|
236
|
+
if (scope === _Storage.StorageScope.Secure && key.startsWith(SECURE_WEB_PREFIX)) {
|
|
241
237
|
const plainKey = fromSecureStorageKey(key);
|
|
242
|
-
if (
|
|
238
|
+
if (newValue === null) {
|
|
243
239
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(plainKey);
|
|
244
240
|
cacheRawValue(_Storage.StorageScope.Secure, plainKey, undefined);
|
|
245
241
|
} else {
|
|
246
242
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(plainKey);
|
|
247
|
-
cacheRawValue(_Storage.StorageScope.Secure, plainKey,
|
|
243
|
+
cacheRawValue(_Storage.StorageScope.Secure, plainKey, newValue);
|
|
248
244
|
}
|
|
249
245
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), plainKey);
|
|
250
246
|
return;
|
|
251
247
|
}
|
|
252
|
-
if (key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
248
|
+
if (scope === _Storage.StorageScope.Secure && key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
253
249
|
const plainKey = fromBiometricStorageKey(key);
|
|
254
|
-
if (
|
|
255
|
-
if (
|
|
250
|
+
if (newValue === null) {
|
|
251
|
+
if (withWebBackendOperation(_Storage.StorageScope.Secure, "external-sync:getItem", backend => backend.getItem(toSecureStorageKey(plainKey))) === null) {
|
|
256
252
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(plainKey);
|
|
257
253
|
}
|
|
258
254
|
cacheRawValue(_Storage.StorageScope.Secure, plainKey, undefined);
|
|
259
255
|
} else {
|
|
260
256
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(plainKey);
|
|
261
|
-
cacheRawValue(_Storage.StorageScope.Secure, plainKey,
|
|
257
|
+
cacheRawValue(_Storage.StorageScope.Secure, plainKey, newValue);
|
|
262
258
|
}
|
|
263
259
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), plainKey);
|
|
264
260
|
return;
|
|
265
261
|
}
|
|
266
|
-
if (
|
|
267
|
-
ensureWebScopeKeyIndex(
|
|
268
|
-
cacheRawValue(
|
|
262
|
+
if (newValue === null) {
|
|
263
|
+
ensureWebScopeKeyIndex(scope).delete(key);
|
|
264
|
+
cacheRawValue(scope, key, undefined);
|
|
269
265
|
} else {
|
|
270
|
-
ensureWebScopeKeyIndex(
|
|
271
|
-
cacheRawValue(
|
|
266
|
+
ensureWebScopeKeyIndex(scope).add(key);
|
|
267
|
+
cacheRawValue(scope, key, newValue);
|
|
272
268
|
}
|
|
273
|
-
notifyKeyListeners(getScopedListeners(
|
|
269
|
+
notifyKeyListeners(getScopedListeners(scope), key);
|
|
274
270
|
}
|
|
275
|
-
function
|
|
276
|
-
|
|
277
|
-
if (
|
|
278
|
-
|
|
279
|
-
|
|
271
|
+
function handleWebStorageEvent(event) {
|
|
272
|
+
const key = event.key;
|
|
273
|
+
if (key === null) {
|
|
274
|
+
applyExternalChangeEvent(_Storage.StorageScope.Disk, null, null);
|
|
275
|
+
applyExternalChangeEvent(_Storage.StorageScope.Secure, null, null);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (key.startsWith(SECURE_WEB_PREFIX) || key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
279
|
+
applyExternalChangeEvent(_Storage.StorageScope.Secure, key, event.newValue);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
applyExternalChangeEvent(_Storage.StorageScope.Disk, key, event.newValue);
|
|
283
|
+
}
|
|
284
|
+
function subscribeToBackendChanges(scope) {
|
|
285
|
+
if (externalSyncUnsubscribers.has(scope)) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const backend = getWebBackend(scope);
|
|
289
|
+
if (!backend?.subscribe) {
|
|
290
|
+
return;
|
|
280
291
|
}
|
|
292
|
+
const unsubscribe = backend.subscribe(event => {
|
|
293
|
+
applyExternalChangeEvent(scope, event.key, event.newValue);
|
|
294
|
+
});
|
|
295
|
+
externalSyncUnsubscribers.set(scope, unsubscribe);
|
|
281
296
|
}
|
|
282
|
-
function
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
297
|
+
function resetBackendChangeSubscription(scope) {
|
|
298
|
+
externalSyncUnsubscribers.get(scope)?.();
|
|
299
|
+
externalSyncUnsubscribers.delete(scope);
|
|
300
|
+
}
|
|
301
|
+
function ensureExternalSyncSubscriptions() {
|
|
302
|
+
if (!hasWindowStorageEventSubscription && typeof window !== "undefined" && typeof window.addEventListener === "function") {
|
|
303
|
+
window.addEventListener("storage", handleWebStorageEvent);
|
|
304
|
+
hasWindowStorageEventSubscription = true;
|
|
287
305
|
}
|
|
306
|
+
subscribeToBackendChanges(_Storage.StorageScope.Disk);
|
|
307
|
+
subscribeToBackendChanges(_Storage.StorageScope.Secure);
|
|
288
308
|
}
|
|
289
309
|
function getScopedListeners(scope) {
|
|
290
310
|
return webScopeListeners.get(scope);
|
|
@@ -340,12 +360,49 @@ function addKeyListener(registry, key, listener) {
|
|
|
340
360
|
function readPendingSecureWrite(key) {
|
|
341
361
|
return pendingSecureWrites.get(key)?.value;
|
|
342
362
|
}
|
|
363
|
+
function readPendingDiskWrite(key) {
|
|
364
|
+
return pendingDiskWrites.get(key)?.value;
|
|
365
|
+
}
|
|
366
|
+
function hasPendingDiskWrite(key) {
|
|
367
|
+
return pendingDiskWrites.has(key);
|
|
368
|
+
}
|
|
343
369
|
function hasPendingSecureWrite(key) {
|
|
344
370
|
return pendingSecureWrites.has(key);
|
|
345
371
|
}
|
|
372
|
+
function clearPendingDiskWrite(key) {
|
|
373
|
+
pendingDiskWrites.delete(key);
|
|
374
|
+
}
|
|
346
375
|
function clearPendingSecureWrite(key) {
|
|
347
376
|
pendingSecureWrites.delete(key);
|
|
348
377
|
}
|
|
378
|
+
function flushDiskWrites() {
|
|
379
|
+
diskFlushScheduled = false;
|
|
380
|
+
if (pendingDiskWrites.size === 0) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const writes = Array.from(pendingDiskWrites.values());
|
|
384
|
+
pendingDiskWrites.clear();
|
|
385
|
+
const keysToSet = [];
|
|
386
|
+
const valuesToSet = [];
|
|
387
|
+
const keysToRemove = [];
|
|
388
|
+
writes.forEach(({
|
|
389
|
+
key,
|
|
390
|
+
value
|
|
391
|
+
}) => {
|
|
392
|
+
if (value === undefined) {
|
|
393
|
+
keysToRemove.push(key);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
keysToSet.push(key);
|
|
397
|
+
valuesToSet.push(value);
|
|
398
|
+
});
|
|
399
|
+
if (keysToSet.length > 0) {
|
|
400
|
+
WebStorage.setBatch(keysToSet, valuesToSet, _Storage.StorageScope.Disk);
|
|
401
|
+
}
|
|
402
|
+
if (keysToRemove.length > 0) {
|
|
403
|
+
WebStorage.removeBatch(keysToRemove, _Storage.StorageScope.Disk);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
349
406
|
function flushSecureWrites() {
|
|
350
407
|
secureFlushScheduled = false;
|
|
351
408
|
if (pendingSecureWrites.size === 0) {
|
|
@@ -384,6 +441,17 @@ function flushSecureWrites() {
|
|
|
384
441
|
WebStorage.removeBatch(keysToRemove, _Storage.StorageScope.Secure);
|
|
385
442
|
}
|
|
386
443
|
}
|
|
444
|
+
function scheduleDiskWrite(key, value) {
|
|
445
|
+
pendingDiskWrites.set(key, {
|
|
446
|
+
key,
|
|
447
|
+
value
|
|
448
|
+
});
|
|
449
|
+
if (diskFlushScheduled) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
diskFlushScheduled = true;
|
|
453
|
+
runMicrotask(flushDiskWrites);
|
|
454
|
+
}
|
|
387
455
|
function scheduleSecureWrite(key, value, accessControl) {
|
|
388
456
|
const pendingWrite = {
|
|
389
457
|
key,
|
|
@@ -404,117 +472,124 @@ const WebStorage = {
|
|
|
404
472
|
equals: other => other === WebStorage,
|
|
405
473
|
dispose: () => {},
|
|
406
474
|
set: (key, value, scope) => {
|
|
407
|
-
|
|
408
|
-
if (!storage) {
|
|
475
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
409
476
|
return;
|
|
410
477
|
}
|
|
411
478
|
const storageKey = scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key;
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
479
|
+
withWebBackendOperation(scope, "set", backend => {
|
|
480
|
+
backend.setItem(storageKey, value);
|
|
481
|
+
});
|
|
482
|
+
ensureWebScopeKeyIndex(scope).add(key);
|
|
483
|
+
notifyKeyListeners(getScopedListeners(scope), key);
|
|
417
484
|
},
|
|
418
485
|
get: (key, scope) => {
|
|
419
|
-
|
|
486
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
487
|
+
return undefined;
|
|
488
|
+
}
|
|
420
489
|
const storageKey = scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key;
|
|
421
|
-
|
|
490
|
+
const value = withWebBackendOperation(scope, "get", backend => backend.getItem(storageKey));
|
|
491
|
+
return value ?? undefined;
|
|
422
492
|
},
|
|
423
493
|
remove: (key, scope) => {
|
|
424
|
-
|
|
425
|
-
if (!storage) {
|
|
494
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
426
495
|
return;
|
|
427
496
|
}
|
|
428
497
|
if (scope === _Storage.StorageScope.Secure) {
|
|
429
|
-
|
|
430
|
-
|
|
498
|
+
withWebBackendOperation(scope, "remove", backend => {
|
|
499
|
+
if (backend.removeMany) {
|
|
500
|
+
backend.removeMany([toSecureStorageKey(key), toBiometricStorageKey(key)]);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
backend.removeItem(toSecureStorageKey(key));
|
|
504
|
+
backend.removeItem(toBiometricStorageKey(key));
|
|
505
|
+
});
|
|
431
506
|
} else {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
ensureWebScopeKeyIndex(scope).delete(key);
|
|
436
|
-
notifyKeyListeners(getScopedListeners(scope), key);
|
|
507
|
+
withWebBackendOperation(scope, "remove", backend => {
|
|
508
|
+
backend.removeItem(key);
|
|
509
|
+
});
|
|
437
510
|
}
|
|
511
|
+
ensureWebScopeKeyIndex(scope).delete(key);
|
|
512
|
+
notifyKeyListeners(getScopedListeners(scope), key);
|
|
438
513
|
},
|
|
439
514
|
clear: scope => {
|
|
440
|
-
|
|
441
|
-
if (!storage) {
|
|
515
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
442
516
|
return;
|
|
443
517
|
}
|
|
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
|
-
}
|
|
518
|
+
withWebBackendOperation(scope, "clear", backend => {
|
|
519
|
+
backend.clear();
|
|
520
|
+
});
|
|
521
|
+
ensureWebScopeKeyIndex(scope).clear();
|
|
522
|
+
notifyAllListeners(getScopedListeners(scope));
|
|
469
523
|
},
|
|
470
524
|
setBatch: (keys, values, scope) => {
|
|
471
|
-
|
|
472
|
-
if (!storage) {
|
|
525
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
473
526
|
return;
|
|
474
527
|
}
|
|
528
|
+
const entries = [];
|
|
475
529
|
keys.forEach((key, index) => {
|
|
476
530
|
const value = values[index];
|
|
477
531
|
if (value === undefined) {
|
|
478
532
|
return;
|
|
479
533
|
}
|
|
480
|
-
|
|
481
|
-
storage.setItem(storageKey, value);
|
|
534
|
+
entries.push([scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key, value]);
|
|
482
535
|
});
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
536
|
+
withWebBackendOperation(scope, "setBatch", backend => {
|
|
537
|
+
if (backend.setMany) {
|
|
538
|
+
backend.setMany(entries);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
entries.forEach(([storageKey, value]) => {
|
|
542
|
+
backend.setItem(storageKey, value);
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
546
|
+
keys.forEach(key => keyIndex.add(key));
|
|
547
|
+
const listeners = getScopedListeners(scope);
|
|
548
|
+
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
489
549
|
},
|
|
490
550
|
getBatch: (keys, scope) => {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
551
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
552
|
+
return keys.map(() => undefined);
|
|
553
|
+
}
|
|
554
|
+
const storageKeys = keys.map(key => scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key);
|
|
555
|
+
const values = withWebBackendOperation(scope, "getBatch", backend => {
|
|
556
|
+
if (backend.getMany) {
|
|
557
|
+
return backend.getMany(storageKeys);
|
|
558
|
+
}
|
|
559
|
+
return storageKeys.map(storageKey => backend.getItem(storageKey));
|
|
495
560
|
});
|
|
561
|
+
return values.map(value => value ?? undefined);
|
|
496
562
|
},
|
|
497
563
|
removeBatch: (keys, scope) => {
|
|
498
|
-
|
|
499
|
-
if (!storage) {
|
|
564
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
500
565
|
return;
|
|
501
566
|
}
|
|
502
567
|
if (scope === _Storage.StorageScope.Secure) {
|
|
503
|
-
keys.
|
|
504
|
-
|
|
505
|
-
|
|
568
|
+
const storageKeys = keys.flatMap(key => [toSecureStorageKey(key), toBiometricStorageKey(key)]);
|
|
569
|
+
withWebBackendOperation(scope, "removeBatch", backend => {
|
|
570
|
+
if (backend.removeMany) {
|
|
571
|
+
backend.removeMany(storageKeys);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
storageKeys.forEach(storageKey => {
|
|
575
|
+
backend.removeItem(storageKey);
|
|
576
|
+
});
|
|
506
577
|
});
|
|
507
578
|
} else {
|
|
508
|
-
|
|
509
|
-
|
|
579
|
+
withWebBackendOperation(scope, "removeBatch", backend => {
|
|
580
|
+
if (backend.removeMany) {
|
|
581
|
+
backend.removeMany(keys);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
keys.forEach(key => {
|
|
585
|
+
backend.removeItem(key);
|
|
586
|
+
});
|
|
510
587
|
});
|
|
511
588
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
517
|
-
}
|
|
589
|
+
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
590
|
+
keys.forEach(key => keyIndex.delete(key));
|
|
591
|
+
const listeners = getScopedListeners(scope);
|
|
592
|
+
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
518
593
|
},
|
|
519
594
|
removeByPrefix: (prefix, scope) => {
|
|
520
595
|
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
@@ -531,11 +606,13 @@ const WebStorage = {
|
|
|
531
606
|
return () => {};
|
|
532
607
|
},
|
|
533
608
|
has: (key, scope) => {
|
|
534
|
-
const storage = getBrowserStorage(scope);
|
|
535
609
|
if (scope === _Storage.StorageScope.Secure) {
|
|
536
|
-
return
|
|
610
|
+
return withWebBackendOperation(scope, "has", backend => backend.getItem(toSecureStorageKey(key))) !== null || withWebBackendOperation(scope, "has", backend => backend.getItem(toBiometricStorageKey(key))) !== null;
|
|
611
|
+
}
|
|
612
|
+
if (scope !== _Storage.StorageScope.Disk) {
|
|
613
|
+
return false;
|
|
537
614
|
}
|
|
538
|
-
return
|
|
615
|
+
return withWebBackendOperation(scope, "has", backend => backend.getItem(key)) !== null;
|
|
539
616
|
},
|
|
540
617
|
getAllKeys: scope => {
|
|
541
618
|
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
@@ -566,40 +643,43 @@ const WebStorage = {
|
|
|
566
643
|
hasWarnedAboutWebBiometricFallback = true;
|
|
567
644
|
console.warn("[NitroStorage] Biometric storage is not supported on web. Using localStorage.");
|
|
568
645
|
}
|
|
569
|
-
|
|
646
|
+
withWebBackendOperation(_Storage.StorageScope.Secure, "setSecureBiometric", backend => backend.setItem(toBiometricStorageKey(key), value));
|
|
570
647
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(key);
|
|
571
648
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), key);
|
|
572
649
|
},
|
|
573
650
|
getSecureBiometric: key => {
|
|
574
|
-
|
|
651
|
+
const value = withWebBackendOperation(_Storage.StorageScope.Secure, "getSecureBiometric", backend => backend.getItem(toBiometricStorageKey(key)));
|
|
652
|
+
return value ?? undefined;
|
|
575
653
|
},
|
|
576
654
|
deleteSecureBiometric: key => {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
if (storage?.getItem(toSecureStorageKey(key)) === null) {
|
|
655
|
+
withWebBackendOperation(_Storage.StorageScope.Secure, "deleteSecureBiometric", backend => backend.removeItem(toBiometricStorageKey(key)));
|
|
656
|
+
if (withWebBackendOperation(_Storage.StorageScope.Secure, "deleteSecureBiometric:getItem", backend => backend.getItem(toSecureStorageKey(key))) === null) {
|
|
580
657
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(key);
|
|
581
658
|
}
|
|
582
659
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), key);
|
|
583
660
|
},
|
|
584
661
|
hasSecureBiometric: key => {
|
|
585
|
-
return
|
|
662
|
+
return withWebBackendOperation(_Storage.StorageScope.Secure, "hasSecureBiometric", backend => backend.getItem(toBiometricStorageKey(key))) !== null;
|
|
586
663
|
},
|
|
587
664
|
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
|
-
}
|
|
665
|
+
const storageKeys = withWebBackendOperation(_Storage.StorageScope.Secure, "clearSecureBiometric:getAllKeys", backend => backend.getAllKeys());
|
|
666
|
+
const keysToNotify = storageKeys.filter(key => key.startsWith(BIOMETRIC_WEB_PREFIX)).map(key => fromBiometricStorageKey(key));
|
|
667
|
+
if (keysToNotify.length === 0) {
|
|
668
|
+
return;
|
|
598
669
|
}
|
|
599
|
-
|
|
670
|
+
withWebBackendOperation(_Storage.StorageScope.Secure, "clearSecureBiometric", backend => {
|
|
671
|
+
const biometricKeys = keysToNotify.map(key => toBiometricStorageKey(key));
|
|
672
|
+
if (backend.removeMany) {
|
|
673
|
+
backend.removeMany(biometricKeys);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
biometricKeys.forEach(storageKey => {
|
|
677
|
+
backend.removeItem(storageKey);
|
|
678
|
+
});
|
|
679
|
+
});
|
|
600
680
|
const keyIndex = ensureWebScopeKeyIndex(_Storage.StorageScope.Secure);
|
|
601
681
|
keysToNotify.forEach(key => {
|
|
602
|
-
if (
|
|
682
|
+
if (withWebBackendOperation(_Storage.StorageScope.Secure, "clearSecureBiometric:getItem", backend => backend.getItem(toSecureStorageKey(key))) === null) {
|
|
603
683
|
keyIndex.delete(key);
|
|
604
684
|
}
|
|
605
685
|
});
|
|
@@ -613,6 +693,9 @@ function getRawValue(key, scope) {
|
|
|
613
693
|
const value = memoryStore.get(key);
|
|
614
694
|
return typeof value === "string" ? value : undefined;
|
|
615
695
|
}
|
|
696
|
+
if (scope === _Storage.StorageScope.Disk && hasPendingDiskWrite(key)) {
|
|
697
|
+
return readPendingDiskWrite(key);
|
|
698
|
+
}
|
|
616
699
|
if (scope === _Storage.StorageScope.Secure && hasPendingSecureWrite(key)) {
|
|
617
700
|
return readPendingSecureWrite(key);
|
|
618
701
|
}
|
|
@@ -625,6 +708,15 @@ function setRawValue(key, value, scope) {
|
|
|
625
708
|
notifyKeyListeners(memoryListeners, key);
|
|
626
709
|
return;
|
|
627
710
|
}
|
|
711
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
712
|
+
cacheRawValue(scope, key, value);
|
|
713
|
+
if (diskWritesAsync) {
|
|
714
|
+
scheduleDiskWrite(key, value);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
flushDiskWrites();
|
|
718
|
+
clearPendingDiskWrite(key);
|
|
719
|
+
}
|
|
628
720
|
if (scope === _Storage.StorageScope.Secure) {
|
|
629
721
|
flushSecureWrites();
|
|
630
722
|
clearPendingSecureWrite(key);
|
|
@@ -639,6 +731,15 @@ function removeRawValue(key, scope) {
|
|
|
639
731
|
notifyKeyListeners(memoryListeners, key);
|
|
640
732
|
return;
|
|
641
733
|
}
|
|
734
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
735
|
+
cacheRawValue(scope, key, undefined);
|
|
736
|
+
if (diskWritesAsync) {
|
|
737
|
+
scheduleDiskWrite(key, undefined);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
flushDiskWrites();
|
|
741
|
+
clearPendingDiskWrite(key);
|
|
742
|
+
}
|
|
642
743
|
if (scope === _Storage.StorageScope.Secure) {
|
|
643
744
|
flushSecureWrites();
|
|
644
745
|
clearPendingSecureWrite(key);
|
|
@@ -665,6 +766,10 @@ const storage = exports.storage = {
|
|
|
665
766
|
notifyAllListeners(memoryListeners);
|
|
666
767
|
return;
|
|
667
768
|
}
|
|
769
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
770
|
+
flushDiskWrites();
|
|
771
|
+
pendingDiskWrites.clear();
|
|
772
|
+
}
|
|
668
773
|
if (scope === _Storage.StorageScope.Secure) {
|
|
669
774
|
flushSecureWrites();
|
|
670
775
|
pendingSecureWrites.clear();
|
|
@@ -693,6 +798,9 @@ const storage = exports.storage = {
|
|
|
693
798
|
return;
|
|
694
799
|
}
|
|
695
800
|
const keyPrefix = (0, _internal.prefixKey)(namespace, "");
|
|
801
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
802
|
+
flushDiskWrites();
|
|
803
|
+
}
|
|
696
804
|
if (scope === _Storage.StorageScope.Secure) {
|
|
697
805
|
flushSecureWrites();
|
|
698
806
|
}
|
|
@@ -714,6 +822,12 @@ const storage = exports.storage = {
|
|
|
714
822
|
return measureOperation("storage:has", scope, () => {
|
|
715
823
|
(0, _internal.assertValidScope)(scope);
|
|
716
824
|
if (scope === _Storage.StorageScope.Memory) return memoryStore.has(key);
|
|
825
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
826
|
+
flushDiskWrites();
|
|
827
|
+
}
|
|
828
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
829
|
+
flushSecureWrites();
|
|
830
|
+
}
|
|
717
831
|
return WebStorage.has(key, scope);
|
|
718
832
|
});
|
|
719
833
|
},
|
|
@@ -721,6 +835,12 @@ const storage = exports.storage = {
|
|
|
721
835
|
return measureOperation("storage:getAllKeys", scope, () => {
|
|
722
836
|
(0, _internal.assertValidScope)(scope);
|
|
723
837
|
if (scope === _Storage.StorageScope.Memory) return Array.from(memoryStore.keys());
|
|
838
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
839
|
+
flushDiskWrites();
|
|
840
|
+
}
|
|
841
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
842
|
+
flushSecureWrites();
|
|
843
|
+
}
|
|
724
844
|
return WebStorage.getAllKeys(scope);
|
|
725
845
|
});
|
|
726
846
|
},
|
|
@@ -730,6 +850,12 @@ const storage = exports.storage = {
|
|
|
730
850
|
if (scope === _Storage.StorageScope.Memory) {
|
|
731
851
|
return Array.from(memoryStore.keys()).filter(key => key.startsWith(prefix));
|
|
732
852
|
}
|
|
853
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
854
|
+
flushDiskWrites();
|
|
855
|
+
}
|
|
856
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
857
|
+
flushSecureWrites();
|
|
858
|
+
}
|
|
733
859
|
return WebStorage.getKeysByPrefix(prefix, scope);
|
|
734
860
|
});
|
|
735
861
|
},
|
|
@@ -749,6 +875,12 @@ const storage = exports.storage = {
|
|
|
749
875
|
});
|
|
750
876
|
return result;
|
|
751
877
|
}
|
|
878
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
879
|
+
flushDiskWrites();
|
|
880
|
+
}
|
|
881
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
882
|
+
flushSecureWrites();
|
|
883
|
+
}
|
|
752
884
|
const values = WebStorage.getBatch(keys, scope);
|
|
753
885
|
keys.forEach((key, index) => {
|
|
754
886
|
const value = values[index];
|
|
@@ -769,6 +901,12 @@ const storage = exports.storage = {
|
|
|
769
901
|
});
|
|
770
902
|
return result;
|
|
771
903
|
}
|
|
904
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
905
|
+
flushDiskWrites();
|
|
906
|
+
}
|
|
907
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
908
|
+
flushSecureWrites();
|
|
909
|
+
}
|
|
772
910
|
const keys = WebStorage.getAllKeys(scope);
|
|
773
911
|
if (keys.length === 0) return {};
|
|
774
912
|
const values = WebStorage.getBatch(keys, scope);
|
|
@@ -785,6 +923,12 @@ const storage = exports.storage = {
|
|
|
785
923
|
return measureOperation("storage:size", scope, () => {
|
|
786
924
|
(0, _internal.assertValidScope)(scope);
|
|
787
925
|
if (scope === _Storage.StorageScope.Memory) return memoryStore.size;
|
|
926
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
927
|
+
flushDiskWrites();
|
|
928
|
+
}
|
|
929
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
930
|
+
flushSecureWrites();
|
|
931
|
+
}
|
|
788
932
|
return WebStorage.size(scope);
|
|
789
933
|
});
|
|
790
934
|
},
|
|
@@ -795,6 +939,19 @@ const storage = exports.storage = {
|
|
|
795
939
|
setSecureWritesAsync: _enabled => {
|
|
796
940
|
recordMetric("storage:setSecureWritesAsync", _Storage.StorageScope.Secure, 0);
|
|
797
941
|
},
|
|
942
|
+
setDiskWritesAsync: enabled => {
|
|
943
|
+
measureOperation("storage:setDiskWritesAsync", _Storage.StorageScope.Disk, () => {
|
|
944
|
+
diskWritesAsync = enabled;
|
|
945
|
+
if (!enabled) {
|
|
946
|
+
flushDiskWrites();
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
},
|
|
950
|
+
flushDiskWrites: () => {
|
|
951
|
+
measureOperation("storage:flushDiskWrites", _Storage.StorageScope.Disk, () => {
|
|
952
|
+
flushDiskWrites();
|
|
953
|
+
});
|
|
954
|
+
},
|
|
798
955
|
flushSecureWrites: () => {
|
|
799
956
|
measureOperation("storage:flushSecureWrites", _Storage.StorageScope.Secure, () => {
|
|
800
957
|
flushSecureWrites();
|
|
@@ -821,6 +978,69 @@ const storage = exports.storage = {
|
|
|
821
978
|
resetMetrics: () => {
|
|
822
979
|
metricsCounters.clear();
|
|
823
980
|
},
|
|
981
|
+
getCapabilities: () => ({
|
|
982
|
+
platform: "web",
|
|
983
|
+
backend: {
|
|
984
|
+
disk: getBackendName(_Storage.StorageScope.Disk, webDiskStorageBackend),
|
|
985
|
+
secure: getBackendName(_Storage.StorageScope.Secure, webSecureStorageBackend)
|
|
986
|
+
},
|
|
987
|
+
writeBuffering: {
|
|
988
|
+
disk: true,
|
|
989
|
+
secure: true
|
|
990
|
+
},
|
|
991
|
+
errorClassification: true
|
|
992
|
+
}),
|
|
993
|
+
getSecurityCapabilities: () => {
|
|
994
|
+
const secureBackend = getBackendName(_Storage.StorageScope.Secure, webSecureStorageBackend);
|
|
995
|
+
return {
|
|
996
|
+
platform: "web",
|
|
997
|
+
secureStorage: {
|
|
998
|
+
backend: secureBackend,
|
|
999
|
+
encrypted: getWebSecureEncryptionStatus(webSecureStorageBackend),
|
|
1000
|
+
accessControl: "unavailable",
|
|
1001
|
+
keychainAccessGroup: "unavailable",
|
|
1002
|
+
hardwareBacked: "unavailable"
|
|
1003
|
+
},
|
|
1004
|
+
biometric: {
|
|
1005
|
+
storage: "unavailable",
|
|
1006
|
+
prompt: "unavailable",
|
|
1007
|
+
biometryOnly: "unavailable",
|
|
1008
|
+
biometryOrPasscode: "unavailable"
|
|
1009
|
+
},
|
|
1010
|
+
metadata: {
|
|
1011
|
+
perKey: true,
|
|
1012
|
+
listsWithoutValues: true,
|
|
1013
|
+
persistsTimestamps: false
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
},
|
|
1017
|
+
getSecureMetadata: key => {
|
|
1018
|
+
return measureOperation("storage:getSecureMetadata", _Storage.StorageScope.Secure, () => {
|
|
1019
|
+
flushSecureWrites();
|
|
1020
|
+
const biometricProtected = WebStorage.hasSecureBiometric(key);
|
|
1021
|
+
const exists = biometricProtected || WebStorage.has(key, _Storage.StorageScope.Secure);
|
|
1022
|
+
let kind = "missing";
|
|
1023
|
+
if (exists) {
|
|
1024
|
+
kind = biometricProtected ? "biometric" : "secure";
|
|
1025
|
+
}
|
|
1026
|
+
return {
|
|
1027
|
+
key,
|
|
1028
|
+
exists,
|
|
1029
|
+
kind,
|
|
1030
|
+
backend: getBackendName(_Storage.StorageScope.Secure, webSecureStorageBackend),
|
|
1031
|
+
encrypted: getWebSecureEncryptionStatus(webSecureStorageBackend),
|
|
1032
|
+
hardwareBacked: "unavailable",
|
|
1033
|
+
biometricProtected,
|
|
1034
|
+
valueExposed: false
|
|
1035
|
+
};
|
|
1036
|
+
});
|
|
1037
|
+
},
|
|
1038
|
+
getAllSecureMetadata: () => {
|
|
1039
|
+
return measureOperation("storage:getAllSecureMetadata", _Storage.StorageScope.Secure, () => {
|
|
1040
|
+
flushSecureWrites();
|
|
1041
|
+
return WebStorage.getAllKeys(_Storage.StorageScope.Secure).map(key => storage.getSecureMetadata(key));
|
|
1042
|
+
});
|
|
1043
|
+
},
|
|
824
1044
|
getString: (key, scope) => {
|
|
825
1045
|
return measureOperation("storage:getString", scope, () => {
|
|
826
1046
|
return getRawValue(key, scope);
|
|
@@ -853,21 +1073,50 @@ const storage = exports.storage = {
|
|
|
853
1073
|
flushSecureWrites();
|
|
854
1074
|
WebStorage.setSecureAccessControl(secureDefaultAccessControl);
|
|
855
1075
|
}
|
|
1076
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
1077
|
+
flushDiskWrites();
|
|
1078
|
+
}
|
|
856
1079
|
WebStorage.setBatch(keys, values, scope);
|
|
857
1080
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
858
1081
|
}, keys.length);
|
|
859
1082
|
}
|
|
860
1083
|
};
|
|
861
1084
|
function setWebSecureStorageBackend(backend) {
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1085
|
+
pendingSecureWrites.clear();
|
|
1086
|
+
webSecureStorageBackend = backend ?? createDefaultSecureBackend();
|
|
1087
|
+
resetBackendChangeSubscription(_Storage.StorageScope.Secure);
|
|
865
1088
|
hydratedWebScopeKeyIndex.delete(_Storage.StorageScope.Secure);
|
|
866
1089
|
clearScopeRawCache(_Storage.StorageScope.Secure);
|
|
1090
|
+
ensureExternalSyncSubscriptions();
|
|
867
1091
|
}
|
|
868
1092
|
function getWebSecureStorageBackend() {
|
|
869
1093
|
return webSecureStorageBackend;
|
|
870
1094
|
}
|
|
1095
|
+
function setWebDiskStorageBackend(backend) {
|
|
1096
|
+
pendingDiskWrites.clear();
|
|
1097
|
+
webDiskStorageBackend = backend ?? createDefaultDiskBackend();
|
|
1098
|
+
resetBackendChangeSubscription(_Storage.StorageScope.Disk);
|
|
1099
|
+
hydratedWebScopeKeyIndex.delete(_Storage.StorageScope.Disk);
|
|
1100
|
+
clearScopeRawCache(_Storage.StorageScope.Disk);
|
|
1101
|
+
ensureExternalSyncSubscriptions();
|
|
1102
|
+
}
|
|
1103
|
+
function getWebDiskStorageBackend() {
|
|
1104
|
+
return webDiskStorageBackend;
|
|
1105
|
+
}
|
|
1106
|
+
async function flushWebStorageBackends() {
|
|
1107
|
+
flushDiskWrites();
|
|
1108
|
+
flushSecureWrites();
|
|
1109
|
+
const flushes = [];
|
|
1110
|
+
const diskFlush = webDiskStorageBackend?.flush;
|
|
1111
|
+
const secureFlush = webSecureStorageBackend?.flush;
|
|
1112
|
+
if (diskFlush) {
|
|
1113
|
+
flushes.push(diskFlush());
|
|
1114
|
+
}
|
|
1115
|
+
if (secureFlush) {
|
|
1116
|
+
flushes.push(secureFlush());
|
|
1117
|
+
}
|
|
1118
|
+
await Promise.all(flushes);
|
|
1119
|
+
}
|
|
871
1120
|
function canUseRawBatchPath(item) {
|
|
872
1121
|
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
|
|
873
1122
|
}
|
|
@@ -895,6 +1144,7 @@ function createStorageItem(config) {
|
|
|
895
1144
|
const expirationTtlMs = expiration?.ttlMs;
|
|
896
1145
|
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
897
1146
|
const readCache = !isMemory && config.readCache === true;
|
|
1147
|
+
const coalesceDiskWrites = config.scope === _Storage.StorageScope.Disk && config.coalesceDiskWrites === true;
|
|
898
1148
|
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
|
|
899
1149
|
const defaultValue = config.defaultValue;
|
|
900
1150
|
const nonMemoryScope = config.scope === _Storage.StorageScope.Disk ? _Storage.StorageScope.Disk : config.scope === _Storage.StorageScope.Secure ? _Storage.StorageScope.Secure : null;
|
|
@@ -925,7 +1175,7 @@ function createStorageItem(config) {
|
|
|
925
1175
|
unsubscribe = addKeyListener(memoryListeners, storageKey, listener);
|
|
926
1176
|
return;
|
|
927
1177
|
}
|
|
928
|
-
|
|
1178
|
+
ensureExternalSyncSubscriptions();
|
|
929
1179
|
unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), storageKey, listener);
|
|
930
1180
|
};
|
|
931
1181
|
const readStoredRaw = () => {
|
|
@@ -942,6 +1192,12 @@ function createStorageItem(config) {
|
|
|
942
1192
|
}
|
|
943
1193
|
return memoryStore.get(storageKey);
|
|
944
1194
|
}
|
|
1195
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1196
|
+
const pending = pendingDiskWrites.get(storageKey);
|
|
1197
|
+
if (pending !== undefined) {
|
|
1198
|
+
return pending.value;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
945
1201
|
if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
|
|
946
1202
|
const pending = pendingSecureWrites.get(storageKey);
|
|
947
1203
|
if (pending !== undefined) {
|
|
@@ -968,6 +1224,13 @@ function createStorageItem(config) {
|
|
|
968
1224
|
return;
|
|
969
1225
|
}
|
|
970
1226
|
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
1227
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1228
|
+
if (coalesceDiskWrites || diskWritesAsync) {
|
|
1229
|
+
scheduleDiskWrite(storageKey, rawValue);
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
clearPendingDiskWrite(storageKey);
|
|
1233
|
+
}
|
|
971
1234
|
if (coalesceSecureWrites) {
|
|
972
1235
|
scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
|
|
973
1236
|
return;
|
|
@@ -983,6 +1246,13 @@ function createStorageItem(config) {
|
|
|
983
1246
|
return;
|
|
984
1247
|
}
|
|
985
1248
|
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
1249
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1250
|
+
if (coalesceDiskWrites || diskWritesAsync) {
|
|
1251
|
+
scheduleDiskWrite(storageKey, undefined);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
clearPendingDiskWrite(storageKey);
|
|
1255
|
+
}
|
|
986
1256
|
if (coalesceSecureWrites) {
|
|
987
1257
|
scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
|
|
988
1258
|
return;
|
|
@@ -1143,6 +1413,18 @@ function createStorageItem(config) {
|
|
|
1143
1413
|
const hasItem = () => measureOperation("item:has", config.scope, () => {
|
|
1144
1414
|
if (isMemory) return memoryStore.has(storageKey);
|
|
1145
1415
|
if (isBiometric) return WebStorage.hasSecureBiometric(storageKey);
|
|
1416
|
+
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1417
|
+
const pending = pendingDiskWrites.get(storageKey);
|
|
1418
|
+
if (pending !== undefined) {
|
|
1419
|
+
return pending.value !== undefined;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
1423
|
+
const pending = pendingSecureWrites.get(storageKey);
|
|
1424
|
+
if (pending !== undefined) {
|
|
1425
|
+
return pending.value !== undefined;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1146
1428
|
return WebStorage.has(storageKey, config.scope);
|
|
1147
1429
|
});
|
|
1148
1430
|
const subscribe = callback => {
|
|
@@ -1153,9 +1435,6 @@ function createStorageItem(config) {
|
|
|
1153
1435
|
if (listeners.size === 0 && unsubscribe) {
|
|
1154
1436
|
unsubscribe();
|
|
1155
1437
|
unsubscribe = null;
|
|
1156
|
-
if (!isMemory) {
|
|
1157
|
-
maybeCleanupWebStorageSubscription();
|
|
1158
|
-
}
|
|
1159
1438
|
}
|
|
1160
1439
|
};
|
|
1161
1440
|
};
|
|
@@ -1203,6 +1482,13 @@ function getBatch(items, scope) {
|
|
|
1203
1482
|
const keysToFetch = [];
|
|
1204
1483
|
const keyIndexes = [];
|
|
1205
1484
|
items.forEach((item, index) => {
|
|
1485
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
1486
|
+
const pending = pendingDiskWrites.get(item.key);
|
|
1487
|
+
if (pending !== undefined) {
|
|
1488
|
+
rawValues[index] = pending.value;
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1206
1492
|
if (scope === _Storage.StorageScope.Secure) {
|
|
1207
1493
|
const pending = pendingSecureWrites.get(item.key);
|
|
1208
1494
|
if (pending !== undefined) {
|
|
@@ -1319,6 +1605,7 @@ function setBatch(items, scope) {
|
|
|
1319
1605
|
});
|
|
1320
1606
|
return;
|
|
1321
1607
|
}
|
|
1608
|
+
flushDiskWrites();
|
|
1322
1609
|
const useRawBatchPath = items.every(({
|
|
1323
1610
|
item
|
|
1324
1611
|
}) => canUseRawBatchPath(asInternal(item)));
|
|
@@ -1343,6 +1630,9 @@ function removeBatch(items, scope) {
|
|
|
1343
1630
|
return;
|
|
1344
1631
|
}
|
|
1345
1632
|
const keys = items.map(item => item.key);
|
|
1633
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
1634
|
+
flushDiskWrites();
|
|
1635
|
+
}
|
|
1346
1636
|
if (scope === _Storage.StorageScope.Secure) {
|
|
1347
1637
|
flushSecureWrites();
|
|
1348
1638
|
}
|
|
@@ -1388,6 +1678,9 @@ function migrateToLatest(scope = _Storage.StorageScope.Disk) {
|
|
|
1388
1678
|
function runTransaction(scope, transaction) {
|
|
1389
1679
|
return measureOperation("transaction:run", scope, () => {
|
|
1390
1680
|
(0, _internal.assertValidScope)(scope);
|
|
1681
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
1682
|
+
flushDiskWrites();
|
|
1683
|
+
}
|
|
1391
1684
|
if (scope === _Storage.StorageScope.Secure) {
|
|
1392
1685
|
flushSecureWrites();
|
|
1393
1686
|
}
|
|
@@ -1454,6 +1747,9 @@ function runTransaction(scope, transaction) {
|
|
|
1454
1747
|
valuesToSet.push(previousValue);
|
|
1455
1748
|
}
|
|
1456
1749
|
});
|
|
1750
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
1751
|
+
flushDiskWrites();
|
|
1752
|
+
}
|
|
1457
1753
|
if (scope === _Storage.StorageScope.Secure) {
|
|
1458
1754
|
flushSecureWrites();
|
|
1459
1755
|
}
|
|
@@ -1499,7 +1795,7 @@ function createSecureAuthStorage(config, options) {
|
|
|
1499
1795
|
}
|
|
1500
1796
|
return result;
|
|
1501
1797
|
}
|
|
1502
|
-
function isKeychainLockedError(
|
|
1503
|
-
return
|
|
1798
|
+
function isKeychainLockedError(err) {
|
|
1799
|
+
return (0, _storageRuntime.isLockedStorageErrorCode)((0, _storageRuntime.getStorageErrorCode)(err));
|
|
1504
1800
|
}
|
|
1505
1801
|
//# sourceMappingURL=index.web.js.map
|