@onekeyfe/react-native-async-storage 3.0.7 → 3.0.8

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.
@@ -1,132 +1,368 @@
1
1
  package com.asyncstorage
2
2
 
3
- import android.content.Context
4
- import android.content.SharedPreferences
3
+ import android.database.Cursor
4
+ import android.database.sqlite.SQLiteStatement
5
+ import com.facebook.react.bridge.Arguments
5
6
  import com.facebook.react.bridge.Promise
6
7
  import com.facebook.react.bridge.ReactApplicationContext
7
8
  import com.facebook.react.bridge.ReadableArray
8
- import com.facebook.react.bridge.WritableNativeArray
9
+ import com.facebook.react.bridge.WritableArray
9
10
  import com.facebook.react.module.annotations.ReactModule
11
+ import org.json.JSONObject
12
+ import java.util.concurrent.Executor
13
+ import java.util.concurrent.Executors
10
14
 
15
+ /**
16
+ * Ported from upstream @react-native-async-storage/async-storage AsyncStorageModule.java
17
+ * Adapted to use Promise (TurboModule) instead of Callback.
18
+ * Uses SQLite via ReactDatabaseSupplier (same as upstream).
19
+ */
11
20
  @ReactModule(name = RNCAsyncStorageModule.NAME)
12
21
  class RNCAsyncStorageModule(reactContext: ReactApplicationContext) :
13
22
  NativeRNCAsyncStorageSpec(reactContext) {
14
23
 
15
24
  companion object {
16
25
  const val NAME = "RNCAsyncStorage"
17
- private const val PREFS_NAME = "RNCAsyncStorage"
26
+ // SQL variable number limit, defined by SQLITE_LIMIT_VARIABLE_NUMBER
27
+ private const val MAX_SQL_KEYS = 999
18
28
  }
19
29
 
20
- private fun getPrefs(): SharedPreferences {
21
- return reactApplicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
22
- }
30
+ private val dbSupplier: ReactDatabaseSupplier = ReactDatabaseSupplier.getInstance(reactContext)
31
+ private val executor: Executor = SerialExecutor(Executors.newSingleThreadExecutor())
32
+ @Volatile
33
+ private var shuttingDown = false
23
34
 
24
35
  override fun getName(): String = NAME
25
36
 
37
+ override fun initialize() {
38
+ super.initialize()
39
+ shuttingDown = false
40
+ }
41
+
42
+ override fun invalidate() {
43
+ shuttingDown = true
44
+ dbSupplier.closeDatabase()
45
+ }
46
+
26
47
  override fun multiGet(keys: ReadableArray, promise: Promise) {
27
- Thread {
48
+ executor.execute {
28
49
  try {
29
- val prefs = getPrefs()
30
- val result = WritableNativeArray()
31
- for (i in 0 until keys.size()) {
32
- val key = keys.getString(i)
33
- val pair = WritableNativeArray()
34
- pair.pushString(key)
35
- if (prefs.contains(key)) {
36
- pair.pushString(prefs.getString(key, null))
37
- } else {
38
- pair.pushNull()
50
+ if (!ensureDatabase()) {
51
+ promise.reject("ASYNC_STORAGE_ERROR", "Database Error")
52
+ return@execute
53
+ }
54
+
55
+ val columns = arrayOf(ReactDatabaseSupplier.KEY_COLUMN, ReactDatabaseSupplier.VALUE_COLUMN)
56
+ val keysRemaining = HashSet<String>()
57
+ val data: WritableArray = Arguments.createArray()
58
+
59
+ var keyStart = 0
60
+ while (keyStart < keys.size()) {
61
+ val keyCount = Math.min(keys.size() - keyStart, MAX_SQL_KEYS)
62
+ val cursor: Cursor = dbSupplier.get().query(
63
+ ReactDatabaseSupplier.TABLE_CATALYST,
64
+ columns,
65
+ buildKeySelection(keyCount),
66
+ buildKeySelectionArgs(keys, keyStart, keyCount),
67
+ null, null, null
68
+ )
69
+
70
+ keysRemaining.clear()
71
+ try {
72
+ if (cursor.count != keys.size()) {
73
+ for (keyIndex in keyStart until keyStart + keyCount) {
74
+ keysRemaining.add(keys.getString(keyIndex))
75
+ }
76
+ }
77
+ if (cursor.moveToFirst()) {
78
+ do {
79
+ val row = Arguments.createArray()
80
+ row.pushString(cursor.getString(0))
81
+ row.pushString(cursor.getString(1))
82
+ data.pushArray(row)
83
+ keysRemaining.remove(cursor.getString(0))
84
+ } while (cursor.moveToNext())
85
+ }
86
+ } finally {
87
+ cursor.close()
39
88
  }
40
- result.pushArray(pair)
89
+
90
+ for (key in keysRemaining) {
91
+ val row = Arguments.createArray()
92
+ row.pushString(key)
93
+ row.pushNull()
94
+ data.pushArray(row)
95
+ }
96
+ keysRemaining.clear()
97
+ keyStart += MAX_SQL_KEYS
41
98
  }
42
- promise.resolve(result)
99
+
100
+ promise.resolve(data)
43
101
  } catch (e: Exception) {
44
102
  promise.reject("ASYNC_STORAGE_ERROR", e.message, e)
45
103
  }
46
- }.start()
104
+ }
47
105
  }
48
106
 
49
107
  override fun multiSet(keyValuePairs: ReadableArray, promise: Promise) {
50
- Thread {
108
+ if (keyValuePairs.size() == 0) {
109
+ promise.resolve(null)
110
+ return
111
+ }
112
+
113
+ executor.execute {
51
114
  try {
52
- val editor = getPrefs().edit()
53
- for (i in 0 until keyValuePairs.size()) {
54
- val pair = keyValuePairs.getArray(i)
55
- if (pair != null && pair.size() >= 2) {
115
+ if (!ensureDatabase()) {
116
+ promise.reject("ASYNC_STORAGE_ERROR", "Database Error")
117
+ return@execute
118
+ }
119
+
120
+ val sql = "INSERT OR REPLACE INTO ${ReactDatabaseSupplier.TABLE_CATALYST} VALUES (?, ?);"
121
+ val statement: SQLiteStatement = dbSupplier.get().compileStatement(sql)
122
+ try {
123
+ dbSupplier.get().beginTransaction()
124
+ for (idx in 0 until keyValuePairs.size()) {
125
+ val pair = keyValuePairs.getArray(idx)
126
+ if (pair == null || pair.size() != 2) {
127
+ promise.reject("ASYNC_STORAGE_ERROR", "Invalid Value")
128
+ return@execute
129
+ }
56
130
  val key = pair.getString(0)
57
131
  val value = pair.getString(1)
58
- editor.putString(key, value)
132
+ if (key == null) {
133
+ promise.reject("ASYNC_STORAGE_ERROR", "Invalid key")
134
+ return@execute
135
+ }
136
+ if (value == null) {
137
+ promise.reject("ASYNC_STORAGE_ERROR", "Invalid Value")
138
+ return@execute
139
+ }
140
+ statement.clearBindings()
141
+ statement.bindString(1, key)
142
+ statement.bindString(2, value)
143
+ statement.execute()
144
+ }
145
+ dbSupplier.get().setTransactionSuccessful()
146
+ } finally {
147
+ try {
148
+ dbSupplier.get().endTransaction()
149
+ } catch (e: Exception) {
150
+ promise.reject("ASYNC_STORAGE_ERROR", e.message, e)
151
+ return@execute
59
152
  }
60
153
  }
61
- editor.apply()
62
154
  promise.resolve(null)
63
155
  } catch (e: Exception) {
64
156
  promise.reject("ASYNC_STORAGE_ERROR", e.message, e)
65
157
  }
66
- }.start()
158
+ }
67
159
  }
68
160
 
69
161
  override fun multiRemove(keys: ReadableArray, promise: Promise) {
70
- Thread {
162
+ if (keys.size() == 0) {
163
+ promise.resolve(null)
164
+ return
165
+ }
166
+
167
+ executor.execute {
71
168
  try {
72
- val editor = getPrefs().edit()
73
- for (i in 0 until keys.size()) {
74
- editor.remove(keys.getString(i))
169
+ if (!ensureDatabase()) {
170
+ promise.reject("ASYNC_STORAGE_ERROR", "Database Error")
171
+ return@execute
172
+ }
173
+
174
+ try {
175
+ dbSupplier.get().beginTransaction()
176
+ var keyStart = 0
177
+ while (keyStart < keys.size()) {
178
+ val keyCount = Math.min(keys.size() - keyStart, MAX_SQL_KEYS)
179
+ dbSupplier.get().delete(
180
+ ReactDatabaseSupplier.TABLE_CATALYST,
181
+ buildKeySelection(keyCount),
182
+ buildKeySelectionArgs(keys, keyStart, keyCount)
183
+ )
184
+ keyStart += MAX_SQL_KEYS
185
+ }
186
+ dbSupplier.get().setTransactionSuccessful()
187
+ } finally {
188
+ try {
189
+ dbSupplier.get().endTransaction()
190
+ } catch (e: Exception) {
191
+ promise.reject("ASYNC_STORAGE_ERROR", e.message, e)
192
+ return@execute
193
+ }
75
194
  }
76
- editor.apply()
77
195
  promise.resolve(null)
78
196
  } catch (e: Exception) {
79
197
  promise.reject("ASYNC_STORAGE_ERROR", e.message, e)
80
198
  }
81
- }.start()
199
+ }
82
200
  }
83
201
 
84
202
  override fun multiMerge(keyValuePairs: ReadableArray, promise: Promise) {
85
- Thread {
203
+ executor.execute {
86
204
  try {
87
- val prefs = getPrefs()
88
- val editor = prefs.edit()
89
- for (i in 0 until keyValuePairs.size()) {
90
- val pair = keyValuePairs.getArray(i)
91
- if (pair != null && pair.size() >= 2) {
205
+ if (!ensureDatabase()) {
206
+ promise.reject("ASYNC_STORAGE_ERROR", "Database Error")
207
+ return@execute
208
+ }
209
+
210
+ try {
211
+ dbSupplier.get().beginTransaction()
212
+ for (idx in 0 until keyValuePairs.size()) {
213
+ val pair = keyValuePairs.getArray(idx)
214
+ if (pair == null || pair.size() != 2) {
215
+ promise.reject("ASYNC_STORAGE_ERROR", "Invalid Value")
216
+ return@execute
217
+ }
92
218
  val key = pair.getString(0)
93
219
  val value = pair.getString(1)
94
- // For merge, if key exists and both are JSON objects, deep merge.
95
- // For simplicity, we overwrite (shallow merge by replacing value).
96
- editor.putString(key, value)
220
+ if (key == null) {
221
+ promise.reject("ASYNC_STORAGE_ERROR", "Invalid key")
222
+ return@execute
223
+ }
224
+ if (value == null) {
225
+ promise.reject("ASYNC_STORAGE_ERROR", "Invalid Value")
226
+ return@execute
227
+ }
228
+ if (!mergeImpl(key, value)) {
229
+ promise.reject("ASYNC_STORAGE_ERROR", "Database Error")
230
+ return@execute
231
+ }
232
+ }
233
+ dbSupplier.get().setTransactionSuccessful()
234
+ } finally {
235
+ try {
236
+ dbSupplier.get().endTransaction()
237
+ } catch (e: Exception) {
238
+ promise.reject("ASYNC_STORAGE_ERROR", e.message, e)
239
+ return@execute
97
240
  }
98
241
  }
99
- editor.apply()
100
242
  promise.resolve(null)
101
243
  } catch (e: Exception) {
102
244
  promise.reject("ASYNC_STORAGE_ERROR", e.message, e)
103
245
  }
104
- }.start()
246
+ }
105
247
  }
106
248
 
107
249
  override fun getAllKeys(promise: Promise) {
108
- Thread {
250
+ executor.execute {
109
251
  try {
110
- val prefs = getPrefs()
111
- val result = WritableNativeArray()
112
- for (key in prefs.all.keys) {
113
- result.pushString(key)
252
+ if (!ensureDatabase()) {
253
+ promise.reject("ASYNC_STORAGE_ERROR", "Database Error")
254
+ return@execute
255
+ }
256
+
257
+ val columns = arrayOf(ReactDatabaseSupplier.KEY_COLUMN)
258
+ val cursor: Cursor = dbSupplier.get().query(
259
+ ReactDatabaseSupplier.TABLE_CATALYST,
260
+ columns, null, null, null, null, null
261
+ )
262
+
263
+ val data: WritableArray = Arguments.createArray()
264
+ try {
265
+ if (cursor.moveToFirst()) {
266
+ do {
267
+ data.pushString(cursor.getString(0))
268
+ } while (cursor.moveToNext())
269
+ }
270
+ } finally {
271
+ cursor.close()
114
272
  }
115
- promise.resolve(result)
273
+ promise.resolve(data)
116
274
  } catch (e: Exception) {
117
275
  promise.reject("ASYNC_STORAGE_ERROR", e.message, e)
118
276
  }
119
- }.start()
277
+ }
120
278
  }
121
279
 
122
280
  override fun clear(promise: Promise) {
123
- Thread {
281
+ executor.execute {
124
282
  try {
125
- getPrefs().edit().clear().apply()
283
+ if (!dbSupplier.ensureDatabase()) {
284
+ promise.reject("ASYNC_STORAGE_ERROR", "Database Error")
285
+ return@execute
286
+ }
287
+ dbSupplier.clear()
126
288
  promise.resolve(null)
127
289
  } catch (e: Exception) {
128
290
  promise.reject("ASYNC_STORAGE_ERROR", e.message, e)
129
291
  }
130
- }.start()
292
+ }
293
+ }
294
+
295
+ // --- Private helpers (ported from AsyncLocalStorageUtil.java) ---
296
+
297
+ private fun ensureDatabase(): Boolean {
298
+ return !shuttingDown && dbSupplier.ensureDatabase()
299
+ }
300
+
301
+ private fun buildKeySelection(count: Int): String {
302
+ val list = Array(count) { "?" }
303
+ return "${ReactDatabaseSupplier.KEY_COLUMN} IN (${list.joinToString(", ")})"
304
+ }
305
+
306
+ private fun buildKeySelectionArgs(keys: ReadableArray, start: Int, count: Int): Array<String> {
307
+ return Array(count) { keys.getString(start + it) }
308
+ }
309
+
310
+ private fun getItemImpl(key: String): String? {
311
+ val columns = arrayOf(ReactDatabaseSupplier.VALUE_COLUMN)
312
+ val selectionArgs = arrayOf(key)
313
+ val cursor = dbSupplier.get().query(
314
+ ReactDatabaseSupplier.TABLE_CATALYST,
315
+ columns,
316
+ "${ReactDatabaseSupplier.KEY_COLUMN}=?",
317
+ selectionArgs,
318
+ null, null, null
319
+ )
320
+ try {
321
+ return if (!cursor.moveToFirst()) null else cursor.getString(0)
322
+ } finally {
323
+ cursor.close()
324
+ }
325
+ }
326
+
327
+ private fun setItemImpl(key: String, value: String): Boolean {
328
+ val contentValues = android.content.ContentValues()
329
+ contentValues.put(ReactDatabaseSupplier.KEY_COLUMN, key)
330
+ contentValues.put(ReactDatabaseSupplier.VALUE_COLUMN, value)
331
+ val inserted = dbSupplier.get().insertWithOnConflict(
332
+ ReactDatabaseSupplier.TABLE_CATALYST,
333
+ null,
334
+ contentValues,
335
+ android.database.sqlite.SQLiteDatabase.CONFLICT_REPLACE
336
+ )
337
+ return inserted != -1L
338
+ }
339
+
340
+ private fun mergeImpl(key: String, value: String): Boolean {
341
+ val oldValue = getItemImpl(key)
342
+ val newValue: String
343
+ if (oldValue == null) {
344
+ newValue = value
345
+ } else {
346
+ val oldJSON = JSONObject(oldValue)
347
+ val newJSON = JSONObject(value)
348
+ deepMergeInto(oldJSON, newJSON)
349
+ newValue = oldJSON.toString()
350
+ }
351
+ return setItemImpl(key, newValue)
352
+ }
353
+
354
+ private fun deepMergeInto(oldJSON: JSONObject, newJSON: JSONObject) {
355
+ val keys = newJSON.keys()
356
+ while (keys.hasNext()) {
357
+ val key = keys.next()
358
+ val newJSONObject = newJSON.optJSONObject(key)
359
+ val oldJSONObject = oldJSON.optJSONObject(key)
360
+ if (newJSONObject != null && oldJSONObject != null) {
361
+ deepMergeInto(oldJSONObject, newJSONObject)
362
+ oldJSON.put(key, oldJSONObject)
363
+ } else {
364
+ oldJSON.put(key, newJSON.get(key))
365
+ }
366
+ }
131
367
  }
132
368
  }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ package com.asyncstorage;
9
+
10
+ import android.content.Context;
11
+ import android.database.sqlite.SQLiteDatabase;
12
+ import android.database.sqlite.SQLiteException;
13
+ import android.database.sqlite.SQLiteOpenHelper;
14
+ import android.util.Log;
15
+ import javax.annotation.Nullable;
16
+
17
+ /**
18
+ * Database supplier of the database used by react native async storage.
19
+ * Ported from upstream @react-native-async-storage/async-storage ReactDatabaseSupplier.java.
20
+ */
21
+ public class ReactDatabaseSupplier extends SQLiteOpenHelper {
22
+
23
+ public static final String DATABASE_NAME = "RKStorage";
24
+
25
+ private static final int DATABASE_VERSION = 1;
26
+ private static final int SLEEP_TIME_MS = 30;
27
+
28
+ static final String TABLE_CATALYST = "catalystLocalStorage";
29
+ static final String KEY_COLUMN = "key";
30
+ static final String VALUE_COLUMN = "value";
31
+
32
+ static final String VERSION_TABLE_CREATE =
33
+ "CREATE TABLE " + TABLE_CATALYST + " (" +
34
+ KEY_COLUMN + " TEXT PRIMARY KEY, " +
35
+ VALUE_COLUMN + " TEXT NOT NULL" +
36
+ ")";
37
+
38
+ private static @Nullable ReactDatabaseSupplier sReactDatabaseSupplierInstance;
39
+
40
+ private Context mContext;
41
+ private @Nullable SQLiteDatabase mDb;
42
+ private long mMaximumDatabaseSize = 6L * 1024L * 1024L;
43
+
44
+ private ReactDatabaseSupplier(Context context) {
45
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
46
+ mContext = context;
47
+ }
48
+
49
+ public static ReactDatabaseSupplier getInstance(Context context) {
50
+ if (sReactDatabaseSupplierInstance == null) {
51
+ sReactDatabaseSupplierInstance = new ReactDatabaseSupplier(context.getApplicationContext());
52
+ }
53
+ return sReactDatabaseSupplierInstance;
54
+ }
55
+
56
+ @Override
57
+ public void onCreate(SQLiteDatabase db) {
58
+ db.execSQL(VERSION_TABLE_CREATE);
59
+ }
60
+
61
+ @Override
62
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
63
+ if (oldVersion != newVersion) {
64
+ deleteDatabase();
65
+ onCreate(db);
66
+ }
67
+ }
68
+
69
+ synchronized boolean ensureDatabase() {
70
+ if (mDb != null && mDb.isOpen()) {
71
+ return true;
72
+ }
73
+ SQLiteException lastSQLiteException = null;
74
+ for (int tries = 0; tries < 2; tries++) {
75
+ try {
76
+ if (tries > 0) {
77
+ deleteDatabase();
78
+ }
79
+ mDb = getWritableDatabase();
80
+ break;
81
+ } catch (SQLiteException e) {
82
+ lastSQLiteException = e;
83
+ }
84
+ try {
85
+ Thread.sleep(SLEEP_TIME_MS);
86
+ } catch (InterruptedException ie) {
87
+ Thread.currentThread().interrupt();
88
+ }
89
+ }
90
+ if (mDb == null) {
91
+ throw lastSQLiteException;
92
+ }
93
+ mDb.setMaximumSize(mMaximumDatabaseSize);
94
+ return true;
95
+ }
96
+
97
+ public synchronized SQLiteDatabase get() {
98
+ ensureDatabase();
99
+ return mDb;
100
+ }
101
+
102
+ public synchronized void clearAndCloseDatabase() throws RuntimeException {
103
+ try {
104
+ clear();
105
+ closeDatabase();
106
+ } catch (Exception e) {
107
+ if (deleteDatabase()) {
108
+ return;
109
+ }
110
+ throw new RuntimeException("Clearing and deleting database " + DATABASE_NAME + " failed");
111
+ }
112
+ }
113
+
114
+ synchronized void clear() {
115
+ get().delete(TABLE_CATALYST, null, null);
116
+ }
117
+
118
+ public synchronized void setMaximumSize(long size) {
119
+ mMaximumDatabaseSize = size;
120
+ if (mDb != null) {
121
+ mDb.setMaximumSize(mMaximumDatabaseSize);
122
+ }
123
+ }
124
+
125
+ private synchronized boolean deleteDatabase() {
126
+ closeDatabase();
127
+ return mContext.deleteDatabase(DATABASE_NAME);
128
+ }
129
+
130
+ public synchronized void closeDatabase() {
131
+ if (mDb != null && mDb.isOpen()) {
132
+ mDb.close();
133
+ mDb = null;
134
+ }
135
+ }
136
+
137
+ public static void deleteInstance() {
138
+ sReactDatabaseSupplierInstance = null;
139
+ }
140
+ }
@@ -0,0 +1,38 @@
1
+ package com.asyncstorage;
2
+
3
+ import java.util.ArrayDeque;
4
+ import java.util.concurrent.Executor;
5
+
6
+ /**
7
+ * Ported from upstream @react-native-async-storage/async-storage SerialExecutor.java.
8
+ */
9
+ public class SerialExecutor implements Executor {
10
+ private final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
11
+ private Runnable mActive;
12
+ private final Executor executor;
13
+
14
+ public SerialExecutor(Executor executor) {
15
+ this.executor = executor;
16
+ }
17
+
18
+ public synchronized void execute(final Runnable r) {
19
+ mTasks.offer(new Runnable() {
20
+ public void run() {
21
+ try {
22
+ r.run();
23
+ } finally {
24
+ scheduleNext();
25
+ }
26
+ }
27
+ });
28
+ if (mActive == null) {
29
+ scheduleNext();
30
+ }
31
+ }
32
+
33
+ synchronized void scheduleNext() {
34
+ if ((mActive = mTasks.poll()) != null) {
35
+ executor.execute(mActive);
36
+ }
37
+ }
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-async-storage",
3
- "version": "3.0.7",
3
+ "version": "3.0.8",
4
4
  "description": "react-native-async-storage",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",