strata-storage 2.6.1 → 2.7.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.
- package/AI-INTEGRATION-GUIDE.md +129 -11
- 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/CacheAdapter.d.ts.map +1 -1
- package/dist/adapters/web/CacheAdapter.js +20 -5
- package/dist/adapters/web/CookieAdapter.d.ts +2 -2
- package/dist/adapters/web/CookieAdapter.d.ts.map +1 -1
- package/dist/adapters/web/CookieAdapter.js +27 -3
- 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 +19 -4
- package/dist/adapters/web/LocalStorageAdapter.d.ts +2 -2
- package/dist/adapters/web/LocalStorageAdapter.d.ts.map +1 -1
- package/dist/adapters/web/LocalStorageAdapter.js +15 -1
- package/dist/adapters/web/MemoryAdapter.d.ts +9 -2
- package/dist/adapters/web/MemoryAdapter.d.ts.map +1 -1
- package/dist/adapters/web/MemoryAdapter.js +22 -5
- 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 +17 -3
- 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 +53 -7
- package/dist/core/Strata.d.ts.map +1 -1
- package/dist/core/Strata.js +581 -125
- package/dist/features/compression/index.d.ts +1 -1
- package/dist/features/compression.d.ts +28 -9
- package/dist/features/compression.d.ts.map +1 -1
- package/dist/features/compression.js +164 -77
- 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/migration.d.ts.map +1 -1
- package/dist/features/migration.js +7 -2
- 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 +2 -1
- package/dist/features/query.d.ts.map +1 -1
- package/dist/features/query.js +32 -7
- 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 +75 -12
- package/dist/features/ttl/index.d.ts +1 -1
- package/dist/features/ttl.d.ts +2 -2
- package/dist/features/ttl.d.ts.map +1 -1
- package/dist/features/ttl.js +4 -2
- package/dist/firebase.d.ts +1 -1
- package/dist/firebase.d.ts.map +1 -1
- package/dist/firebase.js +72 -10
- 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 +46 -27
- 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
package/AI-INTEGRATION-GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AI Integration Guide - strata-storage
|
|
2
2
|
|
|
3
|
-
Quick reference for AI development agents (Claude Code, Cursor, Copilot, etc.) to integrate `strata-storage` into web and mobile projects. Current version: **2.
|
|
3
|
+
Quick reference for AI development agents (Claude Code, Cursor, Copilot, etc.) to integrate `strata-storage` into web and mobile projects. Current version: **2.7.0**.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -18,6 +18,36 @@ Zero runtime dependencies. React, Vue, Angular, and `@capacitor/core` are option
|
|
|
18
18
|
- **One API, many backends** — `storage` types: `memory`, `localStorage`, `sessionStorage`, `indexedDB`, `cookies`, `cache`, `url`, plus native `preferences`, `secure`, `sqlite`, `filesystem` (Capacitor).
|
|
19
19
|
- **Opt-in features** — encryption, compression, TTL, queries, cross-tab sync, integrity, durable writes, mirroring, snapshots. All off by default.
|
|
20
20
|
|
|
21
|
+
## Entry points (import map)
|
|
22
|
+
|
|
23
|
+
| Import | Use for |
|
|
24
|
+
|--------|---------|
|
|
25
|
+
| `strata-storage` | Core API + all web adapters (`memory`, `localStorage`, `sessionStorage`, `indexedDB`, `cookies`, `cache`, `url`). |
|
|
26
|
+
| `strata-storage/capacitor` | Native mobile adapters: `PreferencesAdapter`, `SecureAdapter`, `SqliteAdapter`, `FilesystemAdapter`. |
|
|
27
|
+
| `strata-storage/firebase` | `enableFirebaseSync`, `isFirebaseAvailable` — cross-device Firestore / Realtime DB sync. `firebase` is an optional peer dep. |
|
|
28
|
+
| `strata-storage/react` | `createStrataHooks` (provider-free) + `<StrataProvider>`. |
|
|
29
|
+
| `strata-storage/vue` | `createStrataComposables` + `StrataPlugin`. |
|
|
30
|
+
| `strata-storage/angular` | `provideStrata` / `StrataService` (RxJS). |
|
|
31
|
+
|
|
32
|
+
## Choosing a backend (decision guide)
|
|
33
|
+
|
|
34
|
+
Pick the `storage` per the host project's needs:
|
|
35
|
+
|
|
36
|
+
- **Web, small & persistent** → `localStorage` (sync-capable).
|
|
37
|
+
- **Web, large / structured / queryable** → `indexedDB`.
|
|
38
|
+
- **Web, offline assets / big blobs** → `cache`.
|
|
39
|
+
- **Shareable UI state in the URL** → `url`.
|
|
40
|
+
- **Server-readable small data** → `cookies`.
|
|
41
|
+
- **Temporary / tests** → `memory`.
|
|
42
|
+
- **Mobile (Capacitor), sensitive** → `secure` (Keychain/Keystore).
|
|
43
|
+
- **Mobile, structured / multi-store** → `sqlite` (isolated `(database, table)`).
|
|
44
|
+
- **Mobile, document-shaped values** → `filesystem`.
|
|
45
|
+
- **Mobile, simple settings** → `preferences`.
|
|
46
|
+
- **Cross-device / multi-user sync** → `firestore` or `realtime` (Firebase).
|
|
47
|
+
|
|
48
|
+
Layer features on top regardless of backend: `encrypt`, `compress`, `ttl`,
|
|
49
|
+
`tags`+`query`, cross-tab `sync`, `integrity`/`durable`/`mirror`.
|
|
50
|
+
|
|
21
51
|
## Quick Start
|
|
22
52
|
|
|
23
53
|
### Default instance (simplest)
|
|
@@ -51,7 +81,7 @@ await storage.set('key', value, {
|
|
|
51
81
|
ttl: 3_600_000, // expire in 1 hour (ms)
|
|
52
82
|
encrypt: true, // async only — requires encryption config + password
|
|
53
83
|
compress: true, // async only
|
|
54
|
-
tags: ['user-data'], //
|
|
84
|
+
tags: ['user-data'], // grouping; filter with clear({ tags })
|
|
55
85
|
metadata: { version: 1 },
|
|
56
86
|
verify: true, // store + check an integrity checksum (2.5.0)
|
|
57
87
|
durable: true, // read back and verify the write (2.5.0)
|
|
@@ -60,6 +90,27 @@ await storage.set('key', value, {
|
|
|
60
90
|
const value = await storage.get('key', { storage: 'indexedDB' });
|
|
61
91
|
```
|
|
62
92
|
|
|
93
|
+
## Query data model
|
|
94
|
+
|
|
95
|
+
`query(condition, options?)` matches the condition against the **stored value's own
|
|
96
|
+
bare fields** (the decoded user value) using MongoDB-style operators
|
|
97
|
+
(`$eq $ne $gt $gte $lt $lte $in $nin $regex $exists $type $and $or $not`):
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// matches stored values whose own `status` === 'active' AND `score` >= 10
|
|
101
|
+
const rows = await storage.query<{ status: string; score: number }>({
|
|
102
|
+
status: 'active',
|
|
103
|
+
score: { $gte: 10 },
|
|
104
|
+
}); // => Array<{ key: string; value: T }>
|
|
105
|
+
|
|
106
|
+
// sort/skip/limit/select use bare value field names (no `value.` prefix)
|
|
107
|
+
await storage.query({ status: 'active' }, { sort: { score: -1 }, limit: 20 });
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The wrapper fields `key`, `tags`, `created`, `updated`, `expires`, and `metadata`
|
|
111
|
+
are **NOT** queryable via `query()` — they return nothing. Filter by tag with
|
|
112
|
+
`clear({ tags })`, not `query()`.
|
|
113
|
+
|
|
63
114
|
## Synchronous API (2.5.0)
|
|
64
115
|
|
|
65
116
|
No `await`. Works only on sync-capable adapters: `memory`, `localStorage`, `sessionStorage`, `cookies`, `url`.
|
|
@@ -162,14 +213,14 @@ const storage = defineStorage();
|
|
|
162
213
|
import { defineStorage } from 'strata-storage';
|
|
163
214
|
import {
|
|
164
215
|
PreferencesAdapter,
|
|
165
|
-
|
|
216
|
+
SecureAdapter,
|
|
166
217
|
SqliteAdapter,
|
|
167
218
|
FilesystemAdapter,
|
|
168
219
|
} from 'strata-storage/capacitor';
|
|
169
220
|
|
|
170
221
|
const storage = defineStorage();
|
|
171
222
|
storage.registerAdapter(new PreferencesAdapter());
|
|
172
|
-
storage.registerAdapter(new
|
|
223
|
+
storage.registerAdapter(new SecureAdapter());
|
|
173
224
|
storage.registerAdapter(new SqliteAdapter()); // 2.6.0 — multi-store (database + table)
|
|
174
225
|
storage.registerAdapter(new FilesystemAdapter()); // 2.6.0 — file-per-key, atomic writes
|
|
175
226
|
await storage.set('secret', token, { storage: 'secure' });
|
|
@@ -177,7 +228,51 @@ await storage.set('secret', token, { storage: 'secure' });
|
|
|
177
228
|
|
|
178
229
|
**SQLite multi-store** (2.6.0): each `new SqliteAdapter({ database, table })` is an isolated store — distinct `(database, table)` pairs map to distinct physical `.db` files / tables natively and cannot collide. **Filesystem** (2.6.0): one JSON file per key under the app's documents/files directory; `isAvailable()` now returns `true` on iOS and Android.
|
|
179
230
|
|
|
180
|
-
Native adapters depend on the downstream Capacitor project setup; follow
|
|
231
|
+
Native adapters depend on the downstream Capacitor project setup; follow the [device-verification guide](https://stratastorage-docs.aoneahsan.com/guides/platforms/device-verification) to verify on a real iOS + Android device.
|
|
232
|
+
|
|
233
|
+
### Firebase cross-device sync (optional)
|
|
234
|
+
|
|
235
|
+
`firebase` is an optional peer dependency, loaded dynamically only when used.
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
import { enableFirebaseSync, isFirebaseAvailable } from 'strata-storage/firebase';
|
|
239
|
+
|
|
240
|
+
await enableFirebaseSync(storage, {
|
|
241
|
+
apiKey: '…', authDomain: '…', projectId: '…', appId: '…',
|
|
242
|
+
firestore: true, // registers the `firestore` adapter
|
|
243
|
+
realtimeDatabase: true, // registers the `realtime` adapter
|
|
244
|
+
collectionName: 'strata-storage', // optional (default)
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
await storage.set('profile', data, { storage: 'firestore' });
|
|
248
|
+
storage.subscribe((c) => { if (c.source === 'remote') apply(c); }, { storage: 'firestore' });
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`firestore` is observable + queryable; `realtime` is observable. **Neither is
|
|
252
|
+
encrypted at rest by Strata** — combine with `encrypt: true` and/or Firebase
|
|
253
|
+
Security Rules for confidentiality. `firestore.size()` returns `{ total: 0, count: 0 }`.
|
|
254
|
+
Note: `'firestore'`/`'realtime'` are runtime adapter names (not in the `StorageType`
|
|
255
|
+
union), so strict TS may need a cast on the per-call options object.
|
|
256
|
+
|
|
257
|
+
## Encryption & security (2.7.0)
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
const storage = defineStorage({
|
|
261
|
+
encryption: {
|
|
262
|
+
enabled: true,
|
|
263
|
+
password: 'user-derived-password',
|
|
264
|
+
algorithm: 'AES-GCM', // default; or 'AES-CBC' (authenticated via Encrypt-then-MAC)
|
|
265
|
+
iterations: 600000, // PBKDF2 default (OWASP); stored per-record, used on decrypt
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
- **`AES-GCM`** (default) is AEAD. **`AES-CBC`** is authenticated with
|
|
271
|
+
Encrypt-then-MAC (HMAC-SHA256 over `iv ‖ ciphertext`, verified before decrypt).
|
|
272
|
+
- **Breaking:** legacy AES-CBC ciphertexts written before authentication have no
|
|
273
|
+
MAC and now fail closed with a "re-encrypt" error — re-encrypt them or use GCM.
|
|
274
|
+
GCM data is unaffected.
|
|
275
|
+
- Encryption runs on the Web Crypto API on web, Capacitor webviews, and Node 20+ (SSR).
|
|
181
276
|
|
|
182
277
|
## Strata API (most-used methods)
|
|
183
278
|
|
|
@@ -196,6 +291,25 @@ Native adapters depend on the downstream Capacitor project setup; follow `docs/g
|
|
|
196
291
|
| `getTTL`/`extendTTL`/`persist` | TTL management |
|
|
197
292
|
| `registerAdapter(adapter)` | `void` |
|
|
198
293
|
|
|
294
|
+
## Notes for AI agents
|
|
295
|
+
|
|
296
|
+
- **Error handling** — every library error class (`QuotaExceededError`,
|
|
297
|
+
`EncryptionError`, `ValidationError`, `NotFoundError`, `IntegrityError`, etc.)
|
|
298
|
+
extends `StrataError`. Catch any of them with `catch (e) { if (e instanceof StrataError) … }`.
|
|
299
|
+
- **Server / SSR durability** — the default `storage` singleton uses web adapters and
|
|
300
|
+
**falls back to non-persistent `memory` on Node/SSR** (no localStorage/IndexedDB there).
|
|
301
|
+
For durable server storage register `sqlite`/`filesystem` or a custom adapter and
|
|
302
|
+
target it with `{ storage: '…' }`.
|
|
303
|
+
- **Browser encryption** — `crypto.subtle` requires a **secure context**
|
|
304
|
+
(HTTPS or `localhost`); encryption throws on insecure-origin pages. Node 20+ is fine.
|
|
305
|
+
- **Sync API limits** — `getSync`/`setSync`/… throw on async-only adapters
|
|
306
|
+
(indexedDB, cache, sqlite, filesystem, secure, preferences); `setSync` throws if
|
|
307
|
+
`encrypt`/`compress` is requested, and `getSync` throws on an encrypted/compressed
|
|
308
|
+
stored value. Use the async API there.
|
|
309
|
+
- **`useStorage` value is `T | null`** — element `[0]` is `null` during initial load
|
|
310
|
+
and when the key is absent with no default, *even when a defaultValue is passed*.
|
|
311
|
+
Null-guard before use.
|
|
312
|
+
|
|
199
313
|
## Troubleshooting
|
|
200
314
|
|
|
201
315
|
| Issue | Solution |
|
|
@@ -209,9 +323,13 @@ Native adapters depend on the downstream Capacitor project setup; follow `docs/g
|
|
|
209
323
|
|
|
210
324
|
## Links
|
|
211
325
|
|
|
212
|
-
-
|
|
213
|
-
|
|
214
|
-
- [
|
|
215
|
-
- [
|
|
216
|
-
- [
|
|
217
|
-
- [
|
|
326
|
+
Full documentation lives at **https://stratastorage-docs.aoneahsan.com**:
|
|
327
|
+
|
|
328
|
+
- [Documentation home](https://stratastorage-docs.aoneahsan.com/)
|
|
329
|
+
- [For AI Agents (this guide, expanded)](https://stratastorage-docs.aoneahsan.com/ai)
|
|
330
|
+
- [Core API](https://stratastorage-docs.aoneahsan.com/api/core/strata)
|
|
331
|
+
- [Firebase sync](https://stratastorage-docs.aoneahsan.com/guides/platforms/firebase)
|
|
332
|
+
- [URL Adapter](https://stratastorage-docs.aoneahsan.com/api/adapters/web/url)
|
|
333
|
+
- [Recovery & Integrity](https://stratastorage-docs.aoneahsan.com/api/features/recovery)
|
|
334
|
+
- [Changelog](https://stratastorage-docs.aoneahsan.com/reference/changelog)
|
|
335
|
+
- Machine-readable: [`/llms.txt`](https://stratastorage-docs.aoneahsan.com/llms.txt) · [`/llms-full.txt`](https://stratastorage-docs.aoneahsan.com/llms-full.txt)
|
package/README.md
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/strata-storage)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](https://www.typescriptlang.org/)
|
|
10
|
-
[](https://
|
|
10
|
+
[](https://stratastorage.aoneahsan.com)
|
|
11
11
|
|
|
12
|
-
- **Version:** `2.
|
|
12
|
+
- **Version:** `2.7.1`
|
|
13
13
|
- **License:** Apache-2.0
|
|
14
14
|
- **Node.js:** `>= 24.13.0`
|
|
15
15
|
- **Module format:** ESM only
|
|
@@ -71,7 +71,7 @@ await storage.set('token', '...', { encrypt: true });
|
|
|
71
71
|
|
|
72
72
|
## Provider-Free Usage
|
|
73
73
|
|
|
74
|
-
The recommended pattern across all frameworks: create one instance and bind to it. No Provider, plugin, or module is required. The Provider-based styles remain available and are documented in [
|
|
74
|
+
The recommended pattern across all frameworks: create one instance and bind to it. No Provider, plugin, or module is required. The Provider-based styles remain available and are documented in the [examples](https://stratastorage-docs.aoneahsan.com/examples).
|
|
75
75
|
|
|
76
76
|
### Vanilla JavaScript / TypeScript
|
|
77
77
|
|
|
@@ -396,14 +396,14 @@ Register the native adapters you need when running under Capacitor. All four are
|
|
|
396
396
|
import { defineStorage } from 'strata-storage';
|
|
397
397
|
import {
|
|
398
398
|
PreferencesAdapter,
|
|
399
|
-
|
|
399
|
+
SecureAdapter,
|
|
400
400
|
SqliteAdapter,
|
|
401
401
|
FilesystemAdapter,
|
|
402
402
|
} from 'strata-storage/capacitor';
|
|
403
403
|
|
|
404
404
|
const storage = defineStorage();
|
|
405
405
|
storage.registerAdapter(new PreferencesAdapter()); // UserDefaults / SharedPreferences
|
|
406
|
-
storage.registerAdapter(new
|
|
406
|
+
storage.registerAdapter(new SecureAdapter()); // Keychain / EncryptedSharedPreferences
|
|
407
407
|
storage.registerAdapter(new SqliteAdapter()); // native SQLite
|
|
408
408
|
storage.registerAdapter(new FilesystemAdapter()); // native files
|
|
409
409
|
|
|
@@ -432,7 +432,7 @@ audit.registerAdapter(new SqliteAdapter({ database: 'audit', table: 'rows' }));
|
|
|
432
432
|
|
|
433
433
|
`await storage.size(true)` aggregates `{ total, count, byStorage, ... }`; native SQLite and filesystem additionally report a per-column byte breakdown (keys / values / metadata) when called on those adapters directly.
|
|
434
434
|
|
|
435
|
-
> **Honest note:** the native iOS/Android adapters depend on your downstream Capacitor project setup and platform configuration, and native behavior cannot be exercised
|
|
435
|
+
> **Honest note:** the native iOS/Android adapters depend on your downstream Capacitor project setup and platform configuration, and native behavior cannot be exercised in a web/Node environment. Follow the [device-verification guide](https://stratastorage-docs.aoneahsan.com/guides/platforms/device-verification) to verify on a real iOS and Android device after integrating.
|
|
436
436
|
|
|
437
437
|
### Firebase (optional cloud sync)
|
|
438
438
|
|
|
@@ -441,7 +441,12 @@ import { defineStorage } from 'strata-storage';
|
|
|
441
441
|
import { enableFirebaseSync } from 'strata-storage/firebase';
|
|
442
442
|
|
|
443
443
|
const storage = defineStorage();
|
|
444
|
-
await enableFirebaseSync(storage, {
|
|
444
|
+
await enableFirebaseSync(storage, {
|
|
445
|
+
apiKey: '…', authDomain: '…', projectId: '…', appId: '…',
|
|
446
|
+
firestore: true,
|
|
447
|
+
});
|
|
448
|
+
// 'firestore' / 'realtime' are runtime adapter names (not in the StorageType
|
|
449
|
+
// union, so strict TS may need a cast on the options object).
|
|
445
450
|
await storage.set('data', value, { storage: 'firestore' });
|
|
446
451
|
```
|
|
447
452
|
|
|
@@ -473,59 +478,24 @@ Optional peer dependencies (install only the ones you use): `react >= 19.2.3`, `
|
|
|
473
478
|
|
|
474
479
|
## Documentation
|
|
475
480
|
|
|
481
|
+
📚 **Full documentation: [stratastorage-docs.aoneahsan.com](https://stratastorage-docs.aoneahsan.com)**
|
|
482
|
+
🤖 **For AI agents: [stratastorage-docs.aoneahsan.com/ai](https://stratastorage-docs.aoneahsan.com/ai)** (plus [`/llms.txt`](https://stratastorage-docs.aoneahsan.com/llms.txt) and [`/llms-full.txt`](https://stratastorage-docs.aoneahsan.com/llms-full.txt))
|
|
483
|
+
|
|
476
484
|
### Getting Started
|
|
477
|
-
- [Installation](
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
- [API Reference](./docs/api/README.md) — complete API
|
|
483
|
-
- [Core API (`Strata`)](./docs/api/core/strata.md) — the main class
|
|
484
|
-
- [Type Definitions](./docs/api/core/types.md) — TypeScript types
|
|
485
|
-
- [Error Handling](./docs/api/core/errors.md) — error classes
|
|
486
|
-
|
|
487
|
-
### Storage Adapters
|
|
488
|
-
- [Web Adapters](./docs/api/adapters/README.md#web-adapters)
|
|
489
|
-
- [localStorage](./docs/api/adapters/web/localstorage.md)
|
|
490
|
-
- [sessionStorage](./docs/api/adapters/web/sessionstorage.md)
|
|
491
|
-
- [IndexedDB](./docs/api/adapters/web/indexeddb.md)
|
|
492
|
-
- [Cookies](./docs/api/adapters/web/cookies.md)
|
|
493
|
-
- [Cache API](./docs/api/adapters/web/cache.md)
|
|
494
|
-
- [Memory](./docs/api/adapters/web/memory.md)
|
|
495
|
-
- [URL](./docs/api/adapters/web/url.md)
|
|
496
|
-
- [Capacitor Adapters](./docs/api/adapters/README.md#capacitor-adapters)
|
|
497
|
-
- [Preferences](./docs/api/adapters/capacitor/preferences.md)
|
|
498
|
-
- [Secure Storage](./docs/api/adapters/capacitor/secure.md)
|
|
499
|
-
- [SQLite](./docs/api/adapters/capacitor/sqlite.md)
|
|
500
|
-
- [Filesystem](./docs/api/adapters/capacitor/filesystem.md)
|
|
485
|
+
- [Installation](https://stratastorage-docs.aoneahsan.com/installation) · [Quick Start](https://stratastorage-docs.aoneahsan.com/quick-start) · [Configuration](https://stratastorage-docs.aoneahsan.com/configuration)
|
|
486
|
+
|
|
487
|
+
### API
|
|
488
|
+
- [API Reference](https://stratastorage-docs.aoneahsan.com/api) · [Core (`Strata`)](https://stratastorage-docs.aoneahsan.com/api/core/strata) · [Types](https://stratastorage-docs.aoneahsan.com/api/core/types) · [Errors](https://stratastorage-docs.aoneahsan.com/api/core/errors)
|
|
489
|
+
- [All adapters](https://stratastorage-docs.aoneahsan.com/api/adapters) — web (localStorage, IndexedDB, cookies, Cache, URL, …) + Capacitor (Preferences, Secure, SQLite, Filesystem) + remote (Firebase)
|
|
501
490
|
|
|
502
491
|
### Features
|
|
503
|
-
- [Encryption](
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
- [
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
- [
|
|
510
|
-
|
|
511
|
-
### Platform Guides
|
|
512
|
-
- [Web](./docs/guides/platforms/web.md)
|
|
513
|
-
- [iOS](./docs/guides/platforms/ios.md)
|
|
514
|
-
- [Android](./docs/guides/platforms/android.md)
|
|
515
|
-
- [Capacitor](./docs/guides/platforms/capacitor.md)
|
|
516
|
-
|
|
517
|
-
### Examples
|
|
518
|
-
- [Basic Usage](./docs/examples/basic-usage.md)
|
|
519
|
-
- [React Integration](./docs/examples/frameworks/react.md)
|
|
520
|
-
- [Vue Integration](./docs/examples/frameworks/vue.md)
|
|
521
|
-
- [Angular Integration](./docs/examples/frameworks/angular.md)
|
|
522
|
-
- [All Examples](./docs/examples/README.md)
|
|
523
|
-
|
|
524
|
-
### Reference
|
|
525
|
-
- [Changelog](./docs/reference/changelog.md)
|
|
526
|
-
- [FAQ](./docs/reference/faq.md)
|
|
527
|
-
- [Troubleshooting](./docs/reference/troubleshooting.md)
|
|
528
|
-
- [Migration Guide](./docs/MIGRATION.md)
|
|
492
|
+
- [Encryption](https://stratastorage-docs.aoneahsan.com/guides/features/encryption) · [Compression](https://stratastorage-docs.aoneahsan.com/guides/features/compression) · [TTL](https://stratastorage-docs.aoneahsan.com/api/features/ttl) · [Sync](https://stratastorage-docs.aoneahsan.com/guides/features/sync) · [Queries](https://stratastorage-docs.aoneahsan.com/guides/features/queries) · [Migrations](https://stratastorage-docs.aoneahsan.com/guides/features/migrations) · [Recovery & Integrity](https://stratastorage-docs.aoneahsan.com/api/features/recovery)
|
|
493
|
+
|
|
494
|
+
### Platforms
|
|
495
|
+
- [Web](https://stratastorage-docs.aoneahsan.com/guides/platforms/web) · [iOS](https://stratastorage-docs.aoneahsan.com/guides/platforms/ios) · [Android](https://stratastorage-docs.aoneahsan.com/guides/platforms/android) · [Capacitor](https://stratastorage-docs.aoneahsan.com/guides/platforms/capacitor) · [Firebase](https://stratastorage-docs.aoneahsan.com/guides/platforms/firebase)
|
|
496
|
+
|
|
497
|
+
### Examples & Reference
|
|
498
|
+
- [Examples](https://stratastorage-docs.aoneahsan.com/examples) · [Changelog](https://stratastorage-docs.aoneahsan.com/reference/changelog) · [FAQ](https://stratastorage-docs.aoneahsan.com/reference/faq) · [Troubleshooting](https://stratastorage-docs.aoneahsan.com/reference/troubleshooting) · [Migration](https://stratastorage-docs.aoneahsan.com/migration)
|
|
529
499
|
|
|
530
500
|
## Contributing
|
|
531
501
|
|
|
@@ -547,17 +517,25 @@ Apache License 2.0 — see [LICENSE](LICENSE). Free for commercial use, modifica
|
|
|
547
517
|
|
|
548
518
|
## Links
|
|
549
519
|
|
|
550
|
-
- **NPM Package:**
|
|
551
|
-
- **
|
|
552
|
-
- **
|
|
553
|
-
- **Documentation:** [github.com/aoneahsan/strata-storage/tree/main/docs](https://github.com/aoneahsan/strata-storage/tree/main/docs)
|
|
520
|
+
- **NPM Package:** https://www.npmjs.com/package/strata-storage
|
|
521
|
+
- **Documentation:** https://stratastorage-docs.aoneahsan.com
|
|
522
|
+
- **Website:** https://stratastorage.aoneahsan.com
|
|
554
523
|
|
|
555
524
|
## Support
|
|
556
525
|
|
|
557
|
-
1. Check the [FAQ](
|
|
558
|
-
2.
|
|
559
|
-
3.
|
|
526
|
+
1. Check the [FAQ](https://stratastorage-docs.aoneahsan.com/reference/faq) and [Troubleshooting](https://stratastorage-docs.aoneahsan.com/reference/troubleshooting)
|
|
527
|
+
2. Browse the [documentation](https://stratastorage-docs.aoneahsan.com)
|
|
528
|
+
3. [Contact us / report an issue](https://stratastorage.aoneahsan.com/contact)
|
|
560
529
|
|
|
561
530
|
---
|
|
562
531
|
|
|
563
532
|
Made by [Ahsan Mahmood](https://aoneahsan.com). **One API. Every Storage. Everywhere.**
|
|
533
|
+
|
|
534
|
+
<!-- project-links:start -->
|
|
535
|
+
## Links
|
|
536
|
+
|
|
537
|
+
- Live: https://www.npmjs.com/package/strata-storage
|
|
538
|
+
- NPM: https://www.npmjs.com/package/strata-storage
|
|
539
|
+
|
|
540
|
+
_URL source of truth: `01-code/projects/project-live-urls.json` (auto-generated — do not hand-edit between these markers)._
|
|
541
|
+
<!-- project-links:end -->
|
package/android/AGENTS.md
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
# AGENTS.md — android/
|
|
2
2
|
|
|
3
|
-
Last Updated: 2026-
|
|
3
|
+
Last Updated: 2026-06-24
|
|
4
4
|
|
|
5
5
|
> Agent instructions for Android native development.
|
|
6
6
|
|
|
7
|
+
> **v2.7.0:** Secure storage THROWS on API < 23 (no plaintext fallback) and
|
|
8
|
+
> `minSdkVersion` is raised to **23**. `FilesystemStorage` writes atomically
|
|
9
|
+
> (`Files.move` ATOMIC_MOVE on API 26+, backup-swap fallback below) — never
|
|
10
|
+
> deletes the old value before the new one commits. Preferences/secure `get()`
|
|
11
|
+
> parses the JSON wrapper back to an object so `StorageValue` (TTL/tags/metadata)
|
|
12
|
+
> round-trips. `EncryptedStorage.objectToJsonString()` serialises arrays via
|
|
13
|
+
> `java.lang.reflect.Array` so primitive arrays (`int[]`, `long[]`, …) round-trip
|
|
14
|
+
> (no lossy `toString()` fallback). **SQLite:** identifiers (`table`/`database`)
|
|
15
|
+
> are now REJECTED (throw → `call.reject`) when not strict SQL identifiers
|
|
16
|
+
> (`^[A-Za-z_][A-Za-z0-9_]*$`) instead of being silently stripped (`#5`);
|
|
17
|
+
> `keys()`/`query()` filter expired rows in SQL and `query()` returns the full
|
|
18
|
+
> `{key, value}` wrapper in one round-trip, plus a new `cleanupExpired`
|
|
19
|
+
> single-`DELETE` on the TTL tick (`#6` — no more per-key `get()` N+1).
|
|
20
|
+
> Device-verify pending.
|
|
21
|
+
|
|
7
22
|
## Files
|
|
8
23
|
|
|
9
24
|
| File | Purpose |
|
|
@@ -19,7 +34,7 @@ Last Updated: 2026-05-27
|
|
|
19
34
|
- **`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
35
|
- **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
36
|
- **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 }`.
|
|
37
|
+
- **FilesystemStorage.java:** new class. Writes are atomic (staging temp → `renameTo`). `keys()` excludes `.strata-staging/` artifacts. `size(detailed)` → `{ keys, values, metadata }`.
|
|
23
38
|
- **Pending on-device verification** — see `docs/guides/platforms/device-verification.md`.
|
|
24
39
|
|
|
25
40
|
## Agent Rules
|
|
@@ -34,7 +49,7 @@ Last Updated: 2026-05-27
|
|
|
34
49
|
- NEVER concatenate user input into SQL strings
|
|
35
50
|
|
|
36
51
|
### Filesystem Safety
|
|
37
|
-
- NEVER read from `strata_storage/.staging/` — staging files are transient
|
|
52
|
+
- NEVER read from `strata_storage/.strata-staging/` — staging files are transient
|
|
38
53
|
- Key sanitisation must be consistent between `set`, `get`, `remove`, and `keys`
|
|
39
54
|
|
|
40
55
|
### Plugin Architecture
|
package/android/CLAUDE.md
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
# CLAUDE.md — android/
|
|
2
2
|
|
|
3
|
-
Last Updated: 2026-
|
|
3
|
+
Last Updated: 2026-06-24
|
|
4
|
+
|
|
5
|
+
## v2.7.0 Changes
|
|
6
|
+
|
|
7
|
+
- **Secure storage no longer degrades to plaintext.** `EncryptedStorage` now
|
|
8
|
+
THROWS `GeneralSecurityException` on API < 23 instead of silently falling back
|
|
9
|
+
to unencrypted `SharedPreferences`, and `minSdkVersion` is raised to **23**
|
|
10
|
+
(EncryptedSharedPreferences requires API 23+). 23 is the floor for the secure
|
|
11
|
+
guarantees this plugin advertises.
|
|
12
|
+
- **Filesystem writes are atomic.** `FilesystemStorage.set()` commits via
|
|
13
|
+
`Files.move(ATOMIC_MOVE, REPLACE_EXISTING)` on API 26+, falling back to a
|
|
14
|
+
backup-swap on API 23–25 — the old value is never deleted before the new one
|
|
15
|
+
is committed (the prior delete-then-rename could lose data on a failed rename).
|
|
16
|
+
- **Preferences/secure `StorageValue` round-trip fixed.** `StrataStoragePlugin`
|
|
17
|
+
parses the stored JSON wrapper string back into a `JSObject` on `get()`, so
|
|
18
|
+
TTL/tags/metadata survive a write→read cycle (matching sqlite/filesystem).
|
|
19
|
+
- **Secure primitive-array serialization fixed.** `EncryptedStorage.objectToJsonString()`
|
|
20
|
+
now serialises arrays via `java.lang.reflect.Array` (`getLength`/`get`) instead
|
|
21
|
+
of the `(Object[]) obj` cast, so primitive arrays (`int[]`, `long[]`, …)
|
|
22
|
+
round-trip instead of falling back to lossy `value.toString()` (`[I@…`).
|
|
23
|
+
- **SQLite identifiers are rejected, not stripped (`#5`).** `SQLiteStorage.sanitizeTable`
|
|
24
|
+
and `StrataStoragePlugin.sanitizeDatabase` now throw `IllegalArgumentException`
|
|
25
|
+
on a non-empty name that is not a strict SQL identifier
|
|
26
|
+
(`^[A-Za-z_][A-Za-z0-9_]*$`) instead of silently removing illegal characters.
|
|
27
|
+
The throw propagates to the plugin's `try/catch` → `call.reject` (and
|
|
28
|
+
`getSqliteStore` maps an invalid database to `null` → reject). The TS adapter
|
|
29
|
+
applies the same allow-list and rejects first; this is defense-in-depth.
|
|
30
|
+
Values stay bound parameters — identifiers are the only interpolated input.
|
|
31
|
+
- **SQLite `keys()`/`query()` N+1 eliminated (`#6`).** `keys()` now excludes
|
|
32
|
+
expired rows in SQL (`expires IS NULL OR expires > ?`); `query()` returns the
|
|
33
|
+
full `StorageValue` wrapper `{key, value}` for each non-expired row in one
|
|
34
|
+
round-trip; new `cleanupExpired(table)` does a single `DELETE` of expired rows
|
|
35
|
+
(the TS adapter calls it on its TTL tick). The per-key `get()` loop is gone.
|
|
36
|
+
- All native changes remain pending on-device verification.
|
|
4
37
|
|
|
5
38
|
## Android Native Plugin
|
|
6
39
|
|
|
@@ -45,7 +78,7 @@ returns per-column byte breakdown.
|
|
|
45
78
|
|
|
46
79
|
### FilesystemStorage.java (new)
|
|
47
80
|
Stores each key as `getFilesDir()/strata_storage/<sanitised-key>.json`. Writes
|
|
48
|
-
are atomic: value is written to `strata_storage/.staging/<key>.tmp`, then
|
|
81
|
+
are atomic: value is written to `strata_storage/.strata-staging/<key>.tmp`, then
|
|
49
82
|
renamed into place using `renameTo()`. Staging artifacts are excluded from
|
|
50
83
|
`keys()`. No external filesystem permissions are needed (`getFilesDir()` is
|
|
51
84
|
app-private). `isAvailable()` returns `true`. `size(detailed)` supported.
|
|
@@ -67,7 +100,7 @@ app-private). `isAvailable()` returns `true`. `size(detailed)` supported.
|
|
|
67
100
|
- Use `SQLiteDatabase.query()` or prepared statements
|
|
68
101
|
|
|
69
102
|
### Filesystem Safety
|
|
70
|
-
- NEVER read from `strata_storage/.staging/` — staging files are transient
|
|
103
|
+
- NEVER read from `strata_storage/.strata-staging/` — staging files are transient
|
|
71
104
|
- Key sanitisation must be consistent between `set`, `get`, `remove`, and `keys`
|
|
72
105
|
- `getFilesDir()` is app-private; no external storage permissions required
|
|
73
106
|
|
package/android/build.gradle
CHANGED
|
@@ -21,7 +21,11 @@ android {
|
|
|
21
21
|
namespace "com.stratastorage"
|
|
22
22
|
compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34
|
|
23
23
|
defaultConfig {
|
|
24
|
-
|
|
24
|
+
// minSdk 23 (Marshmallow): EncryptedSharedPreferences-backed secure
|
|
25
|
+
// storage requires API 23+. Below it, "secure" storage would degrade to
|
|
26
|
+
// plaintext, so 23 is the floor for the security guarantees this plugin
|
|
27
|
+
// advertises.
|
|
28
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
|
|
25
29
|
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 34
|
|
26
30
|
versionCode 1
|
|
27
31
|
versionName "1.0"
|
|
@@ -8,6 +8,7 @@ import androidx.security.crypto.EncryptedSharedPreferences;
|
|
|
8
8
|
import androidx.security.crypto.MasterKey;
|
|
9
9
|
import java.security.GeneralSecurityException;
|
|
10
10
|
import java.io.IOException;
|
|
11
|
+
import java.lang.reflect.Array;
|
|
11
12
|
import java.util.Set;
|
|
12
13
|
import java.util.HashSet;
|
|
13
14
|
import java.util.Map;
|
|
@@ -18,7 +19,6 @@ import org.json.JSONArray;
|
|
|
18
19
|
|
|
19
20
|
public class EncryptedStorage {
|
|
20
21
|
private SharedPreferences encryptedPrefs;
|
|
21
|
-
private SharedPreferences.Editor editor;
|
|
22
22
|
private static final String DEFAULT_NAME = "StrataSecureStorage";
|
|
23
23
|
|
|
24
24
|
public EncryptedStorage(Context context) throws GeneralSecurityException, IOException {
|
|
@@ -42,20 +42,24 @@ public class EncryptedStorage {
|
|
|
42
42
|
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
|
43
43
|
);
|
|
44
44
|
|
|
45
|
-
editor = encryptedPrefs.edit();
|
|
46
45
|
} catch (GeneralSecurityException | IOException e) {
|
|
47
46
|
Log.e("StrataStorage", "Failed to initialize encrypted storage", e);
|
|
48
47
|
throw e;
|
|
49
48
|
}
|
|
50
49
|
} else {
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
// SECURITY: EncryptedSharedPreferences requires API 23+. Refuse to
|
|
51
|
+
// silently fall back to UNENCRYPTED SharedPreferences for "secure"
|
|
52
|
+
// storage — that would persist sensitive data in plaintext while the
|
|
53
|
+
// caller believes it is encrypted. Reject so the caller can decide.
|
|
54
|
+
// (minSdkVersion is also raised to 23, so this is defense in depth.)
|
|
55
|
+
Log.e("StrataStorage", "Secure storage unavailable on API " + Build.VERSION.SDK_INT + " (< 23)");
|
|
56
|
+
throw new GeneralSecurityException(
|
|
57
|
+
"Secure storage requires Android API 23 (Marshmallow) or higher; refusing to "
|
|
58
|
+
+ "fall back to unencrypted storage on API " + Build.VERSION.SDK_INT);
|
|
55
59
|
}
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
public boolean set(String key, Object value) {
|
|
62
|
+
public synchronized boolean set(String key, Object value) {
|
|
59
63
|
try {
|
|
60
64
|
String stringValue;
|
|
61
65
|
if (value instanceof String) {
|
|
@@ -74,6 +78,7 @@ public class EncryptedStorage {
|
|
|
74
78
|
}
|
|
75
79
|
}
|
|
76
80
|
}
|
|
81
|
+
SharedPreferences.Editor editor = encryptedPrefs.edit();
|
|
77
82
|
editor.putString(key, stringValue);
|
|
78
83
|
return editor.commit();
|
|
79
84
|
} catch (Exception e) {
|
|
@@ -82,11 +87,12 @@ public class EncryptedStorage {
|
|
|
82
87
|
}
|
|
83
88
|
}
|
|
84
89
|
|
|
85
|
-
public String get(String key) {
|
|
90
|
+
public synchronized String get(String key) {
|
|
86
91
|
return encryptedPrefs.getString(key, null);
|
|
87
92
|
}
|
|
88
|
-
|
|
89
|
-
public boolean remove(String key) {
|
|
93
|
+
|
|
94
|
+
public synchronized boolean remove(String key) {
|
|
95
|
+
SharedPreferences.Editor editor = encryptedPrefs.edit();
|
|
90
96
|
editor.remove(key);
|
|
91
97
|
return editor.commit();
|
|
92
98
|
}
|
|
@@ -95,7 +101,8 @@ public class EncryptedStorage {
|
|
|
95
101
|
return clear(null);
|
|
96
102
|
}
|
|
97
103
|
|
|
98
|
-
public boolean clear(String prefix) {
|
|
104
|
+
public synchronized boolean clear(String prefix) {
|
|
105
|
+
SharedPreferences.Editor editor = encryptedPrefs.edit();
|
|
99
106
|
if (prefix != null) {
|
|
100
107
|
// Clear only keys with the given prefix
|
|
101
108
|
Set<String> keysToRemove = new HashSet<>();
|
|
@@ -118,7 +125,7 @@ public class EncryptedStorage {
|
|
|
118
125
|
return keys(null);
|
|
119
126
|
}
|
|
120
127
|
|
|
121
|
-
public List<String> keys(String pattern) {
|
|
128
|
+
public synchronized List<String> keys(String pattern) {
|
|
122
129
|
Set<String> allKeys = encryptedPrefs.getAll().keySet();
|
|
123
130
|
|
|
124
131
|
if (pattern == null) {
|
|
@@ -135,11 +142,11 @@ public class EncryptedStorage {
|
|
|
135
142
|
return filteredKeys;
|
|
136
143
|
}
|
|
137
144
|
|
|
138
|
-
public boolean has(String key) {
|
|
145
|
+
public synchronized boolean has(String key) {
|
|
139
146
|
return encryptedPrefs.contains(key);
|
|
140
147
|
}
|
|
141
|
-
|
|
142
|
-
public SizeInfo size() {
|
|
148
|
+
|
|
149
|
+
public synchronized SizeInfo size() {
|
|
143
150
|
Map<String, ?> all = encryptedPrefs.getAll();
|
|
144
151
|
long totalSize = 0;
|
|
145
152
|
int count = all.size();
|
|
@@ -188,9 +195,15 @@ public class EncryptedStorage {
|
|
|
188
195
|
jsonArray.put(item);
|
|
189
196
|
}
|
|
190
197
|
} else {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
198
|
+
// Use java.lang.reflect.Array so EVERY array type round-trips,
|
|
199
|
+
// including primitive arrays (int[], long[], double[], boolean[],
|
|
200
|
+
// ...). A direct `(Object[]) obj` cast throws ClassCastException for
|
|
201
|
+
// primitive arrays, which the caller would swallow and replace with
|
|
202
|
+
// the lossy `value.toString()` (e.g. "[I@1a2b3c"). Array.get boxes
|
|
203
|
+
// each primitive element so JSONArray.put stores a real value.
|
|
204
|
+
int len = Array.getLength(obj);
|
|
205
|
+
for (int i = 0; i < len; i++) {
|
|
206
|
+
jsonArray.put(Array.get(obj, i));
|
|
194
207
|
}
|
|
195
208
|
}
|
|
196
209
|
return jsonArray.toString();
|