strata-storage 2.6.0 → 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 +97 -10
- package/README.md +70 -70
- 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 +70 -70
- 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 +29 -26
- 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
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)
|
|
@@ -160,15 +190,68 @@ const storage = defineStorage();
|
|
|
160
190
|
|
|
161
191
|
```typescript
|
|
162
192
|
import { defineStorage } from 'strata-storage';
|
|
163
|
-
import {
|
|
193
|
+
import {
|
|
194
|
+
PreferencesAdapter,
|
|
195
|
+
SecureAdapter,
|
|
196
|
+
SqliteAdapter,
|
|
197
|
+
FilesystemAdapter,
|
|
198
|
+
} from 'strata-storage/capacitor';
|
|
164
199
|
|
|
165
200
|
const storage = defineStorage();
|
|
166
201
|
storage.registerAdapter(new PreferencesAdapter());
|
|
167
|
-
storage.registerAdapter(new
|
|
202
|
+
storage.registerAdapter(new SecureAdapter());
|
|
203
|
+
storage.registerAdapter(new SqliteAdapter()); // 2.6.0 — multi-store (database + table)
|
|
204
|
+
storage.registerAdapter(new FilesystemAdapter()); // 2.6.0 — file-per-key, atomic writes
|
|
168
205
|
await storage.set('secret', token, { storage: 'secure' });
|
|
169
206
|
```
|
|
170
207
|
|
|
171
|
-
|
|
208
|
+
**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.
|
|
209
|
+
|
|
210
|
+
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.
|
|
211
|
+
|
|
212
|
+
### Firebase cross-device sync (optional)
|
|
213
|
+
|
|
214
|
+
`firebase` is an optional peer dependency, loaded dynamically only when used.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import { enableFirebaseSync, isFirebaseAvailable } from 'strata-storage/firebase';
|
|
218
|
+
|
|
219
|
+
await enableFirebaseSync(storage, {
|
|
220
|
+
apiKey: '…', authDomain: '…', projectId: '…', appId: '…',
|
|
221
|
+
firestore: true, // registers the `firestore` adapter
|
|
222
|
+
realtimeDatabase: true, // registers the `realtime` adapter
|
|
223
|
+
collectionName: 'strata-storage', // optional (default)
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
await storage.set('profile', data, { storage: 'firestore' });
|
|
227
|
+
storage.subscribe((c) => { if (c.source === 'remote') apply(c); }, { storage: 'firestore' });
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`firestore` is observable + queryable; `realtime` is observable. **Neither is
|
|
231
|
+
encrypted at rest by Strata** — combine with `encrypt: true` and/or Firebase
|
|
232
|
+
Security Rules for confidentiality. `firestore.size()` returns `{ total: 0, count: 0 }`.
|
|
233
|
+
Note: `'firestore'`/`'realtime'` are runtime adapter names (not in the `StorageType`
|
|
234
|
+
union), so strict TS may need a cast on the per-call options object.
|
|
235
|
+
|
|
236
|
+
## Encryption & security (2.7.0)
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
const storage = defineStorage({
|
|
240
|
+
encryption: {
|
|
241
|
+
enabled: true,
|
|
242
|
+
password: 'user-derived-password',
|
|
243
|
+
algorithm: 'AES-GCM', // default; or 'AES-CBC' (authenticated via Encrypt-then-MAC)
|
|
244
|
+
iterations: 600000, // PBKDF2 default (OWASP); stored per-record, used on decrypt
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
- **`AES-GCM`** (default) is AEAD. **`AES-CBC`** is authenticated with
|
|
250
|
+
Encrypt-then-MAC (HMAC-SHA256 over `iv ‖ ciphertext`, verified before decrypt).
|
|
251
|
+
- **Breaking:** legacy AES-CBC ciphertexts written before authentication have no
|
|
252
|
+
MAC and now fail closed with a "re-encrypt" error — re-encrypt them or use GCM.
|
|
253
|
+
GCM data is unaffected.
|
|
254
|
+
- Encryption runs on the Web Crypto API on web, Capacitor webviews, and Node 20+ (SSR).
|
|
172
255
|
|
|
173
256
|
## Strata API (most-used methods)
|
|
174
257
|
|
|
@@ -200,9 +283,13 @@ Native adapters depend on the downstream Capacitor project setup; verify behavio
|
|
|
200
283
|
|
|
201
284
|
## Links
|
|
202
285
|
|
|
203
|
-
-
|
|
204
|
-
|
|
205
|
-
- [
|
|
206
|
-
- [
|
|
207
|
-
- [
|
|
208
|
-
- [
|
|
286
|
+
Full documentation lives at **https://stratastorage-docs.aoneahsan.com**:
|
|
287
|
+
|
|
288
|
+
- [Documentation home](https://stratastorage-docs.aoneahsan.com/)
|
|
289
|
+
- [For AI Agents (this guide, expanded)](https://stratastorage-docs.aoneahsan.com/ai)
|
|
290
|
+
- [Core API](https://stratastorage-docs.aoneahsan.com/api/core/strata)
|
|
291
|
+
- [Firebase sync](https://stratastorage-docs.aoneahsan.com/guides/platforms/firebase)
|
|
292
|
+
- [URL Adapter](https://stratastorage-docs.aoneahsan.com/api/adapters/web/url)
|
|
293
|
+
- [Recovery & Integrity](https://stratastorage-docs.aoneahsan.com/api/features/recovery)
|
|
294
|
+
- [Changelog](https://stratastorage-docs.aoneahsan.com/reference/changelog)
|
|
295
|
+
- 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.0`
|
|
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
|
|
|
@@ -390,15 +390,22 @@ const active = await storage.query({
|
|
|
390
390
|
|
|
391
391
|
### iOS and Android (via Capacitor)
|
|
392
392
|
|
|
393
|
-
Register native adapters
|
|
393
|
+
Register the native adapters you need when running under Capacitor. All four are zero-runtime-dependency: SQLite is hand-rolled (no plugin dependency) and filesystem uses the platform's native `FileManager` / `java.io.File`.
|
|
394
394
|
|
|
395
395
|
```typescript
|
|
396
396
|
import { defineStorage } from 'strata-storage';
|
|
397
|
-
import {
|
|
397
|
+
import {
|
|
398
|
+
PreferencesAdapter,
|
|
399
|
+
SecureAdapter,
|
|
400
|
+
SqliteAdapter,
|
|
401
|
+
FilesystemAdapter,
|
|
402
|
+
} from 'strata-storage/capacitor';
|
|
398
403
|
|
|
399
404
|
const storage = defineStorage();
|
|
400
|
-
storage.registerAdapter(new PreferencesAdapter());
|
|
401
|
-
storage.registerAdapter(new
|
|
405
|
+
storage.registerAdapter(new PreferencesAdapter()); // UserDefaults / SharedPreferences
|
|
406
|
+
storage.registerAdapter(new SecureAdapter()); // Keychain / EncryptedSharedPreferences
|
|
407
|
+
storage.registerAdapter(new SqliteAdapter()); // native SQLite
|
|
408
|
+
storage.registerAdapter(new FilesystemAdapter()); // native files
|
|
402
409
|
|
|
403
410
|
await storage.set('secret', token, { storage: 'secure' });
|
|
404
411
|
```
|
|
@@ -407,10 +414,25 @@ await storage.set('secret', token, { storage: 'secure' });
|
|
|
407
414
|
|---------|-------------|-----------------|
|
|
408
415
|
| `preferences` | UserDefaults | SharedPreferences |
|
|
409
416
|
| `secure` | Keychain | EncryptedSharedPreferences |
|
|
410
|
-
| `sqlite` | SQLite | SQLite |
|
|
411
|
-
| `filesystem` | FileManager |
|
|
417
|
+
| `sqlite` | SQLite (multi-store) | SQLite (multi-store) |
|
|
418
|
+
| `filesystem` | FileManager | java.io.File |
|
|
412
419
|
|
|
413
|
-
|
|
420
|
+
**SQLite multi-store** (2.6.0+): each `SqliteAdapter` instance binds to a `(database, table)` pair, so distinct logical stores map to distinct physical SQLite files / tables and cannot collide.
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
import { SqliteAdapter } from 'strata-storage/capacitor';
|
|
424
|
+
|
|
425
|
+
const analytics = defineStorage();
|
|
426
|
+
analytics.registerAdapter(new SqliteAdapter({ database: 'analytics', table: 'events' }));
|
|
427
|
+
|
|
428
|
+
const audit = defineStorage();
|
|
429
|
+
audit.registerAdapter(new SqliteAdapter({ database: 'audit', table: 'rows' }));
|
|
430
|
+
// → separate physical .db files; writes to `analytics` can never bleed into `audit`.
|
|
431
|
+
```
|
|
432
|
+
|
|
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
|
+
|
|
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.
|
|
414
436
|
|
|
415
437
|
### Firebase (optional cloud sync)
|
|
416
438
|
|
|
@@ -419,7 +441,12 @@ import { defineStorage } from 'strata-storage';
|
|
|
419
441
|
import { enableFirebaseSync } from 'strata-storage/firebase';
|
|
420
442
|
|
|
421
443
|
const storage = defineStorage();
|
|
422
|
-
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).
|
|
423
450
|
await storage.set('data', value, { storage: 'firestore' });
|
|
424
451
|
```
|
|
425
452
|
|
|
@@ -436,8 +463,8 @@ await storage.set('data', value, { storage: 'firestore' });
|
|
|
436
463
|
| `url` | Web | ✅ | async only | Shareable UI state, length-limited |
|
|
437
464
|
| `preferences` | Mobile | ❌ | async only | UserDefaults / SharedPreferences |
|
|
438
465
|
| `secure` | Mobile | ❌ | async only | Keychain / EncryptedSharedPreferences |
|
|
439
|
-
| `sqlite` | Mobile | ❌ | async only | Native SQLite |
|
|
440
|
-
| `filesystem` | Mobile | ❌ | async only | Native files |
|
|
466
|
+
| `sqlite` | Mobile | ❌ | async only | Native SQLite — multi-store via `(database, table)` (2.6.0+) |
|
|
467
|
+
| `filesystem` | Mobile | ❌ | async only | Native files — file-per-key with atomic writes (2.6.0+) |
|
|
441
468
|
|
|
442
469
|
"async only" means encryption and compression require the `await storage.set(...)` path — the synchronous API cannot encrypt or compress.
|
|
443
470
|
|
|
@@ -451,59 +478,24 @@ Optional peer dependencies (install only the ones you use): `react >= 19.2.3`, `
|
|
|
451
478
|
|
|
452
479
|
## Documentation
|
|
453
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
|
+
|
|
454
484
|
### Getting Started
|
|
455
|
-
- [Installation](
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
- [API Reference](./docs/api/README.md) — complete API
|
|
461
|
-
- [Core API (`Strata`)](./docs/api/core/strata.md) — the main class
|
|
462
|
-
- [Type Definitions](./docs/api/core/types.md) — TypeScript types
|
|
463
|
-
- [Error Handling](./docs/api/core/errors.md) — error classes
|
|
464
|
-
|
|
465
|
-
### Storage Adapters
|
|
466
|
-
- [Web Adapters](./docs/api/adapters/README.md#web-adapters)
|
|
467
|
-
- [localStorage](./docs/api/adapters/web/localstorage.md)
|
|
468
|
-
- [sessionStorage](./docs/api/adapters/web/sessionstorage.md)
|
|
469
|
-
- [IndexedDB](./docs/api/adapters/web/indexeddb.md)
|
|
470
|
-
- [Cookies](./docs/api/adapters/web/cookies.md)
|
|
471
|
-
- [Cache API](./docs/api/adapters/web/cache.md)
|
|
472
|
-
- [Memory](./docs/api/adapters/web/memory.md)
|
|
473
|
-
- [URL](./docs/api/adapters/web/url.md)
|
|
474
|
-
- [Capacitor Adapters](./docs/api/adapters/README.md#capacitor-adapters)
|
|
475
|
-
- [Preferences](./docs/api/adapters/capacitor/preferences.md)
|
|
476
|
-
- [Secure Storage](./docs/api/adapters/capacitor/secure.md)
|
|
477
|
-
- [SQLite](./docs/api/adapters/capacitor/sqlite.md)
|
|
478
|
-
- [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)
|
|
479
490
|
|
|
480
491
|
### Features
|
|
481
|
-
- [Encryption](
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
- [
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
- [
|
|
488
|
-
|
|
489
|
-
### Platform Guides
|
|
490
|
-
- [Web](./docs/guides/platforms/web.md)
|
|
491
|
-
- [iOS](./docs/guides/platforms/ios.md)
|
|
492
|
-
- [Android](./docs/guides/platforms/android.md)
|
|
493
|
-
- [Capacitor](./docs/guides/platforms/capacitor.md)
|
|
494
|
-
|
|
495
|
-
### Examples
|
|
496
|
-
- [Basic Usage](./docs/examples/basic-usage.md)
|
|
497
|
-
- [React Integration](./docs/examples/frameworks/react.md)
|
|
498
|
-
- [Vue Integration](./docs/examples/frameworks/vue.md)
|
|
499
|
-
- [Angular Integration](./docs/examples/frameworks/angular.md)
|
|
500
|
-
- [All Examples](./docs/examples/README.md)
|
|
501
|
-
|
|
502
|
-
### Reference
|
|
503
|
-
- [Changelog](./docs/reference/changelog.md)
|
|
504
|
-
- [FAQ](./docs/reference/faq.md)
|
|
505
|
-
- [Troubleshooting](./docs/reference/troubleshooting.md)
|
|
506
|
-
- [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)
|
|
507
499
|
|
|
508
500
|
## Contributing
|
|
509
501
|
|
|
@@ -525,17 +517,25 @@ Apache License 2.0 — see [LICENSE](LICENSE). Free for commercial use, modifica
|
|
|
525
517
|
|
|
526
518
|
## Links
|
|
527
519
|
|
|
528
|
-
- **NPM Package:**
|
|
529
|
-
- **
|
|
530
|
-
- **
|
|
531
|
-
- **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
|
|
532
523
|
|
|
533
524
|
## Support
|
|
534
525
|
|
|
535
|
-
1. Check the [FAQ](
|
|
536
|
-
2.
|
|
537
|
-
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)
|
|
538
529
|
|
|
539
530
|
---
|
|
540
531
|
|
|
541
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();
|