strata-storage 2.2.0 → 2.4.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/src/main/java/com/strata/storage/EncryptedStorage.java +110 -7
- package/android/src/main/java/com/strata/storage/SQLiteStorage.java +112 -2
- package/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +95 -7
- package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +10 -20
- package/dist/android/src/main/java/com/strata/storage/EncryptedStorage.java +110 -7
- package/dist/android/src/main/java/com/strata/storage/SQLiteStorage.java +112 -2
- package/dist/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +95 -7
- package/dist/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +10 -20
- package/dist/ios/Plugin/KeychainStorage.swift +44 -9
- package/dist/ios/Plugin/SQLiteStorage.swift +36 -2
- package/dist/ios/Plugin/UserDefaultsStorage.swift +14 -0
- package/dist/package.json +1 -1
- package/ios/Plugin/KeychainStorage.swift +44 -9
- package/ios/Plugin/SQLiteStorage.swift +36 -2
- package/ios/Plugin/UserDefaultsStorage.swift +14 -0
- package/package.json +8 -8
|
@@ -9,6 +9,9 @@ import java.util.ArrayList;
|
|
|
9
9
|
import java.util.List;
|
|
10
10
|
import java.util.HashMap;
|
|
11
11
|
import java.util.Map;
|
|
12
|
+
import org.json.JSONObject;
|
|
13
|
+
import org.json.JSONArray;
|
|
14
|
+
import java.nio.charset.StandardCharsets;
|
|
12
15
|
|
|
13
16
|
public class SQLiteStorage extends SQLiteOpenHelper {
|
|
14
17
|
private static final int DATABASE_VERSION = 1;
|
|
@@ -50,13 +53,40 @@ public class SQLiteStorage extends SQLiteOpenHelper {
|
|
|
50
53
|
onCreate(db);
|
|
51
54
|
}
|
|
52
55
|
|
|
53
|
-
public boolean set(String key,
|
|
56
|
+
public boolean set(String key, Object value, Long expires, String tags, String metadata) {
|
|
57
|
+
byte[] valueBytes;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
if (value instanceof byte[]) {
|
|
61
|
+
valueBytes = (byte[]) value;
|
|
62
|
+
} else if (value instanceof String) {
|
|
63
|
+
valueBytes = ((String) value).getBytes(StandardCharsets.UTF_8);
|
|
64
|
+
} else {
|
|
65
|
+
// Convert complex objects to JSON then to bytes
|
|
66
|
+
String json;
|
|
67
|
+
if (value instanceof JSONObject) {
|
|
68
|
+
json = ((JSONObject) value).toString();
|
|
69
|
+
} else {
|
|
70
|
+
// Use helper method to convert object to JSON string
|
|
71
|
+
try {
|
|
72
|
+
json = objectToJsonString(value);
|
|
73
|
+
} catch (Exception jsonEx) {
|
|
74
|
+
// Fallback to string representation
|
|
75
|
+
json = value.toString();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
valueBytes = json.getBytes(StandardCharsets.UTF_8);
|
|
79
|
+
}
|
|
80
|
+
} catch (Exception e) {
|
|
81
|
+
e.printStackTrace();
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
54
84
|
SQLiteDatabase db = this.getWritableDatabase();
|
|
55
85
|
ContentValues values = new ContentValues();
|
|
56
86
|
|
|
57
87
|
long now = System.currentTimeMillis();
|
|
58
88
|
values.put(KEY_ID, key);
|
|
59
|
-
values.put(KEY_VALUE,
|
|
89
|
+
values.put(KEY_VALUE, valueBytes);
|
|
60
90
|
values.put(KEY_CREATED, now);
|
|
61
91
|
values.put(KEY_UPDATED, now);
|
|
62
92
|
|
|
@@ -75,6 +105,11 @@ public class SQLiteStorage extends SQLiteOpenHelper {
|
|
|
75
105
|
return result != -1;
|
|
76
106
|
}
|
|
77
107
|
|
|
108
|
+
// Convenience method for simple Object values
|
|
109
|
+
public boolean set(String key, Object value) {
|
|
110
|
+
return set(key, value, null, null, null);
|
|
111
|
+
}
|
|
112
|
+
|
|
78
113
|
public Map<String, Object> get(String key) {
|
|
79
114
|
SQLiteDatabase db = this.getReadableDatabase();
|
|
80
115
|
Cursor cursor = db.query(TABLE_NAME, null, KEY_ID + "=?",
|
|
@@ -174,4 +209,79 @@ public class SQLiteStorage extends SQLiteOpenHelper {
|
|
|
174
209
|
db.close();
|
|
175
210
|
return exists;
|
|
176
211
|
}
|
|
212
|
+
|
|
213
|
+
public SizeInfo size() {
|
|
214
|
+
SQLiteDatabase db = this.getReadableDatabase();
|
|
215
|
+
Cursor cursor = db.rawQuery("SELECT COUNT(*), SUM(LENGTH(" + KEY_VALUE + ")) FROM " + TABLE_NAME, null);
|
|
216
|
+
|
|
217
|
+
long totalSize = 0;
|
|
218
|
+
int count = 0;
|
|
219
|
+
|
|
220
|
+
if (cursor != null && cursor.moveToFirst()) {
|
|
221
|
+
count = cursor.getInt(0);
|
|
222
|
+
totalSize = cursor.getLong(1);
|
|
223
|
+
cursor.close();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
db.close();
|
|
227
|
+
return new SizeInfo(totalSize, count);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Convert an object to JSON string using reflection
|
|
232
|
+
*/
|
|
233
|
+
private String objectToJsonString(Object obj) throws Exception {
|
|
234
|
+
if (obj == null) {
|
|
235
|
+
return "null";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) {
|
|
239
|
+
return obj.toString();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (obj instanceof Map) {
|
|
243
|
+
JSONObject jsonObj = new JSONObject();
|
|
244
|
+
Map<?, ?> map = (Map<?, ?>) obj;
|
|
245
|
+
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
|
246
|
+
String key = entry.getKey().toString();
|
|
247
|
+
jsonObj.put(key, entry.getValue());
|
|
248
|
+
}
|
|
249
|
+
return jsonObj.toString();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (obj instanceof List || obj.getClass().isArray()) {
|
|
253
|
+
JSONArray jsonArray = new JSONArray();
|
|
254
|
+
if (obj instanceof List) {
|
|
255
|
+
List<?> list = (List<?>) obj;
|
|
256
|
+
for (Object item : list) {
|
|
257
|
+
jsonArray.put(item);
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
Object[] array = (Object[]) obj;
|
|
261
|
+
for (Object item : array) {
|
|
262
|
+
jsonArray.put(item);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return jsonArray.toString();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// For other objects, create a simple JSON object with their string representation
|
|
269
|
+
JSONObject jsonObj = new JSONObject();
|
|
270
|
+
jsonObj.put("value", obj.toString());
|
|
271
|
+
jsonObj.put("type", obj.getClass().getSimpleName());
|
|
272
|
+
return jsonObj.toString();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Size information class
|
|
277
|
+
*/
|
|
278
|
+
public static class SizeInfo {
|
|
279
|
+
public final long total;
|
|
280
|
+
public final int count;
|
|
281
|
+
|
|
282
|
+
public SizeInfo(long total, int count) {
|
|
283
|
+
this.total = total;
|
|
284
|
+
this.count = count;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
177
287
|
}
|
|
@@ -5,6 +5,8 @@ import android.content.SharedPreferences;
|
|
|
5
5
|
import java.util.Map;
|
|
6
6
|
import java.util.Set;
|
|
7
7
|
import java.util.HashSet;
|
|
8
|
+
import java.util.List;
|
|
9
|
+
import java.util.ArrayList;
|
|
8
10
|
import org.json.JSONObject;
|
|
9
11
|
import org.json.JSONArray;
|
|
10
12
|
|
|
@@ -38,9 +40,18 @@ public class SharedPreferencesStorage {
|
|
|
38
40
|
editor.putStringSet(key, (Set<String>) value);
|
|
39
41
|
} else {
|
|
40
42
|
// Convert complex objects to JSON
|
|
41
|
-
String json
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
String json;
|
|
44
|
+
if (value instanceof JSONObject) {
|
|
45
|
+
json = ((JSONObject) value).toString();
|
|
46
|
+
} else {
|
|
47
|
+
// Use reflection to convert object to JSON string
|
|
48
|
+
try {
|
|
49
|
+
json = objectToJsonString(value);
|
|
50
|
+
} catch (Exception e) {
|
|
51
|
+
// Fallback to string representation
|
|
52
|
+
json = value.toString();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
44
55
|
editor.putString(key, json);
|
|
45
56
|
}
|
|
46
57
|
return editor.commit();
|
|
@@ -83,19 +94,19 @@ public class SharedPreferencesStorage {
|
|
|
83
94
|
return editor.commit();
|
|
84
95
|
}
|
|
85
96
|
|
|
86
|
-
public
|
|
97
|
+
public List<String> keys() {
|
|
87
98
|
return keys(null);
|
|
88
99
|
}
|
|
89
100
|
|
|
90
|
-
public
|
|
101
|
+
public List<String> keys(String pattern) {
|
|
91
102
|
Set<String> allKeys = prefs.getAll().keySet();
|
|
92
103
|
|
|
93
104
|
if (pattern == null) {
|
|
94
|
-
return allKeys;
|
|
105
|
+
return new ArrayList<>(allKeys);
|
|
95
106
|
}
|
|
96
107
|
|
|
97
108
|
// Filter keys by pattern
|
|
98
|
-
|
|
109
|
+
List<String> filteredKeys = new ArrayList<>();
|
|
99
110
|
for (String key : allKeys) {
|
|
100
111
|
if (key.startsWith(pattern) || key.contains(pattern)) {
|
|
101
112
|
filteredKeys.add(key);
|
|
@@ -111,4 +122,81 @@ public class SharedPreferencesStorage {
|
|
|
111
122
|
public Map<String, ?> getAll() {
|
|
112
123
|
return prefs.getAll();
|
|
113
124
|
}
|
|
125
|
+
|
|
126
|
+
public SizeInfo size() {
|
|
127
|
+
Map<String, ?> all = prefs.getAll();
|
|
128
|
+
long totalSize = 0;
|
|
129
|
+
int count = all.size();
|
|
130
|
+
|
|
131
|
+
for (Map.Entry<String, ?> entry : all.entrySet()) {
|
|
132
|
+
String key = entry.getKey();
|
|
133
|
+
Object value = entry.getValue();
|
|
134
|
+
|
|
135
|
+
// Estimate size (key + value in bytes)
|
|
136
|
+
totalSize += key.getBytes().length;
|
|
137
|
+
if (value != null) {
|
|
138
|
+
totalSize += value.toString().getBytes().length;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return new SizeInfo(totalSize, count);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Convert an object to JSON string using reflection
|
|
147
|
+
*/
|
|
148
|
+
private String objectToJsonString(Object obj) throws Exception {
|
|
149
|
+
if (obj == null) {
|
|
150
|
+
return "null";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) {
|
|
154
|
+
return obj.toString();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (obj instanceof Map) {
|
|
158
|
+
JSONObject jsonObj = new JSONObject();
|
|
159
|
+
Map<?, ?> map = (Map<?, ?>) obj;
|
|
160
|
+
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
|
161
|
+
String key = entry.getKey().toString();
|
|
162
|
+
jsonObj.put(key, entry.getValue());
|
|
163
|
+
}
|
|
164
|
+
return jsonObj.toString();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (obj instanceof List || obj.getClass().isArray()) {
|
|
168
|
+
JSONArray jsonArray = new JSONArray();
|
|
169
|
+
if (obj instanceof List) {
|
|
170
|
+
List<?> list = (List<?>) obj;
|
|
171
|
+
for (Object item : list) {
|
|
172
|
+
jsonArray.put(item);
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
Object[] array = (Object[]) obj;
|
|
176
|
+
for (Object item : array) {
|
|
177
|
+
jsonArray.put(item);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return jsonArray.toString();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// For other objects, create a simple JSON object with their string representation
|
|
184
|
+
JSONObject jsonObj = new JSONObject();
|
|
185
|
+
jsonObj.put("value", obj.toString());
|
|
186
|
+
jsonObj.put("type", obj.getClass().getSimpleName());
|
|
187
|
+
return jsonObj.toString();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Size information class
|
|
192
|
+
*/
|
|
193
|
+
public static class SizeInfo {
|
|
194
|
+
public final long total;
|
|
195
|
+
public final int count;
|
|
196
|
+
|
|
197
|
+
public SizeInfo(long total, int count) {
|
|
198
|
+
this.total = total;
|
|
199
|
+
this.count = count;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
114
202
|
}
|
|
@@ -11,7 +11,6 @@ import com.strata.storage.SQLiteStorage;
|
|
|
11
11
|
import org.json.JSONArray;
|
|
12
12
|
import org.json.JSONException;
|
|
13
13
|
import java.util.List;
|
|
14
|
-
import java.util.Set;
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* Main Capacitor plugin for Strata Storage
|
|
@@ -226,40 +225,31 @@ public class StrataStoragePlugin extends Plugin {
|
|
|
226
225
|
String storage = call.getString("storage", "preferences");
|
|
227
226
|
|
|
228
227
|
try {
|
|
229
|
-
|
|
228
|
+
JSObject result = new JSObject();
|
|
230
229
|
|
|
231
230
|
switch (storage) {
|
|
232
231
|
case "secure":
|
|
233
|
-
|
|
232
|
+
EncryptedStorage.SizeInfo encryptedSizeInfo = encryptedStorage.size();
|
|
233
|
+
result.put("total", encryptedSizeInfo.total);
|
|
234
|
+
result.put("count", encryptedSizeInfo.count);
|
|
234
235
|
break;
|
|
235
236
|
case "sqlite":
|
|
236
|
-
|
|
237
|
+
SQLiteStorage.SizeInfo sqliteSizeInfo = sqliteStorage.size();
|
|
238
|
+
result.put("total", sqliteSizeInfo.total);
|
|
239
|
+
result.put("count", sqliteSizeInfo.count);
|
|
237
240
|
break;
|
|
238
241
|
case "preferences":
|
|
239
242
|
default:
|
|
240
|
-
|
|
243
|
+
SharedPreferencesStorage.SizeInfo prefsSizeInfo = sharedPrefsStorage.size();
|
|
244
|
+
result.put("total", prefsSizeInfo.total);
|
|
245
|
+
result.put("count", prefsSizeInfo.count);
|
|
241
246
|
break;
|
|
242
247
|
}
|
|
243
248
|
|
|
244
|
-
JSObject result = new JSObject();
|
|
245
|
-
result.put("total", sizeInfo.total);
|
|
246
|
-
result.put("count", sizeInfo.count);
|
|
247
249
|
call.resolve(result);
|
|
248
250
|
} catch (Exception e) {
|
|
249
251
|
call.reject("Failed to get size", e);
|
|
250
252
|
}
|
|
251
253
|
}
|
|
252
254
|
|
|
253
|
-
/**
|
|
254
|
-
* Size information class
|
|
255
|
-
*/
|
|
256
|
-
static class SizeInfo {
|
|
257
|
-
public final long total;
|
|
258
|
-
public final int count;
|
|
259
|
-
|
|
260
|
-
public SizeInfo(long total, int count) {
|
|
261
|
-
this.total = total;
|
|
262
|
-
this.count = count;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
255
|
}
|
|
@@ -11,18 +11,30 @@ import Security
|
|
|
11
11
|
super.init()
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
@objc public func set(key: String, value:
|
|
14
|
+
@objc public func set(key: String, value: Any) throws -> Bool {
|
|
15
|
+
let data: Data
|
|
16
|
+
|
|
17
|
+
if let dataValue = value as? Data {
|
|
18
|
+
data = dataValue
|
|
19
|
+
} else if let stringValue = value as? String {
|
|
20
|
+
data = stringValue.data(using: .utf8) ?? Data()
|
|
21
|
+
} else {
|
|
22
|
+
// Convert to JSON for complex objects
|
|
23
|
+
let jsonData = try JSONSerialization.data(withJSONObject: value, options: [])
|
|
24
|
+
data = jsonData
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
let query = createQuery(key: key)
|
|
16
28
|
SecItemDelete(query as CFDictionary)
|
|
17
29
|
|
|
18
30
|
var newItem = query
|
|
19
|
-
newItem[kSecValueData as String] =
|
|
31
|
+
newItem[kSecValueData as String] = data
|
|
20
32
|
|
|
21
33
|
let status = SecItemAdd(newItem as CFDictionary, nil)
|
|
22
34
|
return status == errSecSuccess
|
|
23
35
|
}
|
|
24
36
|
|
|
25
|
-
@objc public func get(key: String) ->
|
|
37
|
+
@objc public func get(key: String) throws -> Any? {
|
|
26
38
|
var query = createQuery(key: key)
|
|
27
39
|
query[kSecReturnData as String] = true
|
|
28
40
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
@@ -31,22 +43,29 @@ import Security
|
|
|
31
43
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
32
44
|
|
|
33
45
|
guard status == errSecSuccess else { return nil }
|
|
34
|
-
|
|
46
|
+
guard let data = result as? Data else { return nil }
|
|
47
|
+
|
|
48
|
+
// Try to parse as JSON first, fallback to string
|
|
49
|
+
if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []) {
|
|
50
|
+
return jsonObject
|
|
51
|
+
} else {
|
|
52
|
+
return String(data: data, encoding: .utf8)
|
|
53
|
+
}
|
|
35
54
|
}
|
|
36
55
|
|
|
37
|
-
@objc public func remove(key: String) -> Bool {
|
|
56
|
+
@objc public func remove(key: String) throws -> Bool {
|
|
38
57
|
let query = createQuery(key: key)
|
|
39
58
|
let status = SecItemDelete(query as CFDictionary)
|
|
40
59
|
return status == errSecSuccess
|
|
41
60
|
}
|
|
42
61
|
|
|
43
|
-
@objc public func clear(prefix: String? = nil) -> Bool {
|
|
62
|
+
@objc public func clear(prefix: String? = nil) throws -> Bool {
|
|
44
63
|
if let prefix = prefix {
|
|
45
64
|
// Clear only keys with the given prefix
|
|
46
|
-
let keysToRemove = keys(pattern: prefix)
|
|
65
|
+
let keysToRemove = try keys(pattern: prefix)
|
|
47
66
|
var allSuccess = true
|
|
48
67
|
for key in keysToRemove {
|
|
49
|
-
if !remove(key: key) {
|
|
68
|
+
if !(try remove(key: key)) {
|
|
50
69
|
allSuccess = false
|
|
51
70
|
}
|
|
52
71
|
}
|
|
@@ -62,7 +81,7 @@ import Security
|
|
|
62
81
|
}
|
|
63
82
|
}
|
|
64
83
|
|
|
65
|
-
@objc public func keys(pattern: String? = nil) -> [String] {
|
|
84
|
+
@objc public func keys(pattern: String? = nil) throws -> [String] {
|
|
66
85
|
var query: [String: Any] = [
|
|
67
86
|
kSecClass as String: kSecClassGenericPassword,
|
|
68
87
|
kSecAttrService as String: service,
|
|
@@ -106,4 +125,20 @@ import Security
|
|
|
106
125
|
|
|
107
126
|
return query
|
|
108
127
|
}
|
|
128
|
+
|
|
129
|
+
@objc public func size() throws -> (total: Int, count: Int) {
|
|
130
|
+
let allKeys = try keys()
|
|
131
|
+
var totalSize = 0
|
|
132
|
+
|
|
133
|
+
for key in allKeys {
|
|
134
|
+
if let data = try get(key: key) as? Data {
|
|
135
|
+
totalSize += data.count
|
|
136
|
+
} else if let string = try get(key: key) as? String {
|
|
137
|
+
totalSize += string.data(using: .utf8)?.count ?? 0
|
|
138
|
+
}
|
|
139
|
+
totalSize += key.data(using: .utf8)?.count ?? 0
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return (total: totalSize, count: allKeys.count)
|
|
143
|
+
}
|
|
109
144
|
}
|
|
@@ -53,7 +53,18 @@ import SQLite3
|
|
|
53
53
|
sqlite3_finalize(createTableStatement)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
@objc public func set(key: String, value:
|
|
56
|
+
@objc public func set(key: String, value: Any, expires: Int64? = nil, tags: [String]? = nil, metadata: [String: Any]? = nil) throws -> Bool {
|
|
57
|
+
let data: Data
|
|
58
|
+
|
|
59
|
+
if let dataValue = value as? Data {
|
|
60
|
+
data = dataValue
|
|
61
|
+
} else if let stringValue = value as? String {
|
|
62
|
+
data = stringValue.data(using: .utf8) ?? Data()
|
|
63
|
+
} else {
|
|
64
|
+
// Convert to JSON for complex objects
|
|
65
|
+
data = try JSONSerialization.data(withJSONObject: value, options: [])
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
let now = Int64(Date().timeIntervalSince1970 * 1000)
|
|
58
69
|
let tagsJson = tags != nil ? try? JSONSerialization.data(withJSONObject: tags!, options: []) : nil
|
|
59
70
|
let metadataJson = metadata != nil ? try? JSONSerialization.data(withJSONObject: metadata!, options: []) : nil
|
|
@@ -67,7 +78,7 @@ import SQLite3
|
|
|
67
78
|
var statement: OpaquePointer?
|
|
68
79
|
let result = sqlite3_prepare_v2(db, insertSQL, -1, &statement, nil) == SQLITE_OK &&
|
|
69
80
|
sqlite3_bind_text(statement, 1, key, -1, nil) == SQLITE_OK &&
|
|
70
|
-
sqlite3_bind_blob(statement, 2, (
|
|
81
|
+
sqlite3_bind_blob(statement, 2, (data as NSData).bytes, Int32(data.count), nil) == SQLITE_OK &&
|
|
71
82
|
sqlite3_bind_int64(statement, 3, now) == SQLITE_OK &&
|
|
72
83
|
sqlite3_bind_int64(statement, 4, now) == SQLITE_OK &&
|
|
73
84
|
(expires != nil ? sqlite3_bind_int64(statement, 5, expires!) : sqlite3_bind_null(statement, 5)) == SQLITE_OK &&
|
|
@@ -79,6 +90,11 @@ import SQLite3
|
|
|
79
90
|
return result
|
|
80
91
|
}
|
|
81
92
|
|
|
93
|
+
// Convenience method for simple values
|
|
94
|
+
@objc public func set(key: String, value: Any) throws -> Bool {
|
|
95
|
+
return try set(key: key, value: value, expires: nil, tags: nil, metadata: nil)
|
|
96
|
+
}
|
|
97
|
+
|
|
82
98
|
@objc public func get(key: String) -> [String: Any]? {
|
|
83
99
|
let querySQL = "SELECT * FROM \(tableName) WHERE key = ? LIMIT 1"
|
|
84
100
|
var statement: OpaquePointer?
|
|
@@ -187,4 +203,22 @@ import SQLite3
|
|
|
187
203
|
sqlite3_finalize(statement)
|
|
188
204
|
return keys
|
|
189
205
|
}
|
|
206
|
+
|
|
207
|
+
@objc public func size() throws -> (total: Int, count: Int) {
|
|
208
|
+
let querySQL = "SELECT COUNT(*), SUM(LENGTH(value)) FROM \(tableName)"
|
|
209
|
+
var statement: OpaquePointer?
|
|
210
|
+
|
|
211
|
+
var totalSize = 0
|
|
212
|
+
var count = 0
|
|
213
|
+
|
|
214
|
+
if sqlite3_prepare_v2(db, querySQL, -1, &statement, nil) == SQLITE_OK {
|
|
215
|
+
if sqlite3_step(statement) == SQLITE_ROW {
|
|
216
|
+
count = Int(sqlite3_column_int(statement, 0))
|
|
217
|
+
totalSize = Int(sqlite3_column_int64(statement, 1))
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
sqlite3_finalize(statement)
|
|
222
|
+
return (total: totalSize, count: count)
|
|
223
|
+
}
|
|
190
224
|
}
|
|
@@ -59,4 +59,18 @@ import Foundation
|
|
|
59
59
|
@objc public func has(key: String) -> Bool {
|
|
60
60
|
return userDefaults.object(forKey: key) != nil
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
@objc public func size() -> (total: Int, count: Int) {
|
|
64
|
+
let all = userDefaults.dictionaryRepresentation()
|
|
65
|
+
var totalSize = 0
|
|
66
|
+
let count = all.count
|
|
67
|
+
|
|
68
|
+
for (key, value) in all {
|
|
69
|
+
// Estimate size (key + value in bytes)
|
|
70
|
+
totalSize += key.data(using: .utf8)?.count ?? 0
|
|
71
|
+
totalSize += "\(value)".data(using: .utf8)?.count ?? 0
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (total: totalSize, count: count)
|
|
75
|
+
}
|
|
62
76
|
}
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strata-storage",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Zero-dependency universal storage plugin providing a unified API for all storage operations across web, Android, and iOS platforms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -11,18 +11,30 @@ import Security
|
|
|
11
11
|
super.init()
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
@objc public func set(key: String, value:
|
|
14
|
+
@objc public func set(key: String, value: Any) throws -> Bool {
|
|
15
|
+
let data: Data
|
|
16
|
+
|
|
17
|
+
if let dataValue = value as? Data {
|
|
18
|
+
data = dataValue
|
|
19
|
+
} else if let stringValue = value as? String {
|
|
20
|
+
data = stringValue.data(using: .utf8) ?? Data()
|
|
21
|
+
} else {
|
|
22
|
+
// Convert to JSON for complex objects
|
|
23
|
+
let jsonData = try JSONSerialization.data(withJSONObject: value, options: [])
|
|
24
|
+
data = jsonData
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
let query = createQuery(key: key)
|
|
16
28
|
SecItemDelete(query as CFDictionary)
|
|
17
29
|
|
|
18
30
|
var newItem = query
|
|
19
|
-
newItem[kSecValueData as String] =
|
|
31
|
+
newItem[kSecValueData as String] = data
|
|
20
32
|
|
|
21
33
|
let status = SecItemAdd(newItem as CFDictionary, nil)
|
|
22
34
|
return status == errSecSuccess
|
|
23
35
|
}
|
|
24
36
|
|
|
25
|
-
@objc public func get(key: String) ->
|
|
37
|
+
@objc public func get(key: String) throws -> Any? {
|
|
26
38
|
var query = createQuery(key: key)
|
|
27
39
|
query[kSecReturnData as String] = true
|
|
28
40
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
@@ -31,22 +43,29 @@ import Security
|
|
|
31
43
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
32
44
|
|
|
33
45
|
guard status == errSecSuccess else { return nil }
|
|
34
|
-
|
|
46
|
+
guard let data = result as? Data else { return nil }
|
|
47
|
+
|
|
48
|
+
// Try to parse as JSON first, fallback to string
|
|
49
|
+
if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []) {
|
|
50
|
+
return jsonObject
|
|
51
|
+
} else {
|
|
52
|
+
return String(data: data, encoding: .utf8)
|
|
53
|
+
}
|
|
35
54
|
}
|
|
36
55
|
|
|
37
|
-
@objc public func remove(key: String) -> Bool {
|
|
56
|
+
@objc public func remove(key: String) throws -> Bool {
|
|
38
57
|
let query = createQuery(key: key)
|
|
39
58
|
let status = SecItemDelete(query as CFDictionary)
|
|
40
59
|
return status == errSecSuccess
|
|
41
60
|
}
|
|
42
61
|
|
|
43
|
-
@objc public func clear(prefix: String? = nil) -> Bool {
|
|
62
|
+
@objc public func clear(prefix: String? = nil) throws -> Bool {
|
|
44
63
|
if let prefix = prefix {
|
|
45
64
|
// Clear only keys with the given prefix
|
|
46
|
-
let keysToRemove = keys(pattern: prefix)
|
|
65
|
+
let keysToRemove = try keys(pattern: prefix)
|
|
47
66
|
var allSuccess = true
|
|
48
67
|
for key in keysToRemove {
|
|
49
|
-
if !remove(key: key) {
|
|
68
|
+
if !(try remove(key: key)) {
|
|
50
69
|
allSuccess = false
|
|
51
70
|
}
|
|
52
71
|
}
|
|
@@ -62,7 +81,7 @@ import Security
|
|
|
62
81
|
}
|
|
63
82
|
}
|
|
64
83
|
|
|
65
|
-
@objc public func keys(pattern: String? = nil) -> [String] {
|
|
84
|
+
@objc public func keys(pattern: String? = nil) throws -> [String] {
|
|
66
85
|
var query: [String: Any] = [
|
|
67
86
|
kSecClass as String: kSecClassGenericPassword,
|
|
68
87
|
kSecAttrService as String: service,
|
|
@@ -106,4 +125,20 @@ import Security
|
|
|
106
125
|
|
|
107
126
|
return query
|
|
108
127
|
}
|
|
128
|
+
|
|
129
|
+
@objc public func size() throws -> (total: Int, count: Int) {
|
|
130
|
+
let allKeys = try keys()
|
|
131
|
+
var totalSize = 0
|
|
132
|
+
|
|
133
|
+
for key in allKeys {
|
|
134
|
+
if let data = try get(key: key) as? Data {
|
|
135
|
+
totalSize += data.count
|
|
136
|
+
} else if let string = try get(key: key) as? String {
|
|
137
|
+
totalSize += string.data(using: .utf8)?.count ?? 0
|
|
138
|
+
}
|
|
139
|
+
totalSize += key.data(using: .utf8)?.count ?? 0
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return (total: totalSize, count: allKeys.count)
|
|
143
|
+
}
|
|
109
144
|
}
|
|
@@ -53,7 +53,18 @@ import SQLite3
|
|
|
53
53
|
sqlite3_finalize(createTableStatement)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
@objc public func set(key: String, value:
|
|
56
|
+
@objc public func set(key: String, value: Any, expires: Int64? = nil, tags: [String]? = nil, metadata: [String: Any]? = nil) throws -> Bool {
|
|
57
|
+
let data: Data
|
|
58
|
+
|
|
59
|
+
if let dataValue = value as? Data {
|
|
60
|
+
data = dataValue
|
|
61
|
+
} else if let stringValue = value as? String {
|
|
62
|
+
data = stringValue.data(using: .utf8) ?? Data()
|
|
63
|
+
} else {
|
|
64
|
+
// Convert to JSON for complex objects
|
|
65
|
+
data = try JSONSerialization.data(withJSONObject: value, options: [])
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
let now = Int64(Date().timeIntervalSince1970 * 1000)
|
|
58
69
|
let tagsJson = tags != nil ? try? JSONSerialization.data(withJSONObject: tags!, options: []) : nil
|
|
59
70
|
let metadataJson = metadata != nil ? try? JSONSerialization.data(withJSONObject: metadata!, options: []) : nil
|
|
@@ -67,7 +78,7 @@ import SQLite3
|
|
|
67
78
|
var statement: OpaquePointer?
|
|
68
79
|
let result = sqlite3_prepare_v2(db, insertSQL, -1, &statement, nil) == SQLITE_OK &&
|
|
69
80
|
sqlite3_bind_text(statement, 1, key, -1, nil) == SQLITE_OK &&
|
|
70
|
-
sqlite3_bind_blob(statement, 2, (
|
|
81
|
+
sqlite3_bind_blob(statement, 2, (data as NSData).bytes, Int32(data.count), nil) == SQLITE_OK &&
|
|
71
82
|
sqlite3_bind_int64(statement, 3, now) == SQLITE_OK &&
|
|
72
83
|
sqlite3_bind_int64(statement, 4, now) == SQLITE_OK &&
|
|
73
84
|
(expires != nil ? sqlite3_bind_int64(statement, 5, expires!) : sqlite3_bind_null(statement, 5)) == SQLITE_OK &&
|
|
@@ -79,6 +90,11 @@ import SQLite3
|
|
|
79
90
|
return result
|
|
80
91
|
}
|
|
81
92
|
|
|
93
|
+
// Convenience method for simple values
|
|
94
|
+
@objc public func set(key: String, value: Any) throws -> Bool {
|
|
95
|
+
return try set(key: key, value: value, expires: nil, tags: nil, metadata: nil)
|
|
96
|
+
}
|
|
97
|
+
|
|
82
98
|
@objc public func get(key: String) -> [String: Any]? {
|
|
83
99
|
let querySQL = "SELECT * FROM \(tableName) WHERE key = ? LIMIT 1"
|
|
84
100
|
var statement: OpaquePointer?
|
|
@@ -187,4 +203,22 @@ import SQLite3
|
|
|
187
203
|
sqlite3_finalize(statement)
|
|
188
204
|
return keys
|
|
189
205
|
}
|
|
206
|
+
|
|
207
|
+
@objc public func size() throws -> (total: Int, count: Int) {
|
|
208
|
+
let querySQL = "SELECT COUNT(*), SUM(LENGTH(value)) FROM \(tableName)"
|
|
209
|
+
var statement: OpaquePointer?
|
|
210
|
+
|
|
211
|
+
var totalSize = 0
|
|
212
|
+
var count = 0
|
|
213
|
+
|
|
214
|
+
if sqlite3_prepare_v2(db, querySQL, -1, &statement, nil) == SQLITE_OK {
|
|
215
|
+
if sqlite3_step(statement) == SQLITE_ROW {
|
|
216
|
+
count = Int(sqlite3_column_int(statement, 0))
|
|
217
|
+
totalSize = Int(sqlite3_column_int64(statement, 1))
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
sqlite3_finalize(statement)
|
|
222
|
+
return (total: totalSize, count: count)
|
|
223
|
+
}
|
|
190
224
|
}
|