strata-storage 2.6.1 → 2.7.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/AI-INTEGRATION-GUIDE.md +88 -10
- package/README.md +42 -64
- package/android/AGENTS.md +18 -3
- package/android/CLAUDE.md +36 -3
- package/android/build.gradle +5 -1
- package/android/src/main/java/com/strata/storage/EncryptedStorage.java +31 -18
- package/android/src/main/java/com/strata/storage/FilesystemStorage.java +62 -10
- package/android/src/main/java/com/strata/storage/SQLiteStorage.java +65 -13
- package/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +63 -51
- package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +80 -11
- package/dist/README.md +42 -64
- package/dist/adapters/capacitor/FilesystemAdapter.d.ts +2 -2
- package/dist/adapters/capacitor/FilesystemAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/FilesystemAdapter.js +6 -2
- package/dist/adapters/capacitor/PreferencesAdapter.d.ts +2 -2
- package/dist/adapters/capacitor/PreferencesAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/PreferencesAdapter.js +6 -2
- package/dist/adapters/capacitor/SecureAdapter.d.ts +3 -3
- package/dist/adapters/capacitor/SecureAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/SecureAdapter.js +6 -2
- package/dist/adapters/capacitor/SqliteAdapter.d.ts +24 -3
- package/dist/adapters/capacitor/SqliteAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/SqliteAdapter.js +105 -23
- package/dist/adapters/capacitor/index.d.ts +4 -4
- package/dist/adapters/web/CacheAdapter.d.ts +2 -2
- package/dist/adapters/web/CookieAdapter.d.ts +2 -2
- package/dist/adapters/web/IndexedDBAdapter.d.ts +10 -3
- package/dist/adapters/web/IndexedDBAdapter.d.ts.map +1 -1
- package/dist/adapters/web/IndexedDBAdapter.js +14 -3
- package/dist/adapters/web/LocalStorageAdapter.d.ts +2 -2
- package/dist/adapters/web/MemoryAdapter.d.ts +2 -2
- package/dist/adapters/web/SessionStorageAdapter.d.ts +10 -10
- package/dist/adapters/web/SessionStorageAdapter.d.ts.map +1 -1
- package/dist/adapters/web/SessionStorageAdapter.js +3 -2
- package/dist/adapters/web/URLAdapter.d.ts +2 -2
- package/dist/adapters/web/index.d.ts +7 -7
- package/dist/capacitor.d.ts +6 -6
- package/dist/capacitor.d.ts.map +1 -1
- package/dist/capacitor.js +5 -4
- package/dist/core/AdapterRegistry.d.ts +1 -1
- package/dist/core/BaseAdapter.d.ts +21 -5
- package/dist/core/BaseAdapter.d.ts.map +1 -1
- package/dist/core/BaseAdapter.js +29 -1
- package/dist/core/Strata.d.ts +41 -7
- package/dist/core/Strata.d.ts.map +1 -1
- package/dist/core/Strata.js +476 -118
- package/dist/features/compression/index.d.ts +1 -1
- package/dist/features/compression.d.ts +5 -1
- package/dist/features/compression.d.ts.map +1 -1
- package/dist/features/encryption/index.d.ts +1 -1
- package/dist/features/encryption.d.ts +55 -2
- package/dist/features/encryption.d.ts.map +1 -1
- package/dist/features/encryption.js +139 -26
- package/dist/features/migration.d.ts +1 -1
- package/dist/features/observer/index.d.ts +1 -1
- package/dist/features/observer.d.ts +1 -1
- package/dist/features/query/index.d.ts +1 -1
- package/dist/features/query.d.ts +1 -1
- package/dist/features/query.d.ts.map +1 -1
- package/dist/features/query.js +3 -1
- package/dist/features/sync/index.d.ts +1 -1
- package/dist/features/sync.d.ts +19 -3
- package/dist/features/sync.d.ts.map +1 -1
- package/dist/features/sync.js +57 -9
- package/dist/features/ttl/index.d.ts +1 -1
- package/dist/features/ttl.d.ts +2 -2
- package/dist/firebase.d.ts +1 -1
- package/dist/firebase.d.ts.map +1 -1
- package/dist/firebase.js +34 -9
- package/dist/index.d.ts +24 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/integrations/angular/index.d.ts +2 -2
- package/dist/integrations/angular/index.d.ts.map +1 -1
- package/dist/integrations/angular/index.js +7 -8
- package/dist/integrations/react/index.d.ts +3 -3
- package/dist/integrations/react/index.d.ts.map +1 -1
- package/dist/integrations/react/index.js +16 -4
- package/dist/integrations/vue/index.d.ts +2 -2
- package/dist/integrations/vue/index.d.ts.map +1 -1
- package/dist/integrations/vue/index.js +5 -2
- package/dist/package.json +11 -2
- package/dist/plugin/definitions.d.ts +29 -3
- package/dist/plugin/definitions.d.ts.map +1 -1
- package/dist/plugin/index.d.ts +2 -2
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +3 -0
- package/dist/plugin/web.d.ts +11 -2
- package/dist/plugin/web.d.ts.map +1 -1
- package/dist/plugin/web.js +23 -4
- package/dist/types/index.d.ts +50 -20
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -10
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +7 -0
- package/ios/AGENTS.md +14 -3
- package/ios/CLAUDE.md +25 -3
- package/ios/Plugin/FilesystemStorage.swift +19 -0
- package/ios/Plugin/SQLiteStorage.swift +59 -9
- package/ios/Plugin/StrataStoragePlugin.m +1 -0
- package/ios/Plugin/StrataStoragePlugin.swift +87 -34
- package/ios/Plugin/UserDefaultsStorage.swift +47 -46
- package/package.json +27 -24
- package/scripts/build.js +16 -14
- package/scripts/cli.js +1 -1
- package/scripts/configure.js +2 -2
- package/scripts/postinstall.js +3 -2
- package/dist/android/AGENTS.md +0 -51
- package/dist/android/CLAUDE.md +0 -89
- package/dist/android/build.gradle +0 -58
- package/dist/android/proguard-rules.pro +0 -11
- package/dist/android/settings.gradle +0 -2
- package/dist/android/src/main/AndroidManifest.xml +0 -2
- package/dist/android/src/main/java/com/strata/storage/EncryptedStorage.java +0 -218
- package/dist/android/src/main/java/com/strata/storage/FilesystemStorage.java +0 -287
- package/dist/android/src/main/java/com/strata/storage/SQLiteStorage.java +0 -371
- package/dist/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +0 -209
- package/dist/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +0 -616
- package/dist/core/StorageStrategy.d.ts +0 -55
- package/dist/core/StorageStrategy.d.ts.map +0 -1
- package/dist/core/StorageStrategy.js +0 -200
- package/dist/ios/AGENTS.md +0 -48
- package/dist/ios/CLAUDE.md +0 -84
- package/dist/ios/Plugin/FilesystemStorage.swift +0 -218
- package/dist/ios/Plugin/KeychainStorage.swift +0 -233
- package/dist/ios/Plugin/SQLiteStorage.swift +0 -426
- package/dist/ios/Plugin/StrataStoragePlugin.m +0 -23
- package/dist/ios/Plugin/StrataStoragePlugin.swift +0 -411
- package/dist/ios/Plugin/UserDefaultsStorage.swift +0 -87
|
@@ -1,411 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
import Capacitor
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Main Capacitor plugin for Strata Storage
|
|
6
|
-
* Coordinates between different storage types on iOS.
|
|
7
|
-
*
|
|
8
|
-
* NOTE: Method registration with the Capacitor bridge lives in
|
|
9
|
-
* StrataStoragePlugin.m (CAP_PLUGIN / CAP_PLUGIN_METHOD). Keep the two in
|
|
10
|
-
* sync when adding or renaming methods.
|
|
11
|
-
*/
|
|
12
|
-
@objc(StrataStoragePlugin)
|
|
13
|
-
public class StrataStoragePlugin: CAPPlugin {
|
|
14
|
-
private let userDefaultsStorage = UserDefaultsStorage()
|
|
15
|
-
private let keychainStorage = KeychainStorage()
|
|
16
|
-
private let filesystemStorage = FilesystemStorage()
|
|
17
|
-
|
|
18
|
-
/// Storage types that have a real native backend on iOS.
|
|
19
|
-
private let supportedStorageTypes: Set<String> = ["preferences", "secure", "sqlite", "filesystem"]
|
|
20
|
-
|
|
21
|
-
// MARK: - SQLite multi-store helpers
|
|
22
|
-
|
|
23
|
-
/// Default database name (→ file `strata_storage.db`) and table.
|
|
24
|
-
private static let defaultDatabase = "strata_storage"
|
|
25
|
-
private static let defaultTable = "storage"
|
|
26
|
-
|
|
27
|
-
/// Sanitizes a database name into a safe `.db` filename. Strips everything
|
|
28
|
-
/// outside `[A-Za-z0-9_]` (which also blocks path separators / traversal),
|
|
29
|
-
/// then appends `.db`. Empty input falls back to the default. The filename
|
|
30
|
-
/// CANNOT be a bound parameter, so it must be sanitized before use.
|
|
31
|
-
private func sqliteFileName(from database: String?) -> String {
|
|
32
|
-
let raw = database ?? StrataStoragePlugin.defaultDatabase
|
|
33
|
-
let cleaned = String(raw.unicodeScalars.filter { CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_").contains($0) })
|
|
34
|
-
let safe = cleaned.isEmpty ? StrataStoragePlugin.defaultDatabase : cleaned
|
|
35
|
-
return "\(safe).db"
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/// Sanitizes a table name to a safe SQL identifier matching `^[A-Za-z0-9_]+$`.
|
|
39
|
-
/// Disallowed characters are removed; empty input falls back to the default.
|
|
40
|
-
/// The table name is interpolated into SQL (it cannot be bound), so this is
|
|
41
|
-
/// the single point that guarantees injection-safety for the identifier.
|
|
42
|
-
private func sqliteTableName(from table: String?) -> String {
|
|
43
|
-
let raw = table ?? StrataStoragePlugin.defaultTable
|
|
44
|
-
let cleaned = String(raw.unicodeScalars.filter { CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_").contains($0) })
|
|
45
|
-
return cleaned.isEmpty ? StrataStoragePlugin.defaultTable : cleaned
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/// Returns the cached SQLite store for the call's `database` option.
|
|
49
|
-
private func sqliteStore(for call: CAPPluginCall) -> SQLiteStorage {
|
|
50
|
-
return SQLiteStorageManager.shared.store(forFile: sqliteFileName(from: call.getString("database")))
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Check if a specific storage type is available.
|
|
55
|
-
* Matches the JS contract: resolves `{ available: boolean }`.
|
|
56
|
-
*/
|
|
57
|
-
@objc func isAvailable(_ call: CAPPluginCall) {
|
|
58
|
-
let storage = call.getString("storage") ?? "preferences"
|
|
59
|
-
call.resolve([
|
|
60
|
-
"available": supportedStorageTypes.contains(storage)
|
|
61
|
-
])
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Get value from storage.
|
|
66
|
-
* For sqlite/filesystem the resolved `value` is the full StorageValue
|
|
67
|
-
* wrapper object (or NSNull on a miss).
|
|
68
|
-
*/
|
|
69
|
-
@objc func get(_ call: CAPPluginCall) {
|
|
70
|
-
guard let key = call.getString("key") else {
|
|
71
|
-
call.reject("Key is required")
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
let storage = call.getString("storage") ?? "preferences"
|
|
76
|
-
|
|
77
|
-
do {
|
|
78
|
-
let value: Any?
|
|
79
|
-
|
|
80
|
-
switch storage {
|
|
81
|
-
case "secure":
|
|
82
|
-
value = try keychainStorage.get(key: key)
|
|
83
|
-
case "sqlite":
|
|
84
|
-
value = sqliteStore(for: call).get(table: sqliteTableName(from: call.getString("table")), key: key)
|
|
85
|
-
case "filesystem":
|
|
86
|
-
value = filesystemStorage.get(key: key)
|
|
87
|
-
case "preferences":
|
|
88
|
-
fallthrough
|
|
89
|
-
default:
|
|
90
|
-
value = userDefaultsStorage.get(key: key)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
call.resolve([
|
|
94
|
-
"value": value ?? NSNull()
|
|
95
|
-
])
|
|
96
|
-
} catch {
|
|
97
|
-
call.reject("Failed to get value", nil, error)
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Set value in storage.
|
|
103
|
-
* For sqlite/filesystem the `value` option is the full StorageValue
|
|
104
|
-
* wrapper object and is stored verbatim (JSON) for a perfect round-trip.
|
|
105
|
-
*/
|
|
106
|
-
@objc func set(_ call: CAPPluginCall) {
|
|
107
|
-
guard let key = call.getString("key") else {
|
|
108
|
-
call.reject("Key is required")
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
let storage = call.getString("storage") ?? "preferences"
|
|
113
|
-
|
|
114
|
-
do {
|
|
115
|
-
switch storage {
|
|
116
|
-
case "secure":
|
|
117
|
-
let value = call.getValue("value") ?? NSNull()
|
|
118
|
-
_ = try keychainStorage.set(key: key, value: value)
|
|
119
|
-
case "sqlite":
|
|
120
|
-
// getObject returns JSObject ([String: JSValue]); upcast each
|
|
121
|
-
// value to Any (the canonical Capacitor pattern) so the bridged
|
|
122
|
-
// Foundation values are JSONSerialization-compatible.
|
|
123
|
-
guard let jsObject = call.getObject("value") else {
|
|
124
|
-
call.reject("SQLite value must be a StorageValue object")
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
let ok = try sqliteStore(for: call).set(
|
|
128
|
-
table: sqliteTableName(from: call.getString("table")),
|
|
129
|
-
key: key,
|
|
130
|
-
wrapper: jsObject as [String: Any]
|
|
131
|
-
)
|
|
132
|
-
if !ok {
|
|
133
|
-
call.reject("Failed to write value to SQLite")
|
|
134
|
-
return
|
|
135
|
-
}
|
|
136
|
-
case "filesystem":
|
|
137
|
-
guard let jsObject = call.getObject("value") else {
|
|
138
|
-
call.reject("Filesystem value must be a StorageValue object")
|
|
139
|
-
return
|
|
140
|
-
}
|
|
141
|
-
let ok = try filesystemStorage.set(key: key, wrapper: jsObject as [String: Any])
|
|
142
|
-
if !ok {
|
|
143
|
-
call.reject("Failed to write value to filesystem")
|
|
144
|
-
return
|
|
145
|
-
}
|
|
146
|
-
case "preferences":
|
|
147
|
-
fallthrough
|
|
148
|
-
default:
|
|
149
|
-
let value = call.getValue("value") ?? NSNull()
|
|
150
|
-
userDefaultsStorage.set(key: key, value: value)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
call.resolve()
|
|
154
|
-
} catch {
|
|
155
|
-
call.reject("Failed to set value", nil, error)
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Remove value from storage
|
|
161
|
-
*/
|
|
162
|
-
@objc func remove(_ call: CAPPluginCall) {
|
|
163
|
-
guard let key = call.getString("key") else {
|
|
164
|
-
call.reject("Key is required")
|
|
165
|
-
return
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
let storage = call.getString("storage") ?? "preferences"
|
|
169
|
-
|
|
170
|
-
do {
|
|
171
|
-
switch storage {
|
|
172
|
-
case "secure":
|
|
173
|
-
_ = try keychainStorage.remove(key: key)
|
|
174
|
-
case "sqlite":
|
|
175
|
-
_ = sqliteStore(for: call).remove(table: sqliteTableName(from: call.getString("table")), key: key)
|
|
176
|
-
case "filesystem":
|
|
177
|
-
_ = filesystemStorage.remove(key: key)
|
|
178
|
-
case "preferences":
|
|
179
|
-
fallthrough
|
|
180
|
-
default:
|
|
181
|
-
userDefaultsStorage.remove(key: key)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
call.resolve()
|
|
185
|
-
} catch {
|
|
186
|
-
call.reject("Failed to remove value", nil, error)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Clear storage.
|
|
192
|
-
* The JS contract sends an optional `pattern`. The native backends accept
|
|
193
|
-
* a prefix; we use `pattern` as that prefix (prefix/contains matching).
|
|
194
|
-
*/
|
|
195
|
-
@objc func clear(_ call: CAPPluginCall) {
|
|
196
|
-
let storage = call.getString("storage") ?? "preferences"
|
|
197
|
-
// Accept both `pattern` (JS contract) and legacy `prefix`.
|
|
198
|
-
let prefix = call.getString("pattern") ?? call.getString("prefix")
|
|
199
|
-
|
|
200
|
-
do {
|
|
201
|
-
switch storage {
|
|
202
|
-
case "secure":
|
|
203
|
-
_ = try keychainStorage.clear(prefix: prefix)
|
|
204
|
-
case "sqlite":
|
|
205
|
-
_ = sqliteStore(for: call).clear(table: sqliteTableName(from: call.getString("table")), prefix: prefix)
|
|
206
|
-
case "filesystem":
|
|
207
|
-
_ = filesystemStorage.clear(prefix: prefix)
|
|
208
|
-
case "preferences":
|
|
209
|
-
fallthrough
|
|
210
|
-
default:
|
|
211
|
-
userDefaultsStorage.clear(prefix: prefix)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
call.resolve()
|
|
215
|
-
} catch {
|
|
216
|
-
call.reject("Failed to clear storage", nil, error)
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Get all keys from storage
|
|
222
|
-
*/
|
|
223
|
-
@objc func keys(_ call: CAPPluginCall) {
|
|
224
|
-
let storage = call.getString("storage") ?? "preferences"
|
|
225
|
-
let pattern = call.getString("pattern")
|
|
226
|
-
|
|
227
|
-
do {
|
|
228
|
-
let keys: [String]
|
|
229
|
-
|
|
230
|
-
switch storage {
|
|
231
|
-
case "secure":
|
|
232
|
-
keys = try keychainStorage.keys(pattern: pattern)
|
|
233
|
-
case "sqlite":
|
|
234
|
-
keys = sqliteStore(for: call).keys(table: sqliteTableName(from: call.getString("table")), pattern: pattern)
|
|
235
|
-
case "filesystem":
|
|
236
|
-
keys = filesystemStorage.keys(pattern: pattern)
|
|
237
|
-
case "preferences":
|
|
238
|
-
fallthrough
|
|
239
|
-
default:
|
|
240
|
-
keys = userDefaultsStorage.keys(pattern: pattern)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
call.resolve([
|
|
244
|
-
"keys": keys
|
|
245
|
-
])
|
|
246
|
-
} catch {
|
|
247
|
-
call.reject("Failed to get keys", nil, error)
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Get storage size information.
|
|
253
|
-
* When `detailed` is true, resolves `{ total, count, detailed: { keys,
|
|
254
|
-
* values, metadata } }`; otherwise `{ total, count }`.
|
|
255
|
-
*/
|
|
256
|
-
@objc func size(_ call: CAPPluginCall) {
|
|
257
|
-
let storage = call.getString("storage") ?? "preferences"
|
|
258
|
-
let detailed = call.getBool("detailed") ?? false
|
|
259
|
-
|
|
260
|
-
do {
|
|
261
|
-
// segments carries total/count/keys/values/metadata in bytes.
|
|
262
|
-
let segments: [String: Int]
|
|
263
|
-
|
|
264
|
-
switch storage {
|
|
265
|
-
case "secure":
|
|
266
|
-
let info = try keychainStorage.size()
|
|
267
|
-
segments = ["total": info.total, "count": info.count, "keys": 0, "values": info.total, "metadata": 0]
|
|
268
|
-
case "sqlite":
|
|
269
|
-
segments = try sqliteStore(for: call).size(table: sqliteTableName(from: call.getString("table")))
|
|
270
|
-
case "filesystem":
|
|
271
|
-
segments = filesystemStorage.size()
|
|
272
|
-
case "preferences":
|
|
273
|
-
fallthrough
|
|
274
|
-
default:
|
|
275
|
-
let info = userDefaultsStorage.size()
|
|
276
|
-
segments = ["total": info.total, "count": info.count, "keys": 0, "values": info.total, "metadata": 0]
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
var result: [String: Any] = [
|
|
280
|
-
"total": segments["total"] ?? 0,
|
|
281
|
-
"count": segments["count"] ?? 0
|
|
282
|
-
]
|
|
283
|
-
if detailed {
|
|
284
|
-
result["detailed"] = [
|
|
285
|
-
"keys": segments["keys"] ?? 0,
|
|
286
|
-
"values": segments["values"] ?? 0,
|
|
287
|
-
"metadata": segments["metadata"] ?? 0
|
|
288
|
-
]
|
|
289
|
-
}
|
|
290
|
-
call.resolve(result)
|
|
291
|
-
} catch {
|
|
292
|
-
call.reject("Failed to get size", nil, error)
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Query SQLite-backed storage.
|
|
298
|
-
* Matches the optional `query` method in the JS contract:
|
|
299
|
-
* resolves `{ results: [{ key }] }` (JS re-fetches each value via get).
|
|
300
|
-
*/
|
|
301
|
-
@objc func query(_ call: CAPPluginCall) {
|
|
302
|
-
let storage = call.getString("storage") ?? "sqlite"
|
|
303
|
-
|
|
304
|
-
guard storage == "sqlite" else {
|
|
305
|
-
call.reject("Query is only supported for the 'sqlite' storage type")
|
|
306
|
-
return
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
let condition = call.getObject("condition") ?? [:]
|
|
310
|
-
let results = sqliteStore(for: call).query(
|
|
311
|
-
table: sqliteTableName(from: call.getString("table")),
|
|
312
|
-
condition: condition
|
|
313
|
-
)
|
|
314
|
-
call.resolve([
|
|
315
|
-
"results": results
|
|
316
|
-
])
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* iOS-specific: read a UserDefaults value.
|
|
321
|
-
* Resolves `{ value: unknown }`.
|
|
322
|
-
*/
|
|
323
|
-
@objc func getUserDefaults(_ call: CAPPluginCall) {
|
|
324
|
-
guard let key = call.getString("key") else {
|
|
325
|
-
call.reject("Key is required")
|
|
326
|
-
return
|
|
327
|
-
}
|
|
328
|
-
let suiteName = call.getString("suiteName")
|
|
329
|
-
let store = suiteName != nil ? UserDefaultsStorage(suiteName: suiteName) : userDefaultsStorage
|
|
330
|
-
call.resolve([
|
|
331
|
-
"value": store.get(key: key) ?? NSNull()
|
|
332
|
-
])
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* iOS-specific: write a UserDefaults value.
|
|
337
|
-
*/
|
|
338
|
-
@objc func setUserDefaults(_ call: CAPPluginCall) {
|
|
339
|
-
guard let key = call.getString("key") else {
|
|
340
|
-
call.reject("Key is required")
|
|
341
|
-
return
|
|
342
|
-
}
|
|
343
|
-
let value = call.getValue("value") ?? NSNull()
|
|
344
|
-
let suiteName = call.getString("suiteName")
|
|
345
|
-
let store = suiteName != nil ? UserDefaultsStorage(suiteName: suiteName) : userDefaultsStorage
|
|
346
|
-
_ = store.set(key: key, value: value)
|
|
347
|
-
call.resolve()
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* iOS-specific: read a Keychain value. Resolves `{ value: string | null }`.
|
|
352
|
-
*/
|
|
353
|
-
@objc func getKeychain(_ call: CAPPluginCall) {
|
|
354
|
-
guard let key = call.getString("key") else {
|
|
355
|
-
call.reject("Key is required")
|
|
356
|
-
return
|
|
357
|
-
}
|
|
358
|
-
let service = call.getString("service")
|
|
359
|
-
let accessGroup = call.getString("accessGroup")
|
|
360
|
-
let store = (service != nil || accessGroup != nil)
|
|
361
|
-
? KeychainStorage(service: service, accessGroup: accessGroup)
|
|
362
|
-
: keychainStorage
|
|
363
|
-
|
|
364
|
-
do {
|
|
365
|
-
let value = try store.get(key: key)
|
|
366
|
-
// The JS contract types this as `string | null`. Serialize
|
|
367
|
-
// non-string values to a JSON string so the bridge stays typed.
|
|
368
|
-
if let value = value {
|
|
369
|
-
if let str = value as? String {
|
|
370
|
-
call.resolve(["value": str])
|
|
371
|
-
} else if let data = try? JSONSerialization.data(withJSONObject: value, options: []),
|
|
372
|
-
let str = String(data: data, encoding: .utf8) {
|
|
373
|
-
call.resolve(["value": str])
|
|
374
|
-
} else {
|
|
375
|
-
call.resolve(["value": NSNull()])
|
|
376
|
-
}
|
|
377
|
-
} else {
|
|
378
|
-
call.resolve(["value": NSNull()])
|
|
379
|
-
}
|
|
380
|
-
} catch {
|
|
381
|
-
call.reject("Failed to read keychain item", nil, error)
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* iOS-specific: write a Keychain value, honoring the `accessible` option.
|
|
387
|
-
*/
|
|
388
|
-
@objc func setKeychain(_ call: CAPPluginCall) {
|
|
389
|
-
guard let key = call.getString("key") else {
|
|
390
|
-
call.reject("Key is required")
|
|
391
|
-
return
|
|
392
|
-
}
|
|
393
|
-
guard let value = call.getString("value") else {
|
|
394
|
-
call.reject("Value is required and must be a string")
|
|
395
|
-
return
|
|
396
|
-
}
|
|
397
|
-
let service = call.getString("service")
|
|
398
|
-
let accessGroup = call.getString("accessGroup")
|
|
399
|
-
let accessible = call.getString("accessible")
|
|
400
|
-
let store = (service != nil || accessGroup != nil)
|
|
401
|
-
? KeychainStorage(service: service, accessGroup: accessGroup)
|
|
402
|
-
: keychainStorage
|
|
403
|
-
|
|
404
|
-
do {
|
|
405
|
-
_ = try store.set(key: key, value: value, accessible: accessible)
|
|
406
|
-
call.resolve()
|
|
407
|
-
} catch {
|
|
408
|
-
call.reject("Failed to write keychain item", nil, error)
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
|
|
3
|
-
@objc public class UserDefaultsStorage: NSObject {
|
|
4
|
-
private let suiteName: String?
|
|
5
|
-
private var userDefaults: UserDefaults
|
|
6
|
-
|
|
7
|
-
@objc public init(suiteName: String? = nil) {
|
|
8
|
-
self.suiteName = suiteName
|
|
9
|
-
if let suiteName = suiteName, let customDefaults = UserDefaults(suiteName: suiteName) {
|
|
10
|
-
self.userDefaults = customDefaults
|
|
11
|
-
} else {
|
|
12
|
-
self.userDefaults = UserDefaults.standard
|
|
13
|
-
}
|
|
14
|
-
super.init()
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
@objc public func set(key: String, value: Any) -> Bool {
|
|
18
|
-
userDefaults.set(value, forKey: key)
|
|
19
|
-
return userDefaults.synchronize()
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
@objc public func get(key: String) -> Any? {
|
|
23
|
-
return userDefaults.object(forKey: key)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
@objc public func remove(key: String) -> Bool {
|
|
27
|
-
userDefaults.removeObject(forKey: key)
|
|
28
|
-
return userDefaults.synchronize()
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
@objc public func clear(prefix: String? = nil) -> Bool {
|
|
32
|
-
if let prefix = prefix {
|
|
33
|
-
// Clear only keys with the given prefix
|
|
34
|
-
let keysToRemove = keys(pattern: prefix)
|
|
35
|
-
for key in keysToRemove {
|
|
36
|
-
userDefaults.removeObject(forKey: key)
|
|
37
|
-
}
|
|
38
|
-
} else {
|
|
39
|
-
// Clear all keys
|
|
40
|
-
if let suiteName = suiteName {
|
|
41
|
-
UserDefaults(suiteName: suiteName)?.removePersistentDomain(forName: suiteName)
|
|
42
|
-
} else {
|
|
43
|
-
guard let domain = Bundle.main.bundleIdentifier else {
|
|
44
|
-
// Fallback: manually clear all keys
|
|
45
|
-
let keys = Array(userDefaults.dictionaryRepresentation().keys)
|
|
46
|
-
for key in keys {
|
|
47
|
-
userDefaults.removeObject(forKey: key)
|
|
48
|
-
}
|
|
49
|
-
return userDefaults.synchronize()
|
|
50
|
-
}
|
|
51
|
-
userDefaults.removePersistentDomain(forName: domain)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return userDefaults.synchronize()
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
@objc public func keys(pattern: String? = nil) -> [String] {
|
|
58
|
-
let allKeys = Array(userDefaults.dictionaryRepresentation().keys)
|
|
59
|
-
|
|
60
|
-
guard let pattern = pattern else {
|
|
61
|
-
return allKeys
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Filter keys by pattern (simple prefix matching)
|
|
65
|
-
return allKeys.filter { key in
|
|
66
|
-
key.hasPrefix(pattern) || key.contains(pattern)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
@objc public func has(key: String) -> Bool {
|
|
71
|
-
return userDefaults.object(forKey: key) != nil
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
@objc public func size() -> (total: Int, count: Int) {
|
|
75
|
-
let all = userDefaults.dictionaryRepresentation()
|
|
76
|
-
var totalSize = 0
|
|
77
|
-
let count = all.count
|
|
78
|
-
|
|
79
|
-
for (key, value) in all {
|
|
80
|
-
// Estimate size (key + value in bytes)
|
|
81
|
-
totalSize += key.data(using: .utf8)?.count ?? 0
|
|
82
|
-
totalSize += "\(value)".data(using: .utf8)?.count ?? 0
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return (total: totalSize, count: count)
|
|
86
|
-
}
|
|
87
|
-
}
|