react-native-mmkv 2.8.0 → 2.10.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 (41) hide show
  1. package/MMKV/Core/Core.xcodeproj/project.pbxproj +15 -19
  2. package/MMKV/Core/Core.xcodeproj/xcshareddata/xcschemes/Core.xcscheme +1 -1
  3. package/MMKV/Core/Core.xcodeproj/xcshareddata/xcschemes/MMKVWatchCore.xcscheme +1 -1
  4. package/MMKV/Core/MMBuffer.cpp +18 -0
  5. package/MMKV/Core/MMBuffer.h +1 -0
  6. package/MMKV/Core/MMKV.cpp +173 -33
  7. package/MMKV/Core/MMKV.h +44 -0
  8. package/MMKV/Core/MMKVMetaInfo.hpp +18 -0
  9. package/MMKV/Core/MMKVPredef.h +2 -2
  10. package/MMKV/Core/MMKV_IO.cpp +477 -68
  11. package/MMKV/Core/MMKV_OSX.cpp +65 -14
  12. package/MMKV/Core/MemoryFile_Win32.cpp +39 -35
  13. package/MMKV/Core/MiniPBCoder.cpp +3 -7
  14. package/MMKV/Core/MiniPBCoder_OSX.cpp +4 -4
  15. package/MMKV/Core/PBUtility.cpp +1 -1
  16. package/MMKV/Core/PBUtility.h +7 -0
  17. package/MMKV/Core/aes/AESCrypt.h +1 -1
  18. package/MMKV/Core/aes/openssl/openssl_aes-armv4.S +4 -0
  19. package/README.md +17 -3
  20. package/android/build.gradle +1 -0
  21. package/android/src/main/AndroidManifest.xml +1 -2
  22. package/ios/MmkvHostObject.mm +18 -6
  23. package/ios/MmkvModule.mm +8 -1
  24. package/lib/commonjs/MMKV.js.map +1 -1
  25. package/lib/commonjs/createMMKV.web.js +23 -0
  26. package/lib/commonjs/createMMKV.web.js.map +1 -1
  27. package/lib/module/MMKV.js.map +1 -1
  28. package/lib/module/createMMKV.web.js +23 -0
  29. package/lib/module/createMMKV.web.js.map +1 -1
  30. package/lib/typescript/MMKV.d.ts +5 -1
  31. package/lib/typescript/MMKV.d.ts.map +1 -1
  32. package/lib/typescript/createMMKV.web.d.ts.map +1 -1
  33. package/package.json +7 -6
  34. package/src/MMKV.ts +252 -0
  35. package/src/PlatformChecker.ts +7 -0
  36. package/src/createMMKV.mock.ts +33 -0
  37. package/src/createMMKV.ts +70 -0
  38. package/src/createMMKV.web.ts +119 -0
  39. package/src/createTextEncoder.ts +16 -0
  40. package/src/hooks.ts +232 -0
  41. package/src/index.ts +2 -0
@@ -0,0 +1,119 @@
1
+ /* global localStorage */
2
+ import type { MMKVConfiguration, NativeMMKV } from 'react-native-mmkv';
3
+ import { createTextEncoder } from './createTextEncoder';
4
+
5
+ const canUseDOM =
6
+ typeof window !== 'undefined' && window.document?.createElement != null;
7
+
8
+ const hasAccessToLocalStorage = () => {
9
+ try {
10
+ // throws ACCESS_DENIED error
11
+ window.localStorage;
12
+
13
+ return true;
14
+ } catch {
15
+ return false;
16
+ }
17
+ };
18
+
19
+ const KEY_WILDCARD = '\\';
20
+ const inMemoryStorage = new Map<string, string>();
21
+
22
+ export const createMMKV = (config: MMKVConfiguration): NativeMMKV => {
23
+ if (config.encryptionKey != null) {
24
+ throw new Error("MMKV: 'encryptionKey' is not supported on Web!");
25
+ }
26
+ if (config.path != null) {
27
+ throw new Error("MMKV: 'path' is not supported on Web!");
28
+ }
29
+
30
+ if (!hasAccessToLocalStorage()) {
31
+ console.warn(
32
+ 'MMKV: LocalStorage has been disabled. Your experience will be limited to in-memory storage!'
33
+ );
34
+ }
35
+
36
+ const storage = () => {
37
+ if (!canUseDOM) {
38
+ throw new Error(
39
+ 'Tried to access storage on the server. Did you forget to call this in useEffect?'
40
+ );
41
+ }
42
+
43
+ if (!hasAccessToLocalStorage()) {
44
+ return {
45
+ getItem: (key: string) => inMemoryStorage.get(key) ?? null,
46
+ setItem: (key: string, value: string) =>
47
+ inMemoryStorage.set(key, value),
48
+ removeItem: (key: string) => inMemoryStorage.delete(key),
49
+ clear: () => inMemoryStorage.clear(),
50
+ length: inMemoryStorage.size,
51
+ key: (index: number) => Object.keys(inMemoryStorage).at(index) ?? null,
52
+ } as Storage;
53
+ }
54
+
55
+ const domStorage =
56
+ global?.localStorage ?? window?.localStorage ?? localStorage;
57
+ if (domStorage == null) {
58
+ throw new Error(`Could not find 'localStorage' instance!`);
59
+ }
60
+ return domStorage;
61
+ };
62
+
63
+ const textEncoder = createTextEncoder();
64
+
65
+ if (config.id.includes(KEY_WILDCARD)) {
66
+ throw new Error(
67
+ 'MMKV: `id` cannot contain the backslash character (`\\`)!'
68
+ );
69
+ }
70
+
71
+ const keyPrefix = `${config.id}${KEY_WILDCARD}`; // mmkv.default\\
72
+ const prefixedKey = (key: string) => {
73
+ if (key.includes('\\')) {
74
+ throw new Error(
75
+ 'MMKV: `key` cannot contain the backslash character (`\\`)!'
76
+ );
77
+ }
78
+ return `${keyPrefix}${key}`;
79
+ };
80
+
81
+ return {
82
+ clearAll: () => {
83
+ const keys = Object.keys(storage());
84
+ for (const key of keys) {
85
+ if (key.startsWith(keyPrefix)) {
86
+ storage().removeItem(key);
87
+ }
88
+ }
89
+ },
90
+ delete: (key) => storage().removeItem(prefixedKey(key)),
91
+ set: (key, value) => {
92
+ storage().setItem(prefixedKey(key), value.toString());
93
+ },
94
+ getString: (key) => storage().getItem(prefixedKey(key)) ?? undefined,
95
+ getNumber: (key) => {
96
+ const value = storage().getItem(prefixedKey(key));
97
+ if (value == null) return undefined;
98
+ return Number(value);
99
+ },
100
+ getBoolean: (key) => {
101
+ const value = storage().getItem(prefixedKey(key));
102
+ if (value == null) return undefined;
103
+ return value === 'true';
104
+ },
105
+ getBuffer: (key) => {
106
+ const value = storage().getItem(prefixedKey(key));
107
+ if (value == null) return undefined;
108
+ return textEncoder.encode(value);
109
+ },
110
+ getAllKeys: () => {
111
+ const keys = Object.keys(storage());
112
+ return keys.filter((key) => key.startsWith(keyPrefix));
113
+ },
114
+ contains: (key) => storage().getItem(prefixedKey(key)) != null,
115
+ recrypt: () => {
116
+ throw new Error('`recrypt(..)` is not supported on Web!');
117
+ },
118
+ };
119
+ };
@@ -0,0 +1,16 @@
1
+ /* global TextEncoder */
2
+ export function createTextEncoder(): TextEncoder {
3
+ if (global.TextEncoder != null) {
4
+ return new global.TextEncoder();
5
+ } else {
6
+ return {
7
+ encode: () => {
8
+ throw new Error('TextEncoder is not supported in this environment!');
9
+ },
10
+ encodeInto: () => {
11
+ throw new Error('TextEncoder is not supported in this environment!');
12
+ },
13
+ encoding: 'utf-8',
14
+ };
15
+ }
16
+ }
package/src/hooks.ts ADDED
@@ -0,0 +1,232 @@
1
+ import { useRef, useState, useMemo, useCallback, useEffect } from 'react';
2
+ import { MMKV, MMKVConfiguration } from './MMKV';
3
+
4
+ function isConfigurationEqual(
5
+ left?: MMKVConfiguration,
6
+ right?: MMKVConfiguration
7
+ ): boolean {
8
+ if (left == null || right == null) return left == null && right == null;
9
+
10
+ return (
11
+ left.encryptionKey === right.encryptionKey &&
12
+ left.id === right.id &&
13
+ left.path === right.path
14
+ );
15
+ }
16
+
17
+ let defaultInstance: MMKV | null = null;
18
+ function getDefaultInstance(): MMKV {
19
+ if (defaultInstance == null) {
20
+ defaultInstance = new MMKV();
21
+ }
22
+ return defaultInstance;
23
+ }
24
+
25
+ /**
26
+ * Use the default, shared MMKV instance.
27
+ */
28
+ export function useMMKV(): MMKV;
29
+ /**
30
+ * Use a custom MMKV instance with the given configuration.
31
+ * @param configuration The configuration to initialize the MMKV instance with. Does not have to be memoized.
32
+ */
33
+ export function useMMKV(configuration: MMKVConfiguration): MMKV;
34
+ export function useMMKV(configuration?: MMKVConfiguration): MMKV {
35
+ const instance = useRef<MMKV>();
36
+ const lastConfiguration = useRef<MMKVConfiguration>();
37
+
38
+ if (configuration == null) return getDefaultInstance();
39
+
40
+ if (
41
+ instance.current == null ||
42
+ !isConfigurationEqual(lastConfiguration.current, configuration)
43
+ ) {
44
+ lastConfiguration.current = configuration;
45
+ instance.current = new MMKV(configuration);
46
+ }
47
+
48
+ return instance.current;
49
+ }
50
+
51
+ function createMMKVHook<
52
+ T extends (boolean | number | string | Uint8Array) | undefined,
53
+ TSet extends T | undefined,
54
+ TSetAction extends TSet | ((current: T) => TSet)
55
+ >(getter: (instance: MMKV, key: string) => T) {
56
+ return (
57
+ key: string,
58
+ instance?: MMKV
59
+ ): [value: T, setValue: (value: TSetAction) => void] => {
60
+ const mmkv = instance ?? getDefaultInstance();
61
+ const [value, setValue] = useState(() => getter(mmkv, key));
62
+ const valueRef = useRef<T>(value);
63
+ valueRef.current = value;
64
+
65
+ // update value by user set
66
+ const set = useCallback(
67
+ (v: TSetAction) => {
68
+ const newValue = typeof v === 'function' ? v(valueRef.current) : v;
69
+ switch (typeof newValue) {
70
+ case 'number':
71
+ case 'string':
72
+ case 'boolean':
73
+ mmkv.set(key, newValue);
74
+ break;
75
+ case 'undefined':
76
+ mmkv.delete(key);
77
+ break;
78
+ case 'object':
79
+ if (newValue instanceof Uint8Array) {
80
+ mmkv.set(key, newValue);
81
+ break;
82
+ } else {
83
+ throw new Error(
84
+ `MMKV: Type object (${newValue}) is not supported!`
85
+ );
86
+ }
87
+ default:
88
+ throw new Error(`MMKV: Type ${typeof newValue} is not supported!`);
89
+ }
90
+ },
91
+ [key, mmkv]
92
+ );
93
+
94
+ // update value if key or instance changes
95
+ useEffect(() => {
96
+ setValue(getter(mmkv, key));
97
+ }, [key, mmkv]);
98
+
99
+ // update value if it changes somewhere else (second hook, same key)
100
+ useEffect(() => {
101
+ const listener = mmkv.addOnValueChangedListener((changedKey) => {
102
+ if (changedKey === key) {
103
+ setValue(getter(mmkv, key));
104
+ }
105
+ });
106
+ return () => listener.remove();
107
+ }, [key, mmkv]);
108
+
109
+ return [value, set];
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Use the string value of the given `key` from the given MMKV storage instance.
115
+ *
116
+ * If no instance is provided, a shared default instance will be used.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * const [username, setUsername] = useMMKVString("user.name")
121
+ * ```
122
+ */
123
+ export const useMMKVString = createMMKVHook((instance, key) =>
124
+ instance.getString(key)
125
+ );
126
+
127
+ /**
128
+ * Use the number value of the given `key` from the given MMKV storage instance.
129
+ *
130
+ * If no instance is provided, a shared default instance will be used.
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * const [age, setAge] = useMMKVNumber("user.age")
135
+ * ```
136
+ */
137
+ export const useMMKVNumber = createMMKVHook((instance, key) =>
138
+ instance.getNumber(key)
139
+ );
140
+ /**
141
+ * Use the boolean value of the given `key` from the given MMKV storage instance.
142
+ *
143
+ * If no instance is provided, a shared default instance will be used.
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * const [isPremiumAccount, setIsPremiumAccount] = useMMKVBoolean("user.isPremium")
148
+ * ```
149
+ */
150
+ export const useMMKVBoolean = createMMKVHook((instance, key) =>
151
+ instance.getBoolean(key)
152
+ );
153
+ /**
154
+ * Use the buffer value (unsigned 8-bit (0-255)) of the given `key` from the given MMKV storage instance.
155
+ *
156
+ * If no instance is provided, a shared default instance will be used.
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * const [privateKey, setPrivateKey] = useMMKVBuffer("user.privateKey")
161
+ * ```
162
+ */
163
+ export const useMMKVBuffer = createMMKVHook((instance, key) =>
164
+ instance.getBuffer(key)
165
+ );
166
+ /**
167
+ * Use an object value of the given `key` from the given MMKV storage instance.
168
+ *
169
+ * If no instance is provided, a shared default instance will be used.
170
+ *
171
+ * The object will be serialized using `JSON`.
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * const [user, setUser] = useMMKVObject<User>("user")
176
+ * ```
177
+ */
178
+ export function useMMKVObject<T>(
179
+ key: string,
180
+ instance?: MMKV
181
+ ): [value: T | undefined, setValue: (value: T | undefined) => void] {
182
+ const [string, setString] = useMMKVString(key, instance);
183
+
184
+ const value = useMemo(() => {
185
+ if (string == null) return undefined;
186
+ return JSON.parse(string) as T;
187
+ }, [string]);
188
+ const setValue = useCallback(
189
+ (v: T | undefined) => {
190
+ if (v == null) {
191
+ // Clear the Value
192
+ setString(undefined);
193
+ } else {
194
+ // Store the Object as a serialized Value
195
+ setString(JSON.stringify(v));
196
+ }
197
+ },
198
+ [setString]
199
+ );
200
+
201
+ return [value, setValue];
202
+ }
203
+
204
+ /**
205
+ * Listen for changes in the given MMKV storage instance.
206
+ * If no instance is passed, the default instance will be used.
207
+ * @param valueChangedListener The function to call whenever a value inside the storage instance changes
208
+ * @param instance The instance to listen to changes to (or the default instance)
209
+ *
210
+ * @example
211
+ * ```ts
212
+ * useMMKVListener((key) => {
213
+ * console.log(`Value for "${key}" changed!`)
214
+ * })
215
+ * ```
216
+ */
217
+ export function useMMKVListener(
218
+ valueChangedListener: (key: string) => void,
219
+ instance?: MMKV
220
+ ): void {
221
+ const ref = useRef(valueChangedListener);
222
+ ref.current = valueChangedListener;
223
+
224
+ const mmkv = instance ?? getDefaultInstance();
225
+
226
+ useEffect(() => {
227
+ const listener = mmkv.addOnValueChangedListener((changedKey) => {
228
+ ref.current(changedKey);
229
+ });
230
+ return () => listener.remove();
231
+ }, [mmkv]);
232
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './MMKV';
2
+ export * from './hooks';