strata-storage 2.3.0 → 2.4.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.
Files changed (29) hide show
  1. package/android/src/main/java/com/strata/storage/EncryptedStorage.java +84 -21
  2. package/android/src/main/java/com/strata/storage/SQLiteStorage.java +140 -58
  3. package/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +66 -5
  4. package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +83 -10
  5. package/dist/adapters/web/MemoryAdapter.d.ts.map +1 -1
  6. package/dist/adapters/web/MemoryAdapter.js +7 -1
  7. package/dist/adapters/web/SessionStorageAdapter.d.ts.map +1 -1
  8. package/dist/adapters/web/SessionStorageAdapter.js +3 -2
  9. package/dist/android/src/main/java/com/strata/storage/EncryptedStorage.java +84 -21
  10. package/dist/android/src/main/java/com/strata/storage/SQLiteStorage.java +140 -58
  11. package/dist/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +66 -5
  12. package/dist/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +83 -10
  13. package/dist/core/Strata.d.ts.map +1 -1
  14. package/dist/core/Strata.js +4 -6
  15. package/dist/firebase.d.ts.map +1 -1
  16. package/dist/firebase.js +75 -13
  17. package/dist/ios/Plugin/SQLiteStorage.swift +100 -30
  18. package/dist/ios/Plugin/UserDefaultsStorage.swift +13 -2
  19. package/dist/package.json +5 -5
  20. package/dist/plugin/index.d.ts.map +1 -1
  21. package/dist/plugin/index.js +31 -6
  22. package/dist/plugin/web.d.ts +5 -1
  23. package/dist/plugin/web.d.ts.map +1 -1
  24. package/dist/plugin/web.js +55 -32
  25. package/dist/utils/index.d.ts.map +1 -1
  26. package/dist/utils/index.js +7 -2
  27. package/ios/Plugin/SQLiteStorage.swift +100 -30
  28. package/ios/Plugin/UserDefaultsStorage.swift +13 -2
  29. package/package.json +17 -19
@@ -3,42 +3,52 @@ package com.strata.storage;
3
3
  import android.content.Context;
4
4
  import android.content.SharedPreferences;
5
5
  import android.os.Build;
6
+ import android.util.Log;
6
7
  import androidx.security.crypto.EncryptedSharedPreferences;
7
8
  import androidx.security.crypto.MasterKey;
9
+ import java.security.GeneralSecurityException;
10
+ import java.io.IOException;
8
11
  import java.util.Set;
9
12
  import java.util.HashSet;
10
13
  import java.util.Map;
11
14
  import java.util.List;
12
15
  import java.util.ArrayList;
13
16
  import org.json.JSONObject;
17
+ import org.json.JSONArray;
14
18
 
15
19
  public class EncryptedStorage {
16
20
  private SharedPreferences encryptedPrefs;
17
21
  private SharedPreferences.Editor editor;
18
22
  private static final String DEFAULT_NAME = "StrataSecureStorage";
19
23
 
20
- public EncryptedStorage(Context context) throws Exception {
24
+ public EncryptedStorage(Context context) throws GeneralSecurityException, IOException {
21
25
  this(context, DEFAULT_NAME);
22
26
  }
23
-
24
- public EncryptedStorage(Context context, String name) throws Exception {
27
+
28
+ public EncryptedStorage(Context context, String name) throws GeneralSecurityException, IOException {
25
29
  String fileName = name != null ? name : DEFAULT_NAME;
26
-
30
+
27
31
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
28
- MasterKey masterKey = new MasterKey.Builder(context)
29
- .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
30
- .build();
31
-
32
- encryptedPrefs = EncryptedSharedPreferences.create(
33
- context,
34
- fileName,
35
- masterKey,
36
- EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
37
- EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
38
- );
39
-
40
- editor = encryptedPrefs.edit();
32
+ try {
33
+ MasterKey masterKey = new MasterKey.Builder(context)
34
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
35
+ .build();
36
+
37
+ encryptedPrefs = EncryptedSharedPreferences.create(
38
+ context,
39
+ fileName,
40
+ masterKey,
41
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
42
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
43
+ );
44
+
45
+ editor = encryptedPrefs.edit();
46
+ } catch (GeneralSecurityException | IOException e) {
47
+ Log.e("StrataStorage", "Failed to initialize encrypted storage", e);
48
+ throw e;
49
+ }
41
50
  } else {
51
+ Log.w("StrataStorage", "API < 23, using unencrypted SharedPreferences");
42
52
  // Fallback to regular SharedPreferences for older devices
43
53
  encryptedPrefs = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
44
54
  editor = encryptedPrefs.edit();
@@ -52,14 +62,22 @@ public class EncryptedStorage {
52
62
  stringValue = (String) value;
53
63
  } else {
54
64
  // Convert complex objects to JSON
55
- stringValue = value instanceof JSONObject ?
56
- ((JSONObject) value).toString() :
57
- new JSONObject(value).toString();
65
+ if (value instanceof JSONObject) {
66
+ stringValue = ((JSONObject) value).toString();
67
+ } else {
68
+ // Use helper method to convert object to JSON string
69
+ try {
70
+ stringValue = objectToJsonString(value);
71
+ } catch (Exception e) {
72
+ // Fallback to string representation
73
+ stringValue = value.toString();
74
+ }
75
+ }
58
76
  }
59
77
  editor.putString(key, stringValue);
60
78
  return editor.commit();
61
79
  } catch (Exception e) {
62
- e.printStackTrace();
80
+ Log.e("StrataStorage", "Failed to set value in encrypted storage", e);
63
81
  return false;
64
82
  }
65
83
  }
@@ -140,6 +158,51 @@ public class EncryptedStorage {
140
158
  return new SizeInfo(totalSize, count);
141
159
  }
142
160
 
161
+ /**
162
+ * Convert an object to JSON string using reflection
163
+ */
164
+ private String objectToJsonString(Object obj) throws Exception {
165
+ if (obj == null) {
166
+ return "null";
167
+ }
168
+
169
+ if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) {
170
+ return obj.toString();
171
+ }
172
+
173
+ if (obj instanceof Map) {
174
+ JSONObject jsonObj = new JSONObject();
175
+ Map<?, ?> map = (Map<?, ?>) obj;
176
+ for (Map.Entry<?, ?> entry : map.entrySet()) {
177
+ String key = entry.getKey().toString();
178
+ jsonObj.put(key, entry.getValue());
179
+ }
180
+ return jsonObj.toString();
181
+ }
182
+
183
+ if (obj instanceof List || obj.getClass().isArray()) {
184
+ JSONArray jsonArray = new JSONArray();
185
+ if (obj instanceof List) {
186
+ List<?> list = (List<?>) obj;
187
+ for (Object item : list) {
188
+ jsonArray.put(item);
189
+ }
190
+ } else {
191
+ Object[] array = (Object[]) obj;
192
+ for (Object item : array) {
193
+ jsonArray.put(item);
194
+ }
195
+ }
196
+ return jsonArray.toString();
197
+ }
198
+
199
+ // For other objects, create a simple JSON object with their string representation
200
+ JSONObject jsonObj = new JSONObject();
201
+ jsonObj.put("value", obj.toString());
202
+ jsonObj.put("type", obj.getClass().getSimpleName());
203
+ return jsonObj.toString();
204
+ }
205
+
143
206
  /**
144
207
  * Size information class
145
208
  */
@@ -5,11 +5,13 @@ import android.database.Cursor;
5
5
  import android.database.sqlite.SQLiteDatabase;
6
6
  import android.database.sqlite.SQLiteOpenHelper;
7
7
  import android.content.ContentValues;
8
+ import android.util.Log;
8
9
  import java.util.ArrayList;
9
10
  import java.util.List;
10
11
  import java.util.HashMap;
11
12
  import java.util.Map;
12
13
  import org.json.JSONObject;
14
+ import org.json.JSONArray;
13
15
  import java.nio.charset.StandardCharsets;
14
16
 
15
17
  public class SQLiteStorage extends SQLiteOpenHelper {
@@ -62,13 +64,22 @@ public class SQLiteStorage extends SQLiteOpenHelper {
62
64
  valueBytes = ((String) value).getBytes(StandardCharsets.UTF_8);
63
65
  } else {
64
66
  // Convert complex objects to JSON then to bytes
65
- String json = value instanceof JSONObject ?
66
- ((JSONObject) value).toString() :
67
- new JSONObject(value).toString();
67
+ String json;
68
+ if (value instanceof JSONObject) {
69
+ json = ((JSONObject) value).toString();
70
+ } else {
71
+ // Use helper method to convert object to JSON string
72
+ try {
73
+ json = objectToJsonString(value);
74
+ } catch (Exception jsonEx) {
75
+ // Fallback to string representation
76
+ json = value.toString();
77
+ }
78
+ }
68
79
  valueBytes = json.getBytes(StandardCharsets.UTF_8);
69
80
  }
70
81
  } catch (Exception e) {
71
- e.printStackTrace();
82
+ Log.e("StrataStorage", "Failed to set value in SQLite", e);
72
83
  return false;
73
84
  }
74
85
  SQLiteDatabase db = this.getWritableDatabase();
@@ -102,36 +113,41 @@ public class SQLiteStorage extends SQLiteOpenHelper {
102
113
 
103
114
  public Map<String, Object> get(String key) {
104
115
  SQLiteDatabase db = this.getReadableDatabase();
105
- Cursor cursor = db.query(TABLE_NAME, null, KEY_ID + "=?",
106
- new String[]{key}, null, null, null, null);
107
-
108
- Map<String, Object> result = null;
109
- if (cursor != null && cursor.moveToFirst()) {
110
- result = new HashMap<>();
111
- result.put("key", key);
112
- result.put("value", cursor.getBlob(cursor.getColumnIndex(KEY_VALUE)));
113
- result.put("created", cursor.getLong(cursor.getColumnIndex(KEY_CREATED)));
114
- result.put("updated", cursor.getLong(cursor.getColumnIndex(KEY_UPDATED)));
115
-
116
- int expiresIndex = cursor.getColumnIndex(KEY_EXPIRES);
117
- if (!cursor.isNull(expiresIndex)) {
118
- result.put("expires", cursor.getLong(expiresIndex));
119
- }
120
-
121
- int tagsIndex = cursor.getColumnIndex(KEY_TAGS);
122
- if (!cursor.isNull(tagsIndex)) {
123
- result.put("tags", cursor.getString(tagsIndex));
116
+ Cursor cursor = null;
117
+ try {
118
+ cursor = db.query(TABLE_NAME, null, KEY_ID + "=?",
119
+ new String[]{key}, null, null, null, null);
120
+
121
+ Map<String, Object> result = null;
122
+ if (cursor != null && cursor.moveToFirst()) {
123
+ result = new HashMap<>();
124
+ result.put("key", key);
125
+ result.put("value", cursor.getBlob(cursor.getColumnIndex(KEY_VALUE)));
126
+ result.put("created", cursor.getLong(cursor.getColumnIndex(KEY_CREATED)));
127
+ result.put("updated", cursor.getLong(cursor.getColumnIndex(KEY_UPDATED)));
128
+
129
+ int expiresIndex = cursor.getColumnIndex(KEY_EXPIRES);
130
+ if (!cursor.isNull(expiresIndex)) {
131
+ result.put("expires", cursor.getLong(expiresIndex));
132
+ }
133
+
134
+ int tagsIndex = cursor.getColumnIndex(KEY_TAGS);
135
+ if (!cursor.isNull(tagsIndex)) {
136
+ result.put("tags", cursor.getString(tagsIndex));
137
+ }
138
+
139
+ int metadataIndex = cursor.getColumnIndex(KEY_METADATA);
140
+ if (!cursor.isNull(metadataIndex)) {
141
+ result.put("metadata", cursor.getString(metadataIndex));
142
+ }
124
143
  }
125
-
126
- int metadataIndex = cursor.getColumnIndex(KEY_METADATA);
127
- if (!cursor.isNull(metadataIndex)) {
128
- result.put("metadata", cursor.getString(metadataIndex));
144
+ return result;
145
+ } finally {
146
+ if (cursor != null) {
147
+ cursor.close();
129
148
  }
130
-
131
- cursor.close();
149
+ db.close();
132
150
  }
133
- db.close();
134
- return result;
135
151
  }
136
152
 
137
153
  public boolean remove(String key) {
@@ -169,52 +185,118 @@ public class SQLiteStorage extends SQLiteOpenHelper {
169
185
  List<String> keys = new ArrayList<>();
170
186
  String selectQuery;
171
187
  String[] selectionArgs = null;
172
-
188
+
173
189
  if (pattern != null) {
174
190
  selectQuery = "SELECT " + KEY_ID + " FROM " + TABLE_NAME + " WHERE " + KEY_ID + " LIKE ?";
175
- selectionArgs = new String[]{"% " + pattern + "%"};
191
+ selectionArgs = new String[]{"%" + pattern + "%"};
176
192
  } else {
177
193
  selectQuery = "SELECT " + KEY_ID + " FROM " + TABLE_NAME;
178
194
  }
179
-
195
+
180
196
  SQLiteDatabase db = this.getReadableDatabase();
181
- Cursor cursor = db.rawQuery(selectQuery, selectionArgs);
182
-
183
- if (cursor.moveToFirst()) {
184
- do {
185
- keys.add(cursor.getString(0));
186
- } while (cursor.moveToNext());
197
+ Cursor cursor = null;
198
+ try {
199
+ cursor = db.rawQuery(selectQuery, selectionArgs);
200
+
201
+ if (cursor != null && cursor.moveToFirst()) {
202
+ do {
203
+ keys.add(cursor.getString(0));
204
+ } while (cursor.moveToNext());
205
+ }
206
+ return keys;
207
+ } finally {
208
+ if (cursor != null) {
209
+ cursor.close();
210
+ }
211
+ db.close();
187
212
  }
188
- cursor.close();
189
- db.close();
190
- return keys;
191
213
  }
192
214
 
193
215
  public boolean has(String key) {
194
216
  SQLiteDatabase db = this.getReadableDatabase();
195
- Cursor cursor = db.query(TABLE_NAME, new String[]{KEY_ID}, KEY_ID + "=?",
196
- new String[]{key}, null, null, null, null);
197
- boolean exists = cursor.getCount() > 0;
198
- cursor.close();
199
- db.close();
200
- return exists;
217
+ Cursor cursor = null;
218
+ try {
219
+ cursor = db.query(TABLE_NAME, new String[]{KEY_ID}, KEY_ID + "=?",
220
+ new String[]{key}, null, null, null, null);
221
+ boolean exists = cursor != null && cursor.getCount() > 0;
222
+ return exists;
223
+ } finally {
224
+ if (cursor != null) {
225
+ cursor.close();
226
+ }
227
+ db.close();
228
+ }
201
229
  }
202
230
 
203
231
  public SizeInfo size() {
204
232
  SQLiteDatabase db = this.getReadableDatabase();
205
- Cursor cursor = db.rawQuery("SELECT COUNT(*), SUM(LENGTH(" + KEY_VALUE + ")) FROM " + TABLE_NAME, null);
233
+ Cursor cursor = null;
234
+ try {
235
+ cursor = db.rawQuery("SELECT COUNT(*), SUM(LENGTH(" + KEY_VALUE + ")) FROM " + TABLE_NAME, null);
236
+
237
+ long totalSize = 0;
238
+ int count = 0;
239
+
240
+ if (cursor != null && cursor.moveToFirst()) {
241
+ count = cursor.getInt(0);
242
+ // Check for NULL values from SQL SUM function
243
+ if (!cursor.isNull(1)) {
244
+ totalSize = cursor.getLong(1);
245
+ }
246
+ }
247
+
248
+ return new SizeInfo(totalSize, count);
249
+ } finally {
250
+ if (cursor != null) {
251
+ cursor.close();
252
+ }
253
+ db.close();
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Convert an object to JSON string using reflection
259
+ */
260
+ private String objectToJsonString(Object obj) throws Exception {
261
+ if (obj == null) {
262
+ return "null";
263
+ }
264
+
265
+ if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) {
266
+ return obj.toString();
267
+ }
206
268
 
207
- long totalSize = 0;
208
- int count = 0;
269
+ if (obj instanceof Map) {
270
+ JSONObject jsonObj = new JSONObject();
271
+ Map<?, ?> map = (Map<?, ?>) obj;
272
+ for (Map.Entry<?, ?> entry : map.entrySet()) {
273
+ String key = entry.getKey().toString();
274
+ jsonObj.put(key, entry.getValue());
275
+ }
276
+ return jsonObj.toString();
277
+ }
209
278
 
210
- if (cursor != null && cursor.moveToFirst()) {
211
- count = cursor.getInt(0);
212
- totalSize = cursor.getLong(1);
213
- cursor.close();
279
+ if (obj instanceof List || obj.getClass().isArray()) {
280
+ JSONArray jsonArray = new JSONArray();
281
+ if (obj instanceof List) {
282
+ List<?> list = (List<?>) obj;
283
+ for (Object item : list) {
284
+ jsonArray.put(item);
285
+ }
286
+ } else {
287
+ Object[] array = (Object[]) obj;
288
+ for (Object item : array) {
289
+ jsonArray.put(item);
290
+ }
291
+ }
292
+ return jsonArray.toString();
214
293
  }
215
294
 
216
- db.close();
217
- return new SizeInfo(totalSize, count);
295
+ // For other objects, create a simple JSON object with their string representation
296
+ JSONObject jsonObj = new JSONObject();
297
+ jsonObj.put("value", obj.toString());
298
+ jsonObj.put("type", obj.getClass().getSimpleName());
299
+ return jsonObj.toString();
218
300
  }
219
301
 
220
302
  /**
@@ -2,6 +2,7 @@ package com.strata.storage;
2
2
 
3
3
  import android.content.Context;
4
4
  import android.content.SharedPreferences;
5
+ import android.util.Log;
5
6
  import java.util.Map;
6
7
  import java.util.Set;
7
8
  import java.util.HashSet;
@@ -37,17 +38,32 @@ public class SharedPreferencesStorage {
37
38
  } else if (value instanceof Boolean) {
38
39
  editor.putBoolean(key, (Boolean) value);
39
40
  } else if (value instanceof Set) {
40
- editor.putStringSet(key, (Set<String>) value);
41
+ // Safely convert to Set<String>
42
+ Set<?> rawSet = (Set<?>) value;
43
+ Set<String> stringSet = new HashSet<>();
44
+ for (Object item : rawSet) {
45
+ stringSet.add(item != null ? item.toString() : "null");
46
+ }
47
+ editor.putStringSet(key, stringSet);
41
48
  } else {
42
49
  // Convert complex objects to JSON
43
- String json = value instanceof JSONObject ?
44
- ((JSONObject) value).toString() :
45
- new JSONObject(value).toString();
50
+ String json;
51
+ if (value instanceof JSONObject) {
52
+ json = ((JSONObject) value).toString();
53
+ } else {
54
+ // Use reflection to convert object to JSON string
55
+ try {
56
+ json = objectToJsonString(value);
57
+ } catch (Exception e) {
58
+ // Fallback to string representation
59
+ json = value.toString();
60
+ }
61
+ }
46
62
  editor.putString(key, json);
47
63
  }
48
64
  return editor.commit();
49
65
  } catch (Exception e) {
50
- e.printStackTrace();
66
+ Log.e("StrataStorage", "Failed to set value in SharedPreferences", e);
51
67
  return false;
52
68
  }
53
69
  }
@@ -133,6 +149,51 @@ public class SharedPreferencesStorage {
133
149
  return new SizeInfo(totalSize, count);
134
150
  }
135
151
 
152
+ /**
153
+ * Convert an object to JSON string using reflection
154
+ */
155
+ private String objectToJsonString(Object obj) throws Exception {
156
+ if (obj == null) {
157
+ return "null";
158
+ }
159
+
160
+ if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) {
161
+ return obj.toString();
162
+ }
163
+
164
+ if (obj instanceof Map) {
165
+ JSONObject jsonObj = new JSONObject();
166
+ Map<?, ?> map = (Map<?, ?>) obj;
167
+ for (Map.Entry<?, ?> entry : map.entrySet()) {
168
+ String key = entry.getKey().toString();
169
+ jsonObj.put(key, entry.getValue());
170
+ }
171
+ return jsonObj.toString();
172
+ }
173
+
174
+ if (obj instanceof List || obj.getClass().isArray()) {
175
+ JSONArray jsonArray = new JSONArray();
176
+ if (obj instanceof List) {
177
+ List<?> list = (List<?>) obj;
178
+ for (Object item : list) {
179
+ jsonArray.put(item);
180
+ }
181
+ } else {
182
+ Object[] array = (Object[]) obj;
183
+ for (Object item : array) {
184
+ jsonArray.put(item);
185
+ }
186
+ }
187
+ return jsonArray.toString();
188
+ }
189
+
190
+ // For other objects, create a simple JSON object with their string representation
191
+ JSONObject jsonObj = new JSONObject();
192
+ jsonObj.put("value", obj.toString());
193
+ jsonObj.put("type", obj.getClass().getSimpleName());
194
+ return jsonObj.toString();
195
+ }
196
+
136
197
  /**
137
198
  * Size information class
138
199
  */