react-native-nitro-storage 0.1.4 → 0.3.1
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 +432 -345
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +191 -3
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +21 -41
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +181 -29
- package/android/src/main/java/com/nitrostorage/NitroStoragePackage.kt +2 -2
- package/app.plugin.js +9 -7
- package/cpp/bindings/HybridStorage.cpp +239 -10
- package/cpp/bindings/HybridStorage.hpp +10 -0
- package/cpp/core/NativeStorageAdapter.hpp +22 -0
- package/ios/IOSStorageAdapterCpp.hpp +25 -0
- package/ios/IOSStorageAdapterCpp.mm +315 -33
- package/lib/commonjs/Storage.types.js +23 -1
- package/lib/commonjs/Storage.types.js.map +1 -1
- package/lib/commonjs/index.js +680 -68
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +801 -133
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/internal.js +112 -0
- package/lib/commonjs/internal.js.map +1 -0
- package/lib/module/Storage.types.js +22 -0
- package/lib/module/Storage.types.js.map +1 -1
- package/lib/module/index.js +660 -71
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +766 -125
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/internal.js +100 -0
- package/lib/module/internal.js.map +1 -0
- package/lib/typescript/Storage.nitro.d.ts +10 -0
- package/lib/typescript/Storage.nitro.d.ts.map +1 -1
- package/lib/typescript/Storage.types.d.ts +20 -0
- package/lib/typescript/Storage.types.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +68 -9
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +79 -13
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts +21 -0
- package/lib/typescript/internal.d.ts.map +1 -0
- package/lib/typescript/migration.d.ts +2 -3
- package/lib/typescript/migration.d.ts.map +1 -1
- package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +10 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +10 -0
- package/package.json +22 -8
- package/src/Storage.nitro.ts +11 -2
- package/src/Storage.types.ts +22 -0
- package/src/index.ts +943 -84
- package/src/index.web.ts +1082 -137
- package/src/internal.ts +144 -0
- package/src/migration.ts +3 -3
package/lib/commonjs/index.js
CHANGED
|
@@ -3,22 +3,54 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
Object.defineProperty(exports, "AccessControl", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _Storage.AccessControl;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "BiometricLevel", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _Storage.BiometricLevel;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
6
18
|
Object.defineProperty(exports, "StorageScope", {
|
|
7
19
|
enumerable: true,
|
|
8
20
|
get: function () {
|
|
9
21
|
return _Storage.StorageScope;
|
|
10
22
|
}
|
|
11
23
|
});
|
|
24
|
+
exports.createSecureAuthStorage = createSecureAuthStorage;
|
|
12
25
|
exports.createStorageItem = createStorageItem;
|
|
13
26
|
exports.getBatch = getBatch;
|
|
27
|
+
Object.defineProperty(exports, "migrateFromMMKV", {
|
|
28
|
+
enumerable: true,
|
|
29
|
+
get: function () {
|
|
30
|
+
return _migration.migrateFromMMKV;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
exports.migrateToLatest = migrateToLatest;
|
|
34
|
+
exports.registerMigration = registerMigration;
|
|
14
35
|
exports.removeBatch = removeBatch;
|
|
36
|
+
exports.runTransaction = runTransaction;
|
|
15
37
|
exports.setBatch = setBatch;
|
|
16
38
|
exports.storage = void 0;
|
|
17
39
|
exports.useSetStorage = useSetStorage;
|
|
18
40
|
exports.useStorage = useStorage;
|
|
41
|
+
exports.useStorageSelector = useStorageSelector;
|
|
19
42
|
var _react = require("react");
|
|
20
43
|
var _reactNativeNitroModules = require("react-native-nitro-modules");
|
|
21
44
|
var _Storage = require("./Storage.types");
|
|
45
|
+
var _internal = require("./internal");
|
|
46
|
+
var _migration = require("./migration");
|
|
47
|
+
function asInternal(item) {
|
|
48
|
+
return item;
|
|
49
|
+
}
|
|
50
|
+
const registeredMigrations = new Map();
|
|
51
|
+
const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
|
|
52
|
+
Promise.resolve().then(task);
|
|
53
|
+
};
|
|
22
54
|
let _storageModule = null;
|
|
23
55
|
function getStorageModule() {
|
|
24
56
|
if (!_storageModule) {
|
|
@@ -27,102 +59,506 @@ function getStorageModule() {
|
|
|
27
59
|
return _storageModule;
|
|
28
60
|
}
|
|
29
61
|
const memoryStore = new Map();
|
|
30
|
-
const memoryListeners = new
|
|
31
|
-
|
|
32
|
-
|
|
62
|
+
const memoryListeners = new Map();
|
|
63
|
+
const scopedListeners = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
|
|
64
|
+
const scopedUnsubscribers = new Map();
|
|
65
|
+
const scopedRawCache = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
|
|
66
|
+
const pendingSecureWrites = new Map();
|
|
67
|
+
let secureFlushScheduled = false;
|
|
68
|
+
let secureDefaultAccessControl = _Storage.AccessControl.WhenUnlocked;
|
|
69
|
+
function getScopedListeners(scope) {
|
|
70
|
+
return scopedListeners.get(scope);
|
|
71
|
+
}
|
|
72
|
+
function getScopeRawCache(scope) {
|
|
73
|
+
return scopedRawCache.get(scope);
|
|
74
|
+
}
|
|
75
|
+
function cacheRawValue(scope, key, value) {
|
|
76
|
+
getScopeRawCache(scope).set(key, value);
|
|
77
|
+
}
|
|
78
|
+
function readCachedRawValue(scope, key) {
|
|
79
|
+
return getScopeRawCache(scope).get(key);
|
|
80
|
+
}
|
|
81
|
+
function hasCachedRawValue(scope, key) {
|
|
82
|
+
return getScopeRawCache(scope).has(key);
|
|
83
|
+
}
|
|
84
|
+
function clearScopeRawCache(scope) {
|
|
85
|
+
getScopeRawCache(scope).clear();
|
|
86
|
+
}
|
|
87
|
+
function notifyKeyListeners(registry, key) {
|
|
88
|
+
registry.get(key)?.forEach(listener => listener());
|
|
89
|
+
}
|
|
90
|
+
function notifyAllListeners(registry) {
|
|
91
|
+
registry.forEach(listeners => {
|
|
92
|
+
listeners.forEach(listener => listener());
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function addKeyListener(registry, key, listener) {
|
|
96
|
+
let listeners = registry.get(key);
|
|
97
|
+
if (!listeners) {
|
|
98
|
+
listeners = new Set();
|
|
99
|
+
registry.set(key, listeners);
|
|
100
|
+
}
|
|
101
|
+
listeners.add(listener);
|
|
102
|
+
return () => {
|
|
103
|
+
const scopedListeners = registry.get(key);
|
|
104
|
+
if (!scopedListeners) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
scopedListeners.delete(listener);
|
|
108
|
+
if (scopedListeners.size === 0) {
|
|
109
|
+
registry.delete(key);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function readPendingSecureWrite(key) {
|
|
114
|
+
return pendingSecureWrites.get(key)?.value;
|
|
115
|
+
}
|
|
116
|
+
function hasPendingSecureWrite(key) {
|
|
117
|
+
return pendingSecureWrites.has(key);
|
|
118
|
+
}
|
|
119
|
+
function clearPendingSecureWrite(key) {
|
|
120
|
+
pendingSecureWrites.delete(key);
|
|
121
|
+
}
|
|
122
|
+
function flushSecureWrites() {
|
|
123
|
+
secureFlushScheduled = false;
|
|
124
|
+
if (pendingSecureWrites.size === 0) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const writes = Array.from(pendingSecureWrites.values());
|
|
128
|
+
pendingSecureWrites.clear();
|
|
129
|
+
const keysToSet = [];
|
|
130
|
+
const valuesToSet = [];
|
|
131
|
+
const keysToRemove = [];
|
|
132
|
+
writes.forEach(({
|
|
133
|
+
key,
|
|
134
|
+
value
|
|
135
|
+
}) => {
|
|
136
|
+
if (value === undefined) {
|
|
137
|
+
keysToRemove.push(key);
|
|
138
|
+
} else {
|
|
139
|
+
keysToSet.push(key);
|
|
140
|
+
valuesToSet.push(value);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
const storageModule = getStorageModule();
|
|
144
|
+
storageModule.setSecureAccessControl(secureDefaultAccessControl);
|
|
145
|
+
if (keysToSet.length > 0) {
|
|
146
|
+
storageModule.setBatch(keysToSet, valuesToSet, _Storage.StorageScope.Secure);
|
|
147
|
+
}
|
|
148
|
+
if (keysToRemove.length > 0) {
|
|
149
|
+
storageModule.removeBatch(keysToRemove, _Storage.StorageScope.Secure);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function scheduleSecureWrite(key, value) {
|
|
153
|
+
pendingSecureWrites.set(key, {
|
|
154
|
+
key,
|
|
155
|
+
value
|
|
156
|
+
});
|
|
157
|
+
if (secureFlushScheduled) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
secureFlushScheduled = true;
|
|
161
|
+
runMicrotask(flushSecureWrites);
|
|
162
|
+
}
|
|
163
|
+
function ensureNativeScopeSubscription(scope) {
|
|
164
|
+
if (scopedUnsubscribers.has(scope)) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const unsubscribe = getStorageModule().addOnChange(scope, (key, value) => {
|
|
168
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
169
|
+
if (key === "") {
|
|
170
|
+
pendingSecureWrites.clear();
|
|
171
|
+
} else {
|
|
172
|
+
clearPendingSecureWrite(key);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (key === "") {
|
|
176
|
+
clearScopeRawCache(scope);
|
|
177
|
+
notifyAllListeners(getScopedListeners(scope));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
cacheRawValue(scope, key, value);
|
|
181
|
+
notifyKeyListeners(getScopedListeners(scope), key);
|
|
182
|
+
});
|
|
183
|
+
scopedUnsubscribers.set(scope, unsubscribe);
|
|
184
|
+
}
|
|
185
|
+
function maybeCleanupNativeScopeSubscription(scope) {
|
|
186
|
+
const listeners = getScopedListeners(scope);
|
|
187
|
+
if (listeners.size > 0) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const unsubscribe = scopedUnsubscribers.get(scope);
|
|
191
|
+
if (!unsubscribe) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
unsubscribe();
|
|
195
|
+
scopedUnsubscribers.delete(scope);
|
|
196
|
+
}
|
|
197
|
+
function getRawValue(key, scope) {
|
|
198
|
+
(0, _internal.assertValidScope)(scope);
|
|
199
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
200
|
+
const value = memoryStore.get(key);
|
|
201
|
+
return typeof value === "string" ? value : undefined;
|
|
202
|
+
}
|
|
203
|
+
if (scope === _Storage.StorageScope.Secure && hasPendingSecureWrite(key)) {
|
|
204
|
+
return readPendingSecureWrite(key);
|
|
205
|
+
}
|
|
206
|
+
return getStorageModule().get(key, scope);
|
|
207
|
+
}
|
|
208
|
+
function setRawValue(key, value, scope) {
|
|
209
|
+
(0, _internal.assertValidScope)(scope);
|
|
210
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
211
|
+
memoryStore.set(key, value);
|
|
212
|
+
notifyKeyListeners(memoryListeners, key);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
216
|
+
flushSecureWrites();
|
|
217
|
+
clearPendingSecureWrite(key);
|
|
218
|
+
getStorageModule().setSecureAccessControl(secureDefaultAccessControl);
|
|
219
|
+
}
|
|
220
|
+
getStorageModule().set(key, value, scope);
|
|
221
|
+
cacheRawValue(scope, key, value);
|
|
222
|
+
}
|
|
223
|
+
function removeRawValue(key, scope) {
|
|
224
|
+
(0, _internal.assertValidScope)(scope);
|
|
225
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
226
|
+
memoryStore.delete(key);
|
|
227
|
+
notifyKeyListeners(memoryListeners, key);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
231
|
+
flushSecureWrites();
|
|
232
|
+
clearPendingSecureWrite(key);
|
|
233
|
+
}
|
|
234
|
+
getStorageModule().remove(key, scope);
|
|
235
|
+
cacheRawValue(scope, key, undefined);
|
|
236
|
+
}
|
|
237
|
+
function readMigrationVersion(scope) {
|
|
238
|
+
const raw = getRawValue(_internal.MIGRATION_VERSION_KEY, scope);
|
|
239
|
+
if (raw === undefined) {
|
|
240
|
+
return 0;
|
|
241
|
+
}
|
|
242
|
+
const parsed = Number.parseInt(raw, 10);
|
|
243
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
244
|
+
}
|
|
245
|
+
function writeMigrationVersion(scope, version) {
|
|
246
|
+
setRawValue(_internal.MIGRATION_VERSION_KEY, String(version), scope);
|
|
33
247
|
}
|
|
34
248
|
const storage = exports.storage = {
|
|
35
249
|
clear: scope => {
|
|
36
250
|
if (scope === _Storage.StorageScope.Memory) {
|
|
37
251
|
memoryStore.clear();
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
252
|
+
notifyAllListeners(memoryListeners);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
256
|
+
flushSecureWrites();
|
|
257
|
+
pendingSecureWrites.clear();
|
|
258
|
+
}
|
|
259
|
+
clearScopeRawCache(scope);
|
|
260
|
+
getStorageModule().clear(scope);
|
|
261
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
262
|
+
getStorageModule().clearSecureBiometric();
|
|
41
263
|
}
|
|
42
264
|
},
|
|
43
265
|
clearAll: () => {
|
|
44
266
|
storage.clear(_Storage.StorageScope.Memory);
|
|
45
267
|
storage.clear(_Storage.StorageScope.Disk);
|
|
46
268
|
storage.clear(_Storage.StorageScope.Secure);
|
|
269
|
+
},
|
|
270
|
+
clearNamespace: (namespace, scope) => {
|
|
271
|
+
(0, _internal.assertValidScope)(scope);
|
|
272
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
273
|
+
for (const key of memoryStore.keys()) {
|
|
274
|
+
if ((0, _internal.isNamespaced)(key, namespace)) {
|
|
275
|
+
memoryStore.delete(key);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
notifyAllListeners(memoryListeners);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
282
|
+
flushSecureWrites();
|
|
283
|
+
}
|
|
284
|
+
const keys = getStorageModule().getAllKeys(scope);
|
|
285
|
+
const namespacedKeys = keys.filter(k => (0, _internal.isNamespaced)(k, namespace));
|
|
286
|
+
if (namespacedKeys.length > 0) {
|
|
287
|
+
getStorageModule().removeBatch(namespacedKeys, scope);
|
|
288
|
+
namespacedKeys.forEach(k => cacheRawValue(scope, k, undefined));
|
|
289
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
290
|
+
namespacedKeys.forEach(k => clearPendingSecureWrite(k));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
clearBiometric: () => {
|
|
295
|
+
getStorageModule().clearSecureBiometric();
|
|
296
|
+
},
|
|
297
|
+
has: (key, scope) => {
|
|
298
|
+
(0, _internal.assertValidScope)(scope);
|
|
299
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
300
|
+
return memoryStore.has(key);
|
|
301
|
+
}
|
|
302
|
+
return getStorageModule().has(key, scope);
|
|
303
|
+
},
|
|
304
|
+
getAllKeys: scope => {
|
|
305
|
+
(0, _internal.assertValidScope)(scope);
|
|
306
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
307
|
+
return Array.from(memoryStore.keys());
|
|
308
|
+
}
|
|
309
|
+
return getStorageModule().getAllKeys(scope);
|
|
310
|
+
},
|
|
311
|
+
getAll: scope => {
|
|
312
|
+
(0, _internal.assertValidScope)(scope);
|
|
313
|
+
const result = {};
|
|
314
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
315
|
+
memoryStore.forEach((value, key) => {
|
|
316
|
+
if (typeof value === "string") result[key] = value;
|
|
317
|
+
});
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
320
|
+
const keys = getStorageModule().getAllKeys(scope);
|
|
321
|
+
if (keys.length === 0) return result;
|
|
322
|
+
const values = getStorageModule().getBatch(keys, scope);
|
|
323
|
+
keys.forEach((key, idx) => {
|
|
324
|
+
const val = (0, _internal.decodeNativeBatchValue)(values[idx]);
|
|
325
|
+
if (val !== undefined) result[key] = val;
|
|
326
|
+
});
|
|
327
|
+
return result;
|
|
328
|
+
},
|
|
329
|
+
size: scope => {
|
|
330
|
+
(0, _internal.assertValidScope)(scope);
|
|
331
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
332
|
+
return memoryStore.size;
|
|
333
|
+
}
|
|
334
|
+
return getStorageModule().size(scope);
|
|
335
|
+
},
|
|
336
|
+
setAccessControl: level => {
|
|
337
|
+
secureDefaultAccessControl = level;
|
|
338
|
+
getStorageModule().setSecureAccessControl(level);
|
|
339
|
+
},
|
|
340
|
+
setKeychainAccessGroup: group => {
|
|
341
|
+
getStorageModule().setKeychainAccessGroup(group);
|
|
47
342
|
}
|
|
48
343
|
};
|
|
344
|
+
function canUseRawBatchPath(item) {
|
|
345
|
+
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
|
|
346
|
+
}
|
|
49
347
|
function defaultSerialize(value) {
|
|
50
|
-
return
|
|
348
|
+
return (0, _internal.serializeWithPrimitiveFastPath)(value);
|
|
51
349
|
}
|
|
52
350
|
function defaultDeserialize(value) {
|
|
53
|
-
return
|
|
351
|
+
return (0, _internal.deserializeWithPrimitiveFastPath)(value);
|
|
54
352
|
}
|
|
55
353
|
function createStorageItem(config) {
|
|
354
|
+
const storageKey = (0, _internal.prefixKey)(config.namespace, config.key);
|
|
56
355
|
const serialize = config.serialize ?? defaultSerialize;
|
|
57
356
|
const deserialize = config.deserialize ?? defaultDeserialize;
|
|
58
357
|
const isMemory = config.scope === _Storage.StorageScope.Memory;
|
|
358
|
+
const isBiometric = config.biometric === true && config.scope === _Storage.StorageScope.Secure;
|
|
359
|
+
const secureAccessControl = config.accessControl;
|
|
360
|
+
const validate = config.validate;
|
|
361
|
+
const onValidationError = config.onValidationError;
|
|
362
|
+
const expiration = config.expiration;
|
|
363
|
+
const onExpired = config.onExpired;
|
|
364
|
+
const expirationTtlMs = expiration?.ttlMs;
|
|
365
|
+
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
366
|
+
const readCache = !isMemory && config.readCache === true;
|
|
367
|
+
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric && secureAccessControl === undefined;
|
|
368
|
+
const nonMemoryScope = config.scope === _Storage.StorageScope.Disk ? _Storage.StorageScope.Disk : config.scope === _Storage.StorageScope.Secure ? _Storage.StorageScope.Secure : null;
|
|
369
|
+
if (expiration && expiration.ttlMs <= 0) {
|
|
370
|
+
throw new Error("expiration.ttlMs must be greater than 0.");
|
|
371
|
+
}
|
|
59
372
|
const listeners = new Set();
|
|
60
373
|
let unsubscribe = null;
|
|
374
|
+
let lastRaw = undefined;
|
|
375
|
+
let lastValue;
|
|
376
|
+
let hasLastValue = false;
|
|
377
|
+
const invalidateParsedCache = () => {
|
|
378
|
+
lastRaw = undefined;
|
|
379
|
+
lastValue = undefined;
|
|
380
|
+
hasLastValue = false;
|
|
381
|
+
};
|
|
61
382
|
const ensureSubscription = () => {
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
383
|
+
if (unsubscribe) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const listener = () => {
|
|
387
|
+
invalidateParsedCache();
|
|
388
|
+
listeners.forEach(callback => callback());
|
|
389
|
+
};
|
|
390
|
+
if (isMemory) {
|
|
391
|
+
unsubscribe = addKeyListener(memoryListeners, storageKey, listener);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
ensureNativeScopeSubscription(nonMemoryScope);
|
|
395
|
+
unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), storageKey, listener);
|
|
396
|
+
};
|
|
397
|
+
const readStoredRaw = () => {
|
|
398
|
+
if (isMemory) {
|
|
399
|
+
if (memoryExpiration) {
|
|
400
|
+
const expiresAt = memoryExpiration.get(storageKey);
|
|
401
|
+
if (expiresAt !== undefined && expiresAt <= Date.now()) {
|
|
402
|
+
memoryExpiration.delete(storageKey);
|
|
403
|
+
memoryStore.delete(storageKey);
|
|
404
|
+
notifyKeyListeners(memoryListeners, storageKey);
|
|
405
|
+
onExpired?.(storageKey);
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
81
408
|
}
|
|
409
|
+
return memoryStore.get(storageKey);
|
|
410
|
+
}
|
|
411
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric && hasPendingSecureWrite(storageKey)) {
|
|
412
|
+
return readPendingSecureWrite(storageKey);
|
|
82
413
|
}
|
|
414
|
+
if (readCache) {
|
|
415
|
+
if (hasCachedRawValue(nonMemoryScope, storageKey)) {
|
|
416
|
+
return readCachedRawValue(nonMemoryScope, storageKey);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (isBiometric) {
|
|
420
|
+
return getStorageModule().getSecureBiometric(storageKey);
|
|
421
|
+
}
|
|
422
|
+
const raw = getStorageModule().get(storageKey, config.scope);
|
|
423
|
+
cacheRawValue(nonMemoryScope, storageKey, raw);
|
|
424
|
+
return raw;
|
|
83
425
|
};
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
426
|
+
const writeStoredRaw = rawValue => {
|
|
427
|
+
if (isBiometric) {
|
|
428
|
+
getStorageModule().setSecureBiometric(storageKey, rawValue);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
432
|
+
if (coalesceSecureWrites) {
|
|
433
|
+
scheduleSecureWrite(storageKey, rawValue);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
437
|
+
clearPendingSecureWrite(storageKey);
|
|
438
|
+
getStorageModule().setSecureAccessControl(secureAccessControl ?? secureDefaultAccessControl);
|
|
439
|
+
}
|
|
440
|
+
getStorageModule().set(storageKey, rawValue, config.scope);
|
|
441
|
+
};
|
|
442
|
+
const removeStoredRaw = () => {
|
|
443
|
+
if (isBiometric) {
|
|
444
|
+
getStorageModule().deleteSecureBiometric(storageKey);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
448
|
+
if (coalesceSecureWrites) {
|
|
449
|
+
scheduleSecureWrite(storageKey, undefined);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
453
|
+
clearPendingSecureWrite(storageKey);
|
|
454
|
+
}
|
|
455
|
+
getStorageModule().remove(storageKey, config.scope);
|
|
456
|
+
};
|
|
457
|
+
const writeValueWithoutValidation = value => {
|
|
88
458
|
if (isMemory) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
459
|
+
if (memoryExpiration) {
|
|
460
|
+
memoryExpiration.set(storageKey, Date.now() + (expirationTtlMs ?? 0));
|
|
461
|
+
}
|
|
462
|
+
memoryStore.set(storageKey, value);
|
|
463
|
+
notifyKeyListeners(memoryListeners, storageKey);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const serialized = serialize(value);
|
|
467
|
+
if (expiration) {
|
|
468
|
+
const envelope = {
|
|
469
|
+
__nitroStorageEnvelope: true,
|
|
470
|
+
expiresAt: Date.now() + expiration.ttlMs,
|
|
471
|
+
payload: serialized
|
|
472
|
+
};
|
|
473
|
+
writeStoredRaw(JSON.stringify(envelope));
|
|
474
|
+
return;
|
|
92
475
|
}
|
|
93
|
-
|
|
476
|
+
writeStoredRaw(serialized);
|
|
477
|
+
};
|
|
478
|
+
const resolveInvalidValue = invalidValue => {
|
|
479
|
+
if (onValidationError) {
|
|
480
|
+
return onValidationError(invalidValue);
|
|
481
|
+
}
|
|
482
|
+
return config.defaultValue;
|
|
483
|
+
};
|
|
484
|
+
const ensureValidatedValue = (candidate, hadStoredValue) => {
|
|
485
|
+
if (!validate || validate(candidate)) {
|
|
486
|
+
return candidate;
|
|
487
|
+
}
|
|
488
|
+
const resolved = resolveInvalidValue(candidate);
|
|
489
|
+
if (validate && !validate(resolved)) {
|
|
490
|
+
return config.defaultValue;
|
|
491
|
+
}
|
|
492
|
+
if (hadStoredValue) {
|
|
493
|
+
writeValueWithoutValidation(resolved);
|
|
494
|
+
}
|
|
495
|
+
return resolved;
|
|
496
|
+
};
|
|
497
|
+
const get = () => {
|
|
498
|
+
const raw = readStoredRaw();
|
|
499
|
+
const canUseCachedValue = !expiration && !memoryExpiration;
|
|
500
|
+
if (canUseCachedValue && raw === lastRaw && hasLastValue) {
|
|
94
501
|
return lastValue;
|
|
95
502
|
}
|
|
96
503
|
lastRaw = raw;
|
|
97
504
|
if (raw === undefined) {
|
|
98
|
-
lastValue = config.defaultValue;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
505
|
+
lastValue = ensureValidatedValue(config.defaultValue, false);
|
|
506
|
+
hasLastValue = true;
|
|
507
|
+
return lastValue;
|
|
508
|
+
}
|
|
509
|
+
if (isMemory) {
|
|
510
|
+
lastValue = ensureValidatedValue(raw, true);
|
|
511
|
+
hasLastValue = true;
|
|
512
|
+
return lastValue;
|
|
513
|
+
}
|
|
514
|
+
let deserializableRaw = raw;
|
|
515
|
+
if (expiration) {
|
|
516
|
+
try {
|
|
517
|
+
const parsed = JSON.parse(raw);
|
|
518
|
+
if ((0, _internal.isStoredEnvelope)(parsed)) {
|
|
519
|
+
if (parsed.expiresAt <= Date.now()) {
|
|
520
|
+
removeStoredRaw();
|
|
521
|
+
invalidateParsedCache();
|
|
522
|
+
onExpired?.(storageKey);
|
|
523
|
+
lastValue = ensureValidatedValue(config.defaultValue, false);
|
|
524
|
+
hasLastValue = true;
|
|
525
|
+
return lastValue;
|
|
526
|
+
}
|
|
527
|
+
deserializableRaw = parsed.payload;
|
|
528
|
+
}
|
|
529
|
+
} catch {
|
|
530
|
+
// Keep backward compatibility with legacy raw values.
|
|
104
531
|
}
|
|
105
532
|
}
|
|
533
|
+
lastValue = ensureValidatedValue(deserialize(deserializableRaw), true);
|
|
534
|
+
hasLastValue = true;
|
|
106
535
|
return lastValue;
|
|
107
536
|
};
|
|
108
537
|
const set = valueOrFn => {
|
|
109
538
|
const currentValue = get();
|
|
110
539
|
const newValue = typeof valueOrFn === "function" ? valueOrFn(currentValue) : valueOrFn;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
} else {
|
|
115
|
-
const serialized = serialize(newValue);
|
|
116
|
-
getStorageModule().set(config.key, serialized, config.scope);
|
|
540
|
+
invalidateParsedCache();
|
|
541
|
+
if (validate && !validate(newValue)) {
|
|
542
|
+
throw new Error(`Validation failed for key "${storageKey}" in scope "${_Storage.StorageScope[config.scope]}".`);
|
|
117
543
|
}
|
|
544
|
+
writeValueWithoutValidation(newValue);
|
|
118
545
|
};
|
|
119
546
|
const deleteItem = () => {
|
|
547
|
+
invalidateParsedCache();
|
|
120
548
|
if (isMemory) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
549
|
+
if (memoryExpiration) {
|
|
550
|
+
memoryExpiration.delete(storageKey);
|
|
551
|
+
}
|
|
552
|
+
memoryStore.delete(storageKey);
|
|
553
|
+
notifyKeyListeners(memoryListeners, storageKey);
|
|
554
|
+
return;
|
|
125
555
|
}
|
|
556
|
+
removeStoredRaw();
|
|
557
|
+
};
|
|
558
|
+
const hasItem = () => {
|
|
559
|
+
if (isMemory) return memoryStore.has(storageKey);
|
|
560
|
+
if (isBiometric) return getStorageModule().hasSecureBiometric(storageKey);
|
|
561
|
+
return getStorageModule().has(storageKey, config.scope);
|
|
126
562
|
};
|
|
127
563
|
const subscribe = callback => {
|
|
128
564
|
ensureSubscription();
|
|
@@ -131,41 +567,101 @@ function createStorageItem(config) {
|
|
|
131
567
|
listeners.delete(callback);
|
|
132
568
|
if (listeners.size === 0 && unsubscribe) {
|
|
133
569
|
unsubscribe();
|
|
570
|
+
if (!isMemory) {
|
|
571
|
+
maybeCleanupNativeScopeSubscription(nonMemoryScope);
|
|
572
|
+
}
|
|
134
573
|
unsubscribe = null;
|
|
135
574
|
}
|
|
136
575
|
};
|
|
137
576
|
};
|
|
138
|
-
|
|
577
|
+
const storageItem = {
|
|
139
578
|
get,
|
|
140
579
|
set,
|
|
141
580
|
delete: deleteItem,
|
|
581
|
+
has: hasItem,
|
|
142
582
|
subscribe,
|
|
143
583
|
serialize,
|
|
144
584
|
deserialize,
|
|
145
585
|
_triggerListeners: () => {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
listeners.forEach(l => l());
|
|
586
|
+
invalidateParsedCache();
|
|
587
|
+
listeners.forEach(listener => listener());
|
|
149
588
|
},
|
|
589
|
+
_hasValidation: validate !== undefined,
|
|
590
|
+
_hasExpiration: expiration !== undefined,
|
|
591
|
+
_readCacheEnabled: readCache,
|
|
592
|
+
_isBiometric: isBiometric,
|
|
593
|
+
_secureAccessControl: secureAccessControl,
|
|
150
594
|
scope: config.scope,
|
|
151
|
-
key:
|
|
595
|
+
key: storageKey
|
|
152
596
|
};
|
|
597
|
+
return storageItem;
|
|
153
598
|
}
|
|
154
599
|
function useStorage(item) {
|
|
155
600
|
const value = (0, _react.useSyncExternalStore)(item.subscribe, item.get, item.get);
|
|
156
601
|
return [value, item.set];
|
|
157
602
|
}
|
|
603
|
+
function useStorageSelector(item, selector, isEqual = Object.is) {
|
|
604
|
+
const selectedRef = (0, _react.useRef)({
|
|
605
|
+
hasValue: false
|
|
606
|
+
});
|
|
607
|
+
const getSelectedSnapshot = () => {
|
|
608
|
+
const nextSelected = selector(item.get());
|
|
609
|
+
const current = selectedRef.current;
|
|
610
|
+
if (current.hasValue && isEqual(current.value, nextSelected)) {
|
|
611
|
+
return current.value;
|
|
612
|
+
}
|
|
613
|
+
selectedRef.current = {
|
|
614
|
+
hasValue: true,
|
|
615
|
+
value: nextSelected
|
|
616
|
+
};
|
|
617
|
+
return nextSelected;
|
|
618
|
+
};
|
|
619
|
+
const selectedValue = (0, _react.useSyncExternalStore)(item.subscribe, getSelectedSnapshot, getSelectedSnapshot);
|
|
620
|
+
return [selectedValue, item.set];
|
|
621
|
+
}
|
|
158
622
|
function useSetStorage(item) {
|
|
159
623
|
return item.set;
|
|
160
624
|
}
|
|
161
625
|
function getBatch(items, scope) {
|
|
626
|
+
(0, _internal.assertBatchScope)(items, scope);
|
|
162
627
|
if (scope === _Storage.StorageScope.Memory) {
|
|
163
628
|
return items.map(item => item.get());
|
|
164
629
|
}
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
630
|
+
const useRawBatchPath = items.every(item => canUseRawBatchPath(item));
|
|
631
|
+
if (!useRawBatchPath) {
|
|
632
|
+
return items.map(item => item.get());
|
|
633
|
+
}
|
|
634
|
+
const useBatchCache = items.every(item => item._readCacheEnabled === true);
|
|
635
|
+
const rawValues = new Array(items.length);
|
|
636
|
+
const keysToFetch = [];
|
|
637
|
+
const keyIndexes = [];
|
|
638
|
+
items.forEach((item, index) => {
|
|
639
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
640
|
+
if (hasPendingSecureWrite(item.key)) {
|
|
641
|
+
rawValues[index] = readPendingSecureWrite(item.key);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (useBatchCache) {
|
|
646
|
+
if (hasCachedRawValue(scope, item.key)) {
|
|
647
|
+
rawValues[index] = readCachedRawValue(scope, item.key);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
keysToFetch.push(item.key);
|
|
652
|
+
keyIndexes.push(index);
|
|
653
|
+
});
|
|
654
|
+
if (keysToFetch.length > 0) {
|
|
655
|
+
const fetchedValues = getStorageModule().getBatch(keysToFetch, scope).map(value => (0, _internal.decodeNativeBatchValue)(value));
|
|
656
|
+
fetchedValues.forEach((value, index) => {
|
|
657
|
+
const key = keysToFetch[index];
|
|
658
|
+
const targetIndex = keyIndexes[index];
|
|
659
|
+
rawValues[targetIndex] = value;
|
|
660
|
+
cacheRawValue(scope, key, value);
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
return items.map((item, index) => {
|
|
664
|
+
const raw = rawValues[index];
|
|
169
665
|
if (raw === undefined) {
|
|
170
666
|
return item.get();
|
|
171
667
|
}
|
|
@@ -173,6 +669,7 @@ function getBatch(items, scope) {
|
|
|
173
669
|
});
|
|
174
670
|
}
|
|
175
671
|
function setBatch(items, scope) {
|
|
672
|
+
(0, _internal.assertBatchScope)(items.map(batchEntry => batchEntry.item), scope);
|
|
176
673
|
if (scope === _Storage.StorageScope.Memory) {
|
|
177
674
|
items.forEach(({
|
|
178
675
|
item,
|
|
@@ -180,22 +677,137 @@ function setBatch(items, scope) {
|
|
|
180
677
|
}) => item.set(value));
|
|
181
678
|
return;
|
|
182
679
|
}
|
|
183
|
-
const
|
|
184
|
-
const values = items.map(i => i.item.serialize(i.value));
|
|
185
|
-
getStorageModule().setBatch(keys, values, scope);
|
|
186
|
-
items.forEach(({
|
|
680
|
+
const useRawBatchPath = items.every(({
|
|
187
681
|
item
|
|
188
|
-
}) =>
|
|
189
|
-
|
|
190
|
-
|
|
682
|
+
}) => canUseRawBatchPath(asInternal(item)));
|
|
683
|
+
if (!useRawBatchPath) {
|
|
684
|
+
items.forEach(({
|
|
685
|
+
item,
|
|
686
|
+
value
|
|
687
|
+
}) => item.set(value));
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
const keys = items.map(entry => entry.item.key);
|
|
691
|
+
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
692
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
693
|
+
flushSecureWrites();
|
|
694
|
+
getStorageModule().setSecureAccessControl(secureDefaultAccessControl);
|
|
695
|
+
}
|
|
696
|
+
getStorageModule().setBatch(keys, values, scope);
|
|
697
|
+
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
191
698
|
}
|
|
192
699
|
function removeBatch(items, scope) {
|
|
700
|
+
(0, _internal.assertBatchScope)(items, scope);
|
|
193
701
|
if (scope === _Storage.StorageScope.Memory) {
|
|
194
702
|
items.forEach(item => item.delete());
|
|
195
703
|
return;
|
|
196
704
|
}
|
|
197
705
|
const keys = items.map(item => item.key);
|
|
706
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
707
|
+
flushSecureWrites();
|
|
708
|
+
}
|
|
198
709
|
getStorageModule().removeBatch(keys, scope);
|
|
199
|
-
|
|
710
|
+
keys.forEach(key => cacheRawValue(scope, key, undefined));
|
|
711
|
+
}
|
|
712
|
+
function registerMigration(version, migration) {
|
|
713
|
+
if (!Number.isInteger(version) || version <= 0) {
|
|
714
|
+
throw new Error("Migration version must be a positive integer.");
|
|
715
|
+
}
|
|
716
|
+
if (registeredMigrations.has(version)) {
|
|
717
|
+
throw new Error(`Migration version ${version} is already registered.`);
|
|
718
|
+
}
|
|
719
|
+
registeredMigrations.set(version, migration);
|
|
720
|
+
}
|
|
721
|
+
function migrateToLatest(scope = _Storage.StorageScope.Disk) {
|
|
722
|
+
(0, _internal.assertValidScope)(scope);
|
|
723
|
+
const currentVersion = readMigrationVersion(scope);
|
|
724
|
+
const versions = Array.from(registeredMigrations.keys()).filter(version => version > currentVersion).sort((a, b) => a - b);
|
|
725
|
+
let appliedVersion = currentVersion;
|
|
726
|
+
const context = {
|
|
727
|
+
scope,
|
|
728
|
+
getRaw: key => getRawValue(key, scope),
|
|
729
|
+
setRaw: (key, value) => setRawValue(key, value, scope),
|
|
730
|
+
removeRaw: key => removeRawValue(key, scope)
|
|
731
|
+
};
|
|
732
|
+
versions.forEach(version => {
|
|
733
|
+
const migration = registeredMigrations.get(version);
|
|
734
|
+
if (!migration) {
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
migration(context);
|
|
738
|
+
writeMigrationVersion(scope, version);
|
|
739
|
+
appliedVersion = version;
|
|
740
|
+
});
|
|
741
|
+
return appliedVersion;
|
|
742
|
+
}
|
|
743
|
+
function runTransaction(scope, transaction) {
|
|
744
|
+
(0, _internal.assertValidScope)(scope);
|
|
745
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
746
|
+
flushSecureWrites();
|
|
747
|
+
}
|
|
748
|
+
const rollback = new Map();
|
|
749
|
+
const rememberRollback = key => {
|
|
750
|
+
if (rollback.has(key)) {
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
rollback.set(key, getRawValue(key, scope));
|
|
754
|
+
};
|
|
755
|
+
const tx = {
|
|
756
|
+
scope,
|
|
757
|
+
getRaw: key => getRawValue(key, scope),
|
|
758
|
+
setRaw: (key, value) => {
|
|
759
|
+
rememberRollback(key);
|
|
760
|
+
setRawValue(key, value, scope);
|
|
761
|
+
},
|
|
762
|
+
removeRaw: key => {
|
|
763
|
+
rememberRollback(key);
|
|
764
|
+
removeRawValue(key, scope);
|
|
765
|
+
},
|
|
766
|
+
getItem: item => {
|
|
767
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
768
|
+
return item.get();
|
|
769
|
+
},
|
|
770
|
+
setItem: (item, value) => {
|
|
771
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
772
|
+
rememberRollback(item.key);
|
|
773
|
+
item.set(value);
|
|
774
|
+
},
|
|
775
|
+
removeItem: item => {
|
|
776
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
777
|
+
rememberRollback(item.key);
|
|
778
|
+
item.delete();
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
try {
|
|
782
|
+
return transaction(tx);
|
|
783
|
+
} catch (error) {
|
|
784
|
+
Array.from(rollback.entries()).reverse().forEach(([key, previousValue]) => {
|
|
785
|
+
if (previousValue === undefined) {
|
|
786
|
+
removeRawValue(key, scope);
|
|
787
|
+
} else {
|
|
788
|
+
setRawValue(key, previousValue, scope);
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
throw error;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
function createSecureAuthStorage(config, options) {
|
|
795
|
+
const ns = options?.namespace ?? "auth";
|
|
796
|
+
const result = {};
|
|
797
|
+
for (const key of Object.keys(config)) {
|
|
798
|
+
const itemConfig = config[key];
|
|
799
|
+
result[key] = createStorageItem({
|
|
800
|
+
key,
|
|
801
|
+
scope: _Storage.StorageScope.Secure,
|
|
802
|
+
defaultValue: "",
|
|
803
|
+
namespace: ns,
|
|
804
|
+
biometric: itemConfig.biometric,
|
|
805
|
+
accessControl: itemConfig.accessControl,
|
|
806
|
+
expiration: itemConfig.ttlMs ? {
|
|
807
|
+
ttlMs: itemConfig.ttlMs
|
|
808
|
+
} : undefined
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
return result;
|
|
200
812
|
}
|
|
201
813
|
//# sourceMappingURL=index.js.map
|