react-native-nitro-storage 0.5.9 → 0.7.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +193 -16
  3. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +225 -76
  4. package/app.plugin.js +2 -0
  5. package/cpp/bindings/HybridStorage.cpp +29 -21
  6. package/cpp/bindings/HybridStorage.hpp +5 -0
  7. package/docs/secure-storage.md +4 -0
  8. package/lib/commonjs/index.js +19 -2
  9. package/lib/commonjs/index.js.map +1 -1
  10. package/lib/commonjs/index.web.js +19 -2
  11. package/lib/commonjs/index.web.js.map +1 -1
  12. package/lib/commonjs/indexeddb-backend.js.map +1 -1
  13. package/lib/commonjs/internal.js.map +1 -1
  14. package/lib/commonjs/shared.js.map +1 -1
  15. package/lib/commonjs/storage-core.js +306 -10
  16. package/lib/commonjs/storage-core.js.map +1 -1
  17. package/lib/commonjs/storage-events.js.map +1 -1
  18. package/lib/commonjs/storage-hooks.js +16 -1
  19. package/lib/commonjs/storage-hooks.js.map +1 -1
  20. package/lib/commonjs/testing.js +267 -0
  21. package/lib/commonjs/testing.js.map +1 -0
  22. package/lib/module/index.js +5 -1
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/index.web.js +5 -1
  25. package/lib/module/index.web.js.map +1 -1
  26. package/lib/module/indexeddb-backend.js.map +1 -1
  27. package/lib/module/internal.js.map +1 -1
  28. package/lib/module/shared.js.map +1 -1
  29. package/lib/module/storage-core.js +307 -11
  30. package/lib/module/storage-core.js.map +1 -1
  31. package/lib/module/storage-events.js.map +1 -1
  32. package/lib/module/storage-hooks.js +15 -2
  33. package/lib/module/storage-hooks.js.map +1 -1
  34. package/lib/module/testing.js +188 -0
  35. package/lib/module/testing.js.map +1 -0
  36. package/lib/typescript/index.d.ts +28 -5
  37. package/lib/typescript/index.d.ts.map +1 -1
  38. package/lib/typescript/index.web.d.ts +28 -5
  39. package/lib/typescript/index.web.d.ts.map +1 -1
  40. package/lib/typescript/indexeddb-backend.d.ts.map +1 -1
  41. package/lib/typescript/shared.d.ts +1 -0
  42. package/lib/typescript/shared.d.ts.map +1 -1
  43. package/lib/typescript/storage-core.d.ts +62 -3
  44. package/lib/typescript/storage-core.d.ts.map +1 -1
  45. package/lib/typescript/storage-events.d.ts +1 -1
  46. package/lib/typescript/storage-events.d.ts.map +1 -1
  47. package/lib/typescript/storage-hooks.d.ts +18 -1
  48. package/lib/typescript/storage-hooks.d.ts.map +1 -1
  49. package/lib/typescript/testing.d.ts +158 -0
  50. package/lib/typescript/testing.d.ts.map +1 -0
  51. package/package.json +11 -6
  52. package/src/index.ts +17 -4
  53. package/src/index.web.ts +17 -4
  54. package/src/indexeddb-backend.ts +1 -2
  55. package/src/internal.ts +1 -1
  56. package/src/shared.ts +1 -0
  57. package/src/storage-core.ts +463 -11
  58. package/src/storage-events.ts +3 -2
  59. package/src/storage-hooks.ts +42 -3
  60. package/src/testing.ts +270 -0
@@ -1,16 +1,55 @@
1
- import { useRef, useSyncExternalStore } from "react";
1
+ import { useMemo, useRef, useSyncExternalStore } from "react";
2
2
 
3
3
  type HookStorageItem<T> = {
4
4
  get: () => T;
5
5
  set: StorageSetter<T>;
6
6
  subscribe: (callback: () => void) => () => void;
7
+ merge?: (partial: Partial<T>) => void;
8
+ reset?: () => void;
9
+ delete?: () => void;
10
+ setOrDelete?: (value: T | null | undefined) => void;
11
+ };
12
+
13
+ type ReadableStorageItem<T> = {
14
+ get: () => T;
15
+ subscribe: (callback: () => void) => () => void;
7
16
  };
8
17
 
9
18
  export type StorageSetter<T> = (value: T | ((prev: T) => T)) => void;
10
19
 
11
- export function useStorage<T>(item: HookStorageItem<T>): [T, StorageSetter<T>] {
20
+ export type StorageActions<T> = {
21
+ set: StorageSetter<T>;
22
+ merge: (partial: Partial<T>) => void;
23
+ reset: () => void;
24
+ remove: () => void;
25
+ setOrDelete: (value: T | null | undefined) => void;
26
+ };
27
+
28
+ export function useStorageActions<T>(
29
+ item: HookStorageItem<T>,
30
+ ): StorageActions<T> {
31
+ return useMemo<StorageActions<T>>(
32
+ () => ({
33
+ set: item.set,
34
+ merge: (partial) => item.merge?.(partial),
35
+ reset: () => item.reset?.(),
36
+ remove: () => item.delete?.(),
37
+ setOrDelete: (value) => item.setOrDelete?.(value),
38
+ }),
39
+ [item],
40
+ );
41
+ }
42
+
43
+ export function useStorage<T>(
44
+ item: HookStorageItem<T>,
45
+ ): [T, StorageSetter<T>, StorageActions<T>] {
12
46
  const value = useSyncExternalStore(item.subscribe, item.get, item.get);
13
- return [value, item.set];
47
+ const actions = useStorageActions(item);
48
+ return [value, item.set, actions];
49
+ }
50
+
51
+ export function useStorageValue<T>(item: ReadableStorageItem<T>): T {
52
+ return useSyncExternalStore(item.subscribe, item.get, item.get);
14
53
  }
15
54
 
16
55
  export function useStorageSelector<T, TSelected>(
package/src/testing.ts ADDED
@@ -0,0 +1,270 @@
1
+ import { StorageScope, AccessControl } from "./Storage.types";
2
+ import { assertAccessControlLevel } from "./shared";
3
+ import type { NonMemoryScope } from "./shared";
4
+ import type {
5
+ SecurityCapabilities,
6
+ StorageCapabilities,
7
+ } from "./storage-runtime";
8
+ import {
9
+ createStorageCore,
10
+ type StorageCoreAdapter,
11
+ type StorageCoreBackend,
12
+ type StorageCoreInternals,
13
+ } from "./storage-core";
14
+
15
+ export { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
16
+ export { isKeychainLockedError } from "./shared";
17
+ export { migrateFromMMKV } from "./migration";
18
+ export { getStorageErrorCode } from "./storage-runtime";
19
+ export { createIndexedDBBackend } from "./indexeddb-backend";
20
+ export {
21
+ useSetStorage,
22
+ useStorage,
23
+ useStorageActions,
24
+ useStorageSelector,
25
+ useStorageValue,
26
+ } from "./storage-hooks";
27
+
28
+ export type {
29
+ ExpirationConfig,
30
+ Migration,
31
+ MigrationContext,
32
+ SecureAuthStorageConfig,
33
+ StorageEventObserverOptions,
34
+ StorageExportOptions,
35
+ StorageMetricSummary,
36
+ StorageMetricsEvent,
37
+ StorageMetricsObserver,
38
+ StorageSelectorListener,
39
+ StorageSelectorSubscribeOptions,
40
+ StorageVersion,
41
+ Validator,
42
+ VersionedValue,
43
+ } from "./shared";
44
+ export type {
45
+ SecureStorageMetadata,
46
+ SecurityCapabilities,
47
+ StorageCapabilities,
48
+ StorageErrorCode,
49
+ } from "./storage-runtime";
50
+ export type {
51
+ StorageBatchChangeEvent,
52
+ StorageChangeEvent,
53
+ StorageChangeOperation,
54
+ StorageChangeSource,
55
+ StorageEventListener,
56
+ StorageKeyChangeEvent,
57
+ } from "./storage-events";
58
+ export type { StorageActions, StorageSetter } from "./storage-hooks";
59
+ export type {
60
+ SetItemConfig,
61
+ SetStorageItem,
62
+ StorageBatchSetItem,
63
+ StorageClearOptions,
64
+ StorageItem,
65
+ StorageItemConfig,
66
+ StorageKeyRef,
67
+ TransactionContext,
68
+ } from "./storage-core";
69
+
70
+ const TEST_SECURE_BACKEND = "in-memory-test";
71
+
72
+ type InMemoryBackend = StorageCoreBackend & {
73
+ resetState: () => void;
74
+ };
75
+
76
+ function createInMemoryBackend(): InMemoryBackend {
77
+ const stores = new Map<number, Map<string, string>>([
78
+ [StorageScope.Disk, new Map()],
79
+ [StorageScope.Secure, new Map()],
80
+ ]);
81
+ const biometricStore = new Map<string, string>();
82
+
83
+ const storeFor = (scope: StorageScope): Map<string, string> => {
84
+ const store = stores.get(scope);
85
+ if (!store) {
86
+ throw new Error(`NitroStorage testing: unsupported scope ${scope}`);
87
+ }
88
+ return store;
89
+ };
90
+
91
+ return {
92
+ get: (key, scope) => storeFor(scope).get(key),
93
+ set: (key, value, scope) => {
94
+ storeFor(scope).set(key, value);
95
+ },
96
+ remove: (key, scope) => {
97
+ storeFor(scope).delete(key);
98
+ },
99
+ clear: (scope) => {
100
+ storeFor(scope).clear();
101
+ },
102
+ has: (key, scope) => storeFor(scope).has(key),
103
+ getAllKeys: (scope) => Array.from(storeFor(scope).keys()),
104
+ getKeysByPrefix: (prefix, scope) =>
105
+ Array.from(storeFor(scope).keys()).filter((key) =>
106
+ key.startsWith(prefix),
107
+ ),
108
+ size: (scope) => storeFor(scope).size,
109
+ setBatch: (keys, values, scope) => {
110
+ const store = storeFor(scope);
111
+ keys.forEach((key, index) => {
112
+ const value = values[index];
113
+ if (value !== undefined) {
114
+ store.set(key, value);
115
+ }
116
+ });
117
+ },
118
+ getBatch: (keys, scope) => {
119
+ const store = storeFor(scope);
120
+ return keys.map((key) => store.get(key));
121
+ },
122
+ removeBatch: (keys, scope) => {
123
+ const store = storeFor(scope);
124
+ keys.forEach((key) => store.delete(key));
125
+ },
126
+ removeByPrefix: (prefix, scope) => {
127
+ const store = storeFor(scope);
128
+ for (const key of Array.from(store.keys())) {
129
+ if (key.startsWith(prefix)) {
130
+ store.delete(key);
131
+ }
132
+ }
133
+ },
134
+ setSecureAccessControl: () => {},
135
+ getSecureBiometric: (key) => biometricStore.get(key),
136
+ setSecureBiometricWithLevel: (key, value) => {
137
+ biometricStore.set(key, value);
138
+ },
139
+ deleteSecureBiometric: (key) => {
140
+ biometricStore.delete(key);
141
+ },
142
+ hasSecureBiometric: (key) => biometricStore.has(key),
143
+ clearSecureBiometric: () => {
144
+ biometricStore.clear();
145
+ },
146
+ resetState: () => {
147
+ stores.forEach((store) => store.clear());
148
+ biometricStore.clear();
149
+ },
150
+ };
151
+ }
152
+
153
+ function buildTestingModule() {
154
+ const backend = createInMemoryBackend();
155
+
156
+ const buildAdapter = (
157
+ _internals: StorageCoreInternals,
158
+ ): StorageCoreAdapter => ({
159
+ backend,
160
+ changeSource: "native",
161
+ applyAccessControlOnSecureRawWrite: true,
162
+ flushDiskWritesOnImport: false,
163
+ ensureScopeSubscription: (_scope: NonMemoryScope) => {},
164
+ maybeCleanupScopeSubscription: (_scope: NonMemoryScope) => {},
165
+ onWillEmitChanges: () => {},
166
+ getSecureMetadataProfile: () => ({
167
+ backend: TEST_SECURE_BACKEND,
168
+ encrypted: "unavailable",
169
+ hardwareBacked: "unavailable",
170
+ }),
171
+ });
172
+
173
+ const core = createStorageCore(buildAdapter);
174
+ const { internals } = core;
175
+
176
+ const storage = {
177
+ ...core.storage,
178
+ setAccessControl: (level: AccessControl) => {
179
+ assertAccessControlLevel(level);
180
+ internals.setSecureDefaultAccessControl(level);
181
+ },
182
+ setSecureWritesAsync: (_enabled: boolean) => {},
183
+ setKeychainAccessGroup: (_group: string) => {},
184
+ getCapabilities: (): StorageCapabilities => ({
185
+ platform: "native",
186
+ backend: {
187
+ disk: "in-memory-test",
188
+ secure: TEST_SECURE_BACKEND,
189
+ },
190
+ writeBuffering: {
191
+ disk: false,
192
+ secure: false,
193
+ },
194
+ errorClassification: true,
195
+ }),
196
+ getSecurityCapabilities: (): SecurityCapabilities => ({
197
+ platform: "native",
198
+ secureStorage: {
199
+ backend: TEST_SECURE_BACKEND,
200
+ encrypted: "unavailable",
201
+ accessControl: "unavailable",
202
+ keychainAccessGroup: "unavailable",
203
+ hardwareBacked: "unavailable",
204
+ },
205
+ biometric: {
206
+ storage: "unavailable",
207
+ prompt: "unavailable",
208
+ biometryOnly: "unavailable",
209
+ biometryOrPasscode: "unavailable",
210
+ },
211
+ metadata: {
212
+ perKey: true,
213
+ listsWithoutValues: true,
214
+ persistsTimestamps: false,
215
+ },
216
+ }),
217
+ };
218
+
219
+ const reset = (): void => {
220
+ storage.setEventObserver(undefined);
221
+ storage.setMetricsObserver(undefined);
222
+ storage.resetMetrics();
223
+ storage.clearAll();
224
+ backend.resetState();
225
+ internals.setSecureDefaultAccessControl(AccessControl.WhenUnlocked);
226
+ };
227
+
228
+ return {
229
+ storage,
230
+ createStorageItem: core.createStorageItem,
231
+ memoryItem: core.memoryItem,
232
+ diskItem: core.diskItem,
233
+ secureItem: core.secureItem,
234
+ createSetItem: core.createSetItem,
235
+ getBatch: core.getBatch,
236
+ setBatch: core.setBatch,
237
+ removeBatch: core.removeBatch,
238
+ registerMigration: core.registerMigration,
239
+ migrateToLatest: core.migrateToLatest,
240
+ runTransaction: core.runTransaction,
241
+ createSecureAuthStorage: core.createSecureAuthStorage,
242
+ reset,
243
+ };
244
+ }
245
+
246
+ export type NitroStorageTestModule = ReturnType<typeof buildTestingModule>;
247
+
248
+ const defaultModule = buildTestingModule();
249
+
250
+ export const storage = defaultModule.storage;
251
+ export const createStorageItem = defaultModule.createStorageItem;
252
+ export const memoryItem = defaultModule.memoryItem;
253
+ export const diskItem = defaultModule.diskItem;
254
+ export const secureItem = defaultModule.secureItem;
255
+ export const createSetItem = defaultModule.createSetItem;
256
+ export const getBatch = defaultModule.getBatch;
257
+ export const setBatch = defaultModule.setBatch;
258
+ export const removeBatch = defaultModule.removeBatch;
259
+ export const registerMigration = defaultModule.registerMigration;
260
+ export const migrateToLatest = defaultModule.migrateToLatest;
261
+ export const runTransaction = defaultModule.runTransaction;
262
+ export const createSecureAuthStorage = defaultModule.createSecureAuthStorage;
263
+
264
+ export function resetNitroStorageMock(): void {
265
+ defaultModule.reset();
266
+ }
267
+
268
+ export function createNitroStorageMock(): NitroStorageTestModule {
269
+ return buildTestingModule();
270
+ }