strata-storage 2.5.0 → 2.6.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.
- package/android/AGENTS.md +24 -7
- package/android/CLAUDE.md +42 -4
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/strata/storage/FilesystemStorage.java +287 -0
- package/android/src/main/java/com/strata/storage/SQLiteStorage.java +243 -221
- package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +202 -78
- package/dist/android/AGENTS.md +24 -7
- package/dist/android/CLAUDE.md +42 -4
- package/dist/android/build.gradle +1 -1
- package/dist/android/src/main/java/com/strata/storage/FilesystemStorage.java +287 -0
- package/dist/android/src/main/java/com/strata/storage/SQLiteStorage.java +243 -221
- package/dist/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +202 -78
- package/dist/ios/AGENTS.md +19 -4
- package/dist/ios/CLAUDE.md +39 -4
- package/dist/ios/Plugin/FilesystemStorage.swift +218 -0
- package/dist/ios/Plugin/SQLiteStorage.swift +265 -173
- package/dist/ios/Plugin/StrataStoragePlugin.swift +100 -42
- package/dist/package.json +1 -1
- package/ios/AGENTS.md +19 -4
- package/ios/CLAUDE.md +39 -4
- package/ios/Plugin/FilesystemStorage.swift +218 -0
- package/ios/Plugin/SQLiteStorage.swift +265 -173
- package/ios/Plugin/StrataStoragePlugin.swift +100 -42
- package/package.json +1 -1
|
@@ -8,10 +8,11 @@ import com.getcapacitor.PluginMethod;
|
|
|
8
8
|
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
9
9
|
import com.strata.storage.SharedPreferencesStorage;
|
|
10
10
|
import com.strata.storage.EncryptedStorage;
|
|
11
|
+
import com.strata.storage.FilesystemStorage;
|
|
11
12
|
import com.strata.storage.SQLiteStorage;
|
|
12
13
|
import android.util.Log;
|
|
13
14
|
import org.json.JSONArray;
|
|
14
|
-
import
|
|
15
|
+
import java.util.HashMap;
|
|
15
16
|
import java.util.List;
|
|
16
17
|
import java.util.Map;
|
|
17
18
|
|
|
@@ -19,15 +20,24 @@ import java.util.Map;
|
|
|
19
20
|
* Main Capacitor plugin for Strata Storage
|
|
20
21
|
* Coordinates between different storage types on Android.
|
|
21
22
|
*
|
|
22
|
-
* Storage types with a real native backend: preferences, secure, sqlite
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* Storage types with a real native backend: preferences, secure, sqlite,
|
|
24
|
+
* filesystem.
|
|
25
|
+
*
|
|
26
|
+
* SQLite supports multi-store: the `database` option selects the DB file and
|
|
27
|
+
* `table` selects the table within it. SQLite helper instances are cached per
|
|
28
|
+
* database name so the connection is not reopened per call.
|
|
25
29
|
*/
|
|
26
30
|
@CapacitorPlugin(name = "StrataStorage")
|
|
27
31
|
public class StrataStoragePlugin extends Plugin {
|
|
32
|
+
private static final String DEFAULT_DATABASE = "strata_storage";
|
|
33
|
+
private static final String DEFAULT_TABLE = "storage";
|
|
34
|
+
|
|
28
35
|
private SharedPreferencesStorage sharedPrefsStorage;
|
|
29
36
|
private EncryptedStorage encryptedStorage;
|
|
30
|
-
private
|
|
37
|
+
private FilesystemStorage filesystemStorage;
|
|
38
|
+
|
|
39
|
+
/** SQLite helper cache keyed by resolved database name (without ".db"). */
|
|
40
|
+
private final Map<String, SQLiteStorage> sqliteStores = new HashMap<>();
|
|
31
41
|
|
|
32
42
|
@Override
|
|
33
43
|
public void load() {
|
|
@@ -44,18 +54,43 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
44
54
|
Log.e("StrataStorage", "Failed to initialize encrypted storage", e);
|
|
45
55
|
}
|
|
46
56
|
try {
|
|
47
|
-
|
|
57
|
+
filesystemStorage = new FilesystemStorage(getContext());
|
|
48
58
|
} catch (Exception e) {
|
|
49
|
-
Log.e("StrataStorage", "Failed to initialize
|
|
59
|
+
Log.e("StrataStorage", "Failed to initialize filesystem storage", e);
|
|
50
60
|
}
|
|
51
61
|
}
|
|
52
62
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)
|
|
63
|
+
/**
|
|
64
|
+
* Sanitize a database name to a safe file stem ({@code ^[A-Za-z0-9_]+$})
|
|
65
|
+
* and return the resolved stem. The ".db" suffix is appended when opening.
|
|
66
|
+
*/
|
|
67
|
+
private static String sanitizeDatabase(String database) {
|
|
68
|
+
if (database == null || database.isEmpty()) {
|
|
69
|
+
return DEFAULT_DATABASE;
|
|
70
|
+
}
|
|
71
|
+
String cleaned = database.replaceAll("[^A-Za-z0-9_]", "");
|
|
72
|
+
return cleaned.isEmpty() ? DEFAULT_DATABASE : cleaned;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Return (creating + caching on first use) the SQLite helper for the given
|
|
77
|
+
* database name. The cache is keyed by the sanitized DB stem so the
|
|
78
|
+
* connection is reused across bridge calls. May return {@code null} if the
|
|
79
|
+
* helper cannot be constructed.
|
|
80
|
+
*/
|
|
81
|
+
private synchronized SQLiteStorage getSqliteStore(String database) {
|
|
82
|
+
String dbStem = sanitizeDatabase(database);
|
|
83
|
+
SQLiteStorage store = sqliteStores.get(dbStem);
|
|
84
|
+
if (store == null) {
|
|
85
|
+
try {
|
|
86
|
+
store = new SQLiteStorage(getContext(), dbStem + ".db");
|
|
87
|
+
sqliteStores.put(dbStem, store);
|
|
88
|
+
} catch (Exception e) {
|
|
89
|
+
Log.e("StrataStorage", "Failed to open SQLite database " + dbStem, e);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return store;
|
|
59
94
|
}
|
|
60
95
|
|
|
61
96
|
/**
|
|
@@ -71,11 +106,10 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
71
106
|
available = encryptedStorage != null;
|
|
72
107
|
break;
|
|
73
108
|
case "sqlite":
|
|
74
|
-
available =
|
|
109
|
+
available = getSqliteStore(call.getString("database", DEFAULT_DATABASE)) != null;
|
|
75
110
|
break;
|
|
76
111
|
case "filesystem":
|
|
77
|
-
|
|
78
|
-
available = false;
|
|
112
|
+
available = filesystemStorage != null;
|
|
79
113
|
break;
|
|
80
114
|
case "preferences":
|
|
81
115
|
default:
|
|
@@ -89,7 +123,10 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
89
123
|
}
|
|
90
124
|
|
|
91
125
|
/**
|
|
92
|
-
* Get value from storage
|
|
126
|
+
* Get value from storage.
|
|
127
|
+
*
|
|
128
|
+
* For sqlite/filesystem the resolved `value` is the full wrapper object
|
|
129
|
+
* (parsed back from JSON). A miss resolves `value` = null.
|
|
93
130
|
*/
|
|
94
131
|
@PluginMethod
|
|
95
132
|
public void get(PluginCall call) {
|
|
@@ -102,46 +139,70 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
102
139
|
String storage = call.getString("storage", "preferences");
|
|
103
140
|
|
|
104
141
|
try {
|
|
105
|
-
Object value = null;
|
|
106
|
-
|
|
107
142
|
switch (storage) {
|
|
108
|
-
case "secure":
|
|
143
|
+
case "secure": {
|
|
109
144
|
if (encryptedStorage == null) {
|
|
110
145
|
call.reject("Encrypted storage not available");
|
|
111
146
|
return;
|
|
112
147
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
148
|
+
resolveValue(call, encryptedStorage.get(key));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
case "sqlite": {
|
|
152
|
+
SQLiteStorage store = getSqliteStore(call.getString("database", DEFAULT_DATABASE));
|
|
153
|
+
if (store == null) {
|
|
117
154
|
call.reject("SQLite storage not available");
|
|
118
155
|
return;
|
|
119
156
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
case "filesystem":
|
|
123
|
-
rejectUnsupportedFilesystem(call);
|
|
157
|
+
String table = call.getString("table", DEFAULT_TABLE);
|
|
158
|
+
resolveValue(call, store.get(table, key));
|
|
124
159
|
return;
|
|
160
|
+
}
|
|
161
|
+
case "filesystem": {
|
|
162
|
+
if (filesystemStorage == null) {
|
|
163
|
+
call.reject("Filesystem storage not available");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
resolveValue(call, filesystemStorage.get(key));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
125
169
|
case "preferences":
|
|
126
|
-
default:
|
|
170
|
+
default: {
|
|
127
171
|
if (sharedPrefsStorage == null) {
|
|
128
172
|
call.reject("Preferences storage not available");
|
|
129
173
|
return;
|
|
130
174
|
}
|
|
131
|
-
|
|
132
|
-
|
|
175
|
+
resolveValue(call, sharedPrefsStorage.get(key));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
133
178
|
}
|
|
134
|
-
|
|
135
|
-
JSObject result = new JSObject();
|
|
136
|
-
result.put("value", value);
|
|
137
|
-
call.resolve(result);
|
|
138
179
|
} catch (Exception e) {
|
|
139
180
|
call.reject("Failed to get value", e);
|
|
140
181
|
}
|
|
141
182
|
}
|
|
142
183
|
|
|
143
184
|
/**
|
|
144
|
-
*
|
|
185
|
+
* Resolve a get() call, mapping a Java {@code null} to a JS {@code null}
|
|
186
|
+
* value so the TS adapters treat it as a miss.
|
|
187
|
+
*/
|
|
188
|
+
private void resolveValue(PluginCall call, Object value) {
|
|
189
|
+
JSObject result = new JSObject();
|
|
190
|
+
if (value == null) {
|
|
191
|
+
result.put("value", JSObject.NULL);
|
|
192
|
+
} else {
|
|
193
|
+
result.put("value", value);
|
|
194
|
+
}
|
|
195
|
+
call.resolve(result);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Set value in storage.
|
|
200
|
+
*
|
|
201
|
+
* For sqlite/filesystem the `value` is the FULL wrapper object
|
|
202
|
+
* ({ value, created, updated, expires?, tags?, metadata? }); it is read via
|
|
203
|
+
* call.getObject("value") and stored verbatim (with TTL/query columns
|
|
204
|
+
* extracted for sqlite). For preferences/secure the raw value is forwarded
|
|
205
|
+
* unchanged.
|
|
145
206
|
*/
|
|
146
207
|
@PluginMethod
|
|
147
208
|
public void set(PluginCall call) {
|
|
@@ -151,7 +212,6 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
151
212
|
return;
|
|
152
213
|
}
|
|
153
214
|
|
|
154
|
-
Object value = call.getData().opt("value");
|
|
155
215
|
String storage = call.getString("storage", "preferences");
|
|
156
216
|
|
|
157
217
|
try {
|
|
@@ -162,25 +222,42 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
162
222
|
call.reject("Encrypted storage not available");
|
|
163
223
|
return;
|
|
164
224
|
}
|
|
165
|
-
ok = encryptedStorage.set(key, value);
|
|
225
|
+
ok = encryptedStorage.set(key, call.getData().opt("value"));
|
|
166
226
|
break;
|
|
167
|
-
case "sqlite":
|
|
168
|
-
|
|
227
|
+
case "sqlite": {
|
|
228
|
+
SQLiteStorage store = getSqliteStore(call.getString("database", DEFAULT_DATABASE));
|
|
229
|
+
if (store == null) {
|
|
169
230
|
call.reject("SQLite storage not available");
|
|
170
231
|
return;
|
|
171
232
|
}
|
|
172
|
-
|
|
233
|
+
JSObject wrapper = call.getObject("value");
|
|
234
|
+
if (wrapper == null) {
|
|
235
|
+
call.reject("A wrapper object 'value' is required for sqlite storage");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
ok = store.set(call.getString("table", DEFAULT_TABLE), key, wrapper);
|
|
173
239
|
break;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
240
|
+
}
|
|
241
|
+
case "filesystem": {
|
|
242
|
+
if (filesystemStorage == null) {
|
|
243
|
+
call.reject("Filesystem storage not available");
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
JSObject wrapper = call.getObject("value");
|
|
247
|
+
if (wrapper == null) {
|
|
248
|
+
call.reject("A wrapper object 'value' is required for filesystem storage");
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
ok = filesystemStorage.set(key, wrapper);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
177
254
|
case "preferences":
|
|
178
255
|
default:
|
|
179
256
|
if (sharedPrefsStorage == null) {
|
|
180
257
|
call.reject("Preferences storage not available");
|
|
181
258
|
return;
|
|
182
259
|
}
|
|
183
|
-
ok = sharedPrefsStorage.set(key, value);
|
|
260
|
+
ok = sharedPrefsStorage.set(key, call.getData().opt("value"));
|
|
184
261
|
break;
|
|
185
262
|
}
|
|
186
263
|
|
|
@@ -216,16 +293,22 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
216
293
|
}
|
|
217
294
|
encryptedStorage.remove(key);
|
|
218
295
|
break;
|
|
219
|
-
case "sqlite":
|
|
220
|
-
|
|
296
|
+
case "sqlite": {
|
|
297
|
+
SQLiteStorage store = getSqliteStore(call.getString("database", DEFAULT_DATABASE));
|
|
298
|
+
if (store == null) {
|
|
221
299
|
call.reject("SQLite storage not available");
|
|
222
300
|
return;
|
|
223
301
|
}
|
|
224
|
-
|
|
302
|
+
store.remove(call.getString("table", DEFAULT_TABLE), key);
|
|
225
303
|
break;
|
|
304
|
+
}
|
|
226
305
|
case "filesystem":
|
|
227
|
-
|
|
228
|
-
|
|
306
|
+
if (filesystemStorage == null) {
|
|
307
|
+
call.reject("Filesystem storage not available");
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
filesystemStorage.remove(key);
|
|
311
|
+
break;
|
|
229
312
|
case "preferences":
|
|
230
313
|
default:
|
|
231
314
|
if (sharedPrefsStorage == null) {
|
|
@@ -263,16 +346,22 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
263
346
|
}
|
|
264
347
|
encryptedStorage.clear(prefix);
|
|
265
348
|
break;
|
|
266
|
-
case "sqlite":
|
|
267
|
-
|
|
349
|
+
case "sqlite": {
|
|
350
|
+
SQLiteStorage store = getSqliteStore(call.getString("database", DEFAULT_DATABASE));
|
|
351
|
+
if (store == null) {
|
|
268
352
|
call.reject("SQLite storage not available");
|
|
269
353
|
return;
|
|
270
354
|
}
|
|
271
|
-
|
|
355
|
+
store.clear(call.getString("table", DEFAULT_TABLE), prefix);
|
|
272
356
|
break;
|
|
357
|
+
}
|
|
273
358
|
case "filesystem":
|
|
274
|
-
|
|
275
|
-
|
|
359
|
+
if (filesystemStorage == null) {
|
|
360
|
+
call.reject("Filesystem storage not available");
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
filesystemStorage.clear(prefix);
|
|
364
|
+
break;
|
|
276
365
|
case "preferences":
|
|
277
366
|
default:
|
|
278
367
|
if (sharedPrefsStorage == null) {
|
|
@@ -298,7 +387,7 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
298
387
|
String pattern = call.getString("pattern");
|
|
299
388
|
|
|
300
389
|
try {
|
|
301
|
-
List<String> keys
|
|
390
|
+
List<String> keys;
|
|
302
391
|
|
|
303
392
|
switch (storage) {
|
|
304
393
|
case "secure":
|
|
@@ -308,16 +397,22 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
308
397
|
}
|
|
309
398
|
keys = encryptedStorage.keys(pattern);
|
|
310
399
|
break;
|
|
311
|
-
case "sqlite":
|
|
312
|
-
|
|
400
|
+
case "sqlite": {
|
|
401
|
+
SQLiteStorage store = getSqliteStore(call.getString("database", DEFAULT_DATABASE));
|
|
402
|
+
if (store == null) {
|
|
313
403
|
call.reject("SQLite storage not available");
|
|
314
404
|
return;
|
|
315
405
|
}
|
|
316
|
-
keys =
|
|
406
|
+
keys = store.keys(call.getString("table", DEFAULT_TABLE), pattern);
|
|
317
407
|
break;
|
|
408
|
+
}
|
|
318
409
|
case "filesystem":
|
|
319
|
-
|
|
320
|
-
|
|
410
|
+
if (filesystemStorage == null) {
|
|
411
|
+
call.reject("Filesystem storage not available");
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
keys = filesystemStorage.keys(pattern);
|
|
415
|
+
break;
|
|
321
416
|
case "preferences":
|
|
322
417
|
default:
|
|
323
418
|
if (sharedPrefsStorage == null) {
|
|
@@ -337,17 +432,21 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
337
432
|
}
|
|
338
433
|
|
|
339
434
|
/**
|
|
340
|
-
* Get storage size information
|
|
435
|
+
* Get storage size information.
|
|
436
|
+
*
|
|
437
|
+
* When `detailed` is true the result also carries a byte breakdown
|
|
438
|
+
* { detailed: { keys, values, metadata } }.
|
|
341
439
|
*/
|
|
342
440
|
@PluginMethod
|
|
343
441
|
public void size(PluginCall call) {
|
|
344
442
|
String storage = call.getString("storage", "preferences");
|
|
443
|
+
boolean detailed = Boolean.TRUE.equals(call.getBoolean("detailed", false));
|
|
345
444
|
|
|
346
445
|
try {
|
|
347
446
|
JSObject result = new JSObject();
|
|
348
447
|
|
|
349
448
|
switch (storage) {
|
|
350
|
-
case "secure":
|
|
449
|
+
case "secure": {
|
|
351
450
|
if (encryptedStorage == null) {
|
|
352
451
|
call.reject("Encrypted storage not available");
|
|
353
452
|
return;
|
|
@@ -356,20 +455,29 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
356
455
|
result.put("total", encryptedSizeInfo.total);
|
|
357
456
|
result.put("count", encryptedSizeInfo.count);
|
|
358
457
|
break;
|
|
359
|
-
|
|
360
|
-
|
|
458
|
+
}
|
|
459
|
+
case "sqlite": {
|
|
460
|
+
SQLiteStorage store = getSqliteStore(call.getString("database", DEFAULT_DATABASE));
|
|
461
|
+
if (store == null) {
|
|
361
462
|
call.reject("SQLite storage not available");
|
|
362
463
|
return;
|
|
363
464
|
}
|
|
364
|
-
SQLiteStorage.SizeInfo sqliteSizeInfo =
|
|
365
|
-
|
|
366
|
-
result
|
|
465
|
+
SQLiteStorage.SizeInfo sqliteSizeInfo =
|
|
466
|
+
store.size(call.getString("table", DEFAULT_TABLE), detailed);
|
|
467
|
+
putSizeInfo(result, sqliteSizeInfo);
|
|
367
468
|
break;
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
469
|
+
}
|
|
470
|
+
case "filesystem": {
|
|
471
|
+
if (filesystemStorage == null) {
|
|
472
|
+
call.reject("Filesystem storage not available");
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
SQLiteStorage.SizeInfo fsSizeInfo = filesystemStorage.size(detailed);
|
|
476
|
+
putSizeInfo(result, fsSizeInfo);
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
371
479
|
case "preferences":
|
|
372
|
-
default:
|
|
480
|
+
default: {
|
|
373
481
|
if (sharedPrefsStorage == null) {
|
|
374
482
|
call.reject("Preferences storage not available");
|
|
375
483
|
return;
|
|
@@ -378,6 +486,7 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
378
486
|
result.put("total", prefsSizeInfo.total);
|
|
379
487
|
result.put("count", prefsSizeInfo.count);
|
|
380
488
|
break;
|
|
489
|
+
}
|
|
381
490
|
}
|
|
382
491
|
|
|
383
492
|
call.resolve(result);
|
|
@@ -386,12 +495,27 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
386
495
|
}
|
|
387
496
|
}
|
|
388
497
|
|
|
498
|
+
/**
|
|
499
|
+
* Populate a size result, including the detailed byte breakdown when the
|
|
500
|
+
* {@link SQLiteStorage.SizeInfo} carries one.
|
|
501
|
+
*/
|
|
502
|
+
private void putSizeInfo(JSObject result, SQLiteStorage.SizeInfo info) {
|
|
503
|
+
result.put("total", info.total);
|
|
504
|
+
result.put("count", info.count);
|
|
505
|
+
if (info.detailed) {
|
|
506
|
+
JSObject breakdown = new JSObject();
|
|
507
|
+
breakdown.put("keys", info.keysBytes);
|
|
508
|
+
breakdown.put("values", info.valuesBytes);
|
|
509
|
+
breakdown.put("metadata", info.metadataBytes);
|
|
510
|
+
result.put("detailed", breakdown);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
389
514
|
/**
|
|
390
515
|
* Query SQLite-backed storage.
|
|
391
516
|
* Matches the optional `query` method in the JS contract:
|
|
392
|
-
* resolves { results: [{ key
|
|
393
|
-
*
|
|
394
|
-
* rows.
|
|
517
|
+
* resolves { results: [{ key }] }. The JS SqliteAdapter applies the real
|
|
518
|
+
* condition by re-fetching/filtering, so this enumerates candidate keys.
|
|
395
519
|
*/
|
|
396
520
|
@PluginMethod
|
|
397
521
|
public void query(PluginCall call) {
|
|
@@ -400,18 +524,18 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
400
524
|
call.reject("Query is only supported for the 'sqlite' storage type");
|
|
401
525
|
return;
|
|
402
526
|
}
|
|
403
|
-
|
|
527
|
+
SQLiteStorage store = getSqliteStore(call.getString("database", DEFAULT_DATABASE));
|
|
528
|
+
if (store == null) {
|
|
404
529
|
call.reject("SQLite storage not available");
|
|
405
530
|
return;
|
|
406
531
|
}
|
|
407
532
|
|
|
408
533
|
try {
|
|
409
|
-
List<Map<String, Object>> rows =
|
|
534
|
+
List<Map<String, Object>> rows = store.query(call.getString("table", DEFAULT_TABLE));
|
|
410
535
|
JSArray results = new JSArray();
|
|
411
536
|
for (Map<String, Object> row : rows) {
|
|
412
537
|
JSObject obj = new JSObject();
|
|
413
538
|
obj.put("key", row.get("key"));
|
|
414
|
-
obj.put("value", row.get("value"));
|
|
415
539
|
results.put(obj);
|
|
416
540
|
}
|
|
417
541
|
JSObject result = new JSObject();
|
package/dist/android/AGENTS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AGENTS.md — android/
|
|
2
2
|
|
|
3
|
-
Last Updated: 2026-
|
|
3
|
+
Last Updated: 2026-05-27
|
|
4
4
|
|
|
5
5
|
> Agent instructions for Android native development.
|
|
6
6
|
|
|
@@ -8,27 +8,44 @@ Last Updated: 2026-04-03
|
|
|
8
8
|
|
|
9
9
|
| File | Purpose |
|
|
10
10
|
|------|---------|
|
|
11
|
-
| `StrataStoragePlugin.java` | Capacitor plugin entry (com.stratastorage) |
|
|
12
|
-
| `SharedPreferencesStorage.java` | General key-value storage (com.strata.storage) |
|
|
13
|
-
| `EncryptedStorage.java` | Secure storage (com.strata.storage) |
|
|
14
|
-
| `SQLiteStorage.java` |
|
|
11
|
+
| `StrataStoragePlugin.java` | Capacitor plugin entry (`com.stratastorage`) |
|
|
12
|
+
| `SharedPreferencesStorage.java` | General key-value storage (`com.strata.storage`) |
|
|
13
|
+
| `EncryptedStorage.java` | Secure storage via EncryptedSharedPreferences (`com.strata.storage`); requires `androidx.security:security-crypto 1.1.0` (stable, upgraded in v2.6.0) |
|
|
14
|
+
| `SQLiteStorage.java` | SQLite database storage — multi-store (v2.6.0): `database`/`table` options honoured, full `StorageValue` wrapper round-trip, `size(detailed)` supported (`com.strata.storage`) |
|
|
15
|
+
| `FilesystemStorage.java` | File-per-key native storage under `getFilesDir()/strata_storage/` (new in v2.6.0); atomic writes via staging rename; `isAvailable()` returns `true`; no external permissions needed (`com.strata.storage`) |
|
|
16
|
+
|
|
17
|
+
## v2.6.0 Notes
|
|
18
|
+
|
|
19
|
+
- **`androidx.security 1.1.0`:** upgraded from `1.1.0-alpha06`. Requires `compileSdkVersion 34`. If Gradle sync fails, bump `compileSdkVersion` and `targetSdkVersion` to 34 in `android/app/build.gradle`.
|
|
20
|
+
- **SQLite multi-store:** `call.getString("database")` and `call.getString("table")` are now read and used. Each `(database, table)` pair → distinct `.db` file. Previously ignored.
|
|
21
|
+
- **SQLite value shape:** native `get` returns full `StorageValue` wrapper. Corrupt rows → treated as miss (no throw).
|
|
22
|
+
- **FilesystemStorage.java:** new class. Writes are atomic (staging temp → `renameTo`). `keys()` excludes `.staging/` artifacts. `size(detailed)` → `{ keys, values, metadata }`.
|
|
23
|
+
- **Pending on-device verification** — see `docs/guides/platforms/device-verification.md`.
|
|
15
24
|
|
|
16
25
|
## Agent Rules
|
|
17
26
|
|
|
18
27
|
### Security (IRON-SOLID)
|
|
19
28
|
- Sensitive data MUST use `EncryptedStorage`
|
|
20
29
|
- NEVER store secrets in `SharedPreferencesStorage`
|
|
30
|
+
- `androidx.security:security-crypto` version MUST be `1.1.0` (stable) — not alpha
|
|
21
31
|
|
|
22
32
|
### SQL Safety (IRON-SOLID)
|
|
23
33
|
- ALWAYS use parameterized queries
|
|
24
34
|
- NEVER concatenate user input into SQL strings
|
|
25
35
|
|
|
36
|
+
### Filesystem Safety
|
|
37
|
+
- NEVER read from `strata_storage/.staging/` — staging files are transient
|
|
38
|
+
- Key sanitisation must be consistent between `set`, `get`, `remove`, and `keys`
|
|
39
|
+
|
|
26
40
|
### Plugin Architecture
|
|
27
41
|
- Methods exposed through `StrataStoragePlugin.java`
|
|
28
42
|
- Each storage backend is a separate Java class
|
|
29
43
|
- Two package paths: `com.stratastorage` (plugin) and `com.strata.storage` (impl)
|
|
30
44
|
|
|
45
|
+
### Build
|
|
46
|
+
- `compileSdkVersion 34` required for `androidx.security 1.1.0`
|
|
47
|
+
- Verify Gradle build succeeds after any change
|
|
48
|
+
|
|
31
49
|
### Before Modifying
|
|
32
50
|
- Understand Capacitor plugin protocol for Android
|
|
33
|
-
- Test on Android emulator after changes
|
|
34
|
-
- Verify Gradle build succeeds
|
|
51
|
+
- Test on Android emulator after changes; follow device-verification guide
|
package/dist/android/CLAUDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CLAUDE.md — android/
|
|
2
2
|
|
|
3
|
-
Last Updated: 2026-
|
|
3
|
+
Last Updated: 2026-05-27
|
|
4
4
|
|
|
5
5
|
## Android Native Plugin
|
|
6
6
|
|
|
@@ -12,8 +12,9 @@ Java implementation of native storage backends for Capacitor.
|
|
|
12
12
|
|------|------|---------|
|
|
13
13
|
| `StrataStoragePlugin.java` | `src/main/java/com/stratastorage/` | Main Capacitor plugin entry |
|
|
14
14
|
| `SharedPreferencesStorage.java` | `src/main/java/com/strata/storage/` | SharedPreferences general storage |
|
|
15
|
-
| `EncryptedStorage.java` | `src/main/java/com/strata/storage/` | EncryptedSharedPreferences secure storage |
|
|
16
|
-
| `SQLiteStorage.java` | `src/main/java/com/strata/storage/` | SQLite database storage |
|
|
15
|
+
| `EncryptedStorage.java` | `src/main/java/com/strata/storage/` | EncryptedSharedPreferences secure storage (`androidx.security 1.1.0`) |
|
|
16
|
+
| `SQLiteStorage.java` | `src/main/java/com/strata/storage/` | SQLite database storage — multi-store (v2.6.0) |
|
|
17
|
+
| `FilesystemStorage.java` | `src/main/java/com/strata/storage/` | File-per-key storage under `Context.getFilesDir()/strata_storage/` (new in v2.6.0) |
|
|
17
18
|
|
|
18
19
|
### Configuration
|
|
19
20
|
|
|
@@ -23,27 +24,64 @@ Java implementation of native storage backends for Capacitor.
|
|
|
23
24
|
|
|
24
25
|
**Note**: There are two Java package paths — `com.stratastorage` (plugin) and `com.strata.storage` (implementations).
|
|
25
26
|
|
|
27
|
+
## v2.6.0 Changes
|
|
28
|
+
|
|
29
|
+
### `androidx.security:security-crypto` 1.1.0 (stable)
|
|
30
|
+
`EncryptedStorage.java` depends on `androidx.security:security-crypto`. The
|
|
31
|
+
dependency was upgraded from `1.1.0-alpha06` to `1.1.0` (stable) in v2.6.0.
|
|
32
|
+
The `EncryptedSharedPreferences` / `MasterKey` API is unchanged. The stable
|
|
33
|
+
version requires `compileSdkVersion 34` — if Gradle sync fails after this
|
|
34
|
+
change, bump `compileSdkVersion` and `targetSdkVersion` in
|
|
35
|
+
`android/app/build.gradle` to 34.
|
|
36
|
+
|
|
37
|
+
### SQLite multi-store
|
|
38
|
+
`SQLiteStorage.java` now accepts `database` and `table` parameters from
|
|
39
|
+
`call.getString("database")` / `call.getString("table")`. Each unique pair
|
|
40
|
+
opens a distinct `.db` file in the app's internal storage. Table identifiers
|
|
41
|
+
are sanitised to `[A-Za-z0-9_]`. The full `StorageValue` wrapper is serialised
|
|
42
|
+
to JSON and stored in a single `value` column; native `get` returns the full
|
|
43
|
+
wrapper so TTL, tags, and metadata survive the round-trip. `size(detailed)`
|
|
44
|
+
returns per-column byte breakdown.
|
|
45
|
+
|
|
46
|
+
### FilesystemStorage.java (new)
|
|
47
|
+
Stores each key as `getFilesDir()/strata_storage/<sanitised-key>.json`. Writes
|
|
48
|
+
are atomic: value is written to `strata_storage/.staging/<key>.tmp`, then
|
|
49
|
+
renamed into place using `renameTo()`. Staging artifacts are excluded from
|
|
50
|
+
`keys()`. No external filesystem permissions are needed (`getFilesDir()` is
|
|
51
|
+
app-private). `isAvailable()` returns `true`. `size(detailed)` supported.
|
|
52
|
+
|
|
53
|
+
> **Pending on-device verification** — native code is complete and reviewed; see
|
|
54
|
+
> `docs/guides/platforms/device-verification.md` for the test matrix.
|
|
55
|
+
|
|
26
56
|
## Rules
|
|
27
57
|
|
|
28
58
|
### Security (IRON-SOLID)
|
|
29
59
|
- Sensitive data MUST use `EncryptedStorage` (EncryptedSharedPreferences)
|
|
30
60
|
- NEVER store credentials, tokens, or secrets in `SharedPreferencesStorage`
|
|
31
61
|
- Follow Android Keystore best practices
|
|
62
|
+
- `androidx.security:security-crypto 1.1.0` (stable) is the required version
|
|
32
63
|
|
|
33
64
|
### SQL Safety (IRON-SOLID)
|
|
34
65
|
- ALWAYS use parameterized queries in `SQLiteStorage`
|
|
35
66
|
- NEVER concatenate user input into SQL strings
|
|
36
67
|
- Use `SQLiteDatabase.query()` or prepared statements
|
|
37
68
|
|
|
69
|
+
### Filesystem Safety
|
|
70
|
+
- NEVER read from `strata_storage/.staging/` — staging files are transient
|
|
71
|
+
- Key sanitisation must be consistent between `set`, `get`, `remove`, and `keys`
|
|
72
|
+
- `getFilesDir()` is app-private; no external storage permissions required
|
|
73
|
+
|
|
38
74
|
### Plugin Architecture
|
|
39
75
|
- All native methods exposed through `StrataStoragePlugin.java`
|
|
40
76
|
- Plugin bridges Capacitor JS calls to Java implementations
|
|
41
77
|
- Each storage backend is a separate Java class
|
|
78
|
+
- Two package paths: `com.stratastorage` (plugin) and `com.strata.storage` (impl)
|
|
42
79
|
|
|
43
80
|
### Build
|
|
44
81
|
- Gradle-based build system
|
|
82
|
+
- `compileSdkVersion 34` required for `androidx.security 1.1.0`
|
|
45
83
|
- Proguard rules configured for release builds
|
|
46
|
-
- Test on Android emulator after changes
|
|
84
|
+
- Test on Android emulator after changes; follow device-verification guide
|
|
47
85
|
|
|
48
86
|
### Before Modifying
|
|
49
87
|
- Understand the Capacitor plugin protocol for Android
|
|
@@ -51,7 +51,7 @@ dependencies {
|
|
|
51
51
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
52
52
|
implementation project(':capacitor-android')
|
|
53
53
|
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
54
|
-
implementation 'androidx.security:security-crypto:1.1.0
|
|
54
|
+
implementation 'androidx.security:security-crypto:1.1.0'
|
|
55
55
|
testImplementation "junit:junit:$junitVersion"
|
|
56
56
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
57
57
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|