Add detailed platform implementations, usage examples, and error handling: - Add comprehensive platform-specific implementations for Web (Dexie) and Capacitor (SQLite) - Include detailed database initialization and security features - Add practical usage examples for account and settings management - Document error handling strategies and edge cases - Add concurrency management and data integrity checks - Include platform transition handling This update provides a complete reference for implementing secure storage across different platforms while maintaining backward compatibility.
62 KiB
Secure Storage Implementation Guide for TimeSafari App
Overview
This document outlines the implementation of secure storage for the TimeSafari app using Capacitor solutions. Two primary storage options are provided:
-
SQLite with SQLCipher Encryption (Primary Solution):
- Utilizes
@capacitor-community/sqliteplugin with SQLCipher for 256-bit AES encryption. - Supports web and native platforms (iOS, Android).
- Ideal for complex queries and relational data.
- Platform-specific SQLCipher implementations:
- Android: Native SQLCipher library.
- iOS: SQLCipher as a drop-in SQLite replacement.
- Web: WebAssembly-based SQLCipher.
- Utilizes
-
Capacitor Preferences API (For Small Data):
- Built-in Capacitor solution for lightweight key-value storage.
- Uses platform-specific secure storage (e.g., Keychain on iOS, EncryptedSharedPreferences on Android).
- Limited to small datasets; no query capabilities.
Architecture
Directory Structure
src/services/
├── storage/ # Storage services
│ ├── SQLiteService.ts # Core SQLite service
│ ├── EncryptionService.ts # SQLCipher encryption handling
│ ├── KeyManagementService.ts # Secure key management
│ ├── platforms/ # Platform-specific implementations
│ │ ├── WebStorageService.ts
│ │ ├── CapacitorStorageService.ts
│ │ └── ElectronStorageService.ts
│ ├── migrations/ # Database migration scripts
│ │ ├── 001_initial.ts # Initial schema setup
│ │ └── 002_encryption.ts # Encryption configuration
│ └── types/
│ └── storage.types.ts # TypeScript type definitions
└── PlatformService.ts # Existing platform service interface
Platform Service Integration
The storage implementation integrates with the existing PlatformService interface to provide platform-specific storage operations. This allows for consistent API usage across platforms while maintaining platform-specific optimizations.
Storage Service Interface
// src/services/PlatformService.ts
import { Account } from '../db/tables/accounts';
import { Contact } from '../db/tables/contacts';
import { Settings } from '../db/tables/settings';
import { Secret } from '../db/tables/secret';
export interface PlatformService {
// ... existing platform methods ...
// Storage Operations
// Secret Database
openSecretDatabase(): Promise<void>;
getMasterSecret(): Promise<Secret | undefined>;
setMasterSecret(secret: Secret): Promise<void>;
// Accounts Database
openAccountsDatabase(): Promise<void>;
getAccountsCount(): Promise<number>;
getAllAccounts(): Promise<Account[]>;
getAccountByDid(did: string): Promise<Account | undefined>;
addAccount(account: Account): Promise<void>;
updateAccountSettings(did: string, settings: Partial<Settings>): Promise<void>;
// Settings Operations
getDefaultSettings(): Promise<Settings | undefined>;
getAccountSettings(did: string): Promise<Settings | undefined>;
updateSettings(key: string, changes: Partial<Settings>): Promise<void>;
addSettings(settings: Settings): Promise<void>;
getSettingsCount(): Promise<number>;
// Contacts Operations
getAllContacts(): Promise<Contact[]>;
addContact(contact: Contact): Promise<void>;
// Database Management
deleteDatabase(): Promise<void>;
importDatabase(data: Blob): Promise<void>;
exportDatabase(): Promise<Blob>;
// Migration Support
isFirstInstall(): Promise<boolean>;
needsMigration(): Promise<boolean>;
performMigration(): Promise<void>;
// Platform Detection
isCapacitor(): boolean;
isElectron(): boolean;
isWeb(): boolean;
getCapabilities(): {
hasFileSystem: boolean;
hasSecureStorage: boolean;
hasBiometrics: boolean;
isIOS: boolean;
isAndroid: boolean;
};
}
Platform-Specific Implementations
-
Web Platform (Dexie)
// src/services/platforms/WebPlatformService.ts export class WebPlatformService implements PlatformService { // ... existing web platform methods ... // Secret Database async openSecretDatabase(): Promise<void> { await secretDB.open(); } async getMasterSecret(): Promise<Secret | undefined> { return await secretDB.secret.get(MASTER_SECRET_KEY); } async setMasterSecret(secret: Secret): Promise<void> { await secretDB.secret.put(secret); } // Accounts Database async openAccountsDatabase(): Promise<void> { const accountsDB = await accountsDBPromise; await accountsDB.open(); } async getAccountsCount(): Promise<number> { const accountsDB = await accountsDBPromise; return await accountsDB.accounts.count(); } // ... implement other storage methods using Dexie ... } -
Capacitor Platform (SQLite)
// src/services/platforms/CapacitorPlatformService.ts export class CapacitorPlatformService implements PlatformService { private sqliteService: SQLiteService; private keyManagement: KeyManagementService; constructor() { this.sqliteService = SQLiteService.getInstance(); this.keyManagement = KeyManagementService.getInstance(); } // Secret Database async openSecretDatabase(): Promise<void> { await this.sqliteService.initialize({ database: 'timesafari_secret.db', encrypted: true, version: 1 }); } async getMasterSecret(): Promise<Secret | undefined> { const result = await this.sqliteService.query<Secret>( 'SELECT * FROM secret WHERE id = ?', [MASTER_SECRET_KEY] ); return result.value?.[0]; } async setMasterSecret(secret: Secret): Promise<void> { await this.sqliteService.query( 'INSERT OR REPLACE INTO secret (id, secret) VALUES (?, ?)', [secret.id, secret.secret] ); } // Accounts Database async openAccountsDatabase(): Promise<void> { await this.sqliteService.initialize({ database: 'timesafari_accounts.db', encrypted: true, version: 1, key: await this.keyManagement.getEncryptionKey() }); } async getAccountsCount(): Promise<number> { const result = await this.sqliteService.query<{ count: number }>( 'SELECT COUNT(*) as count FROM accounts' ); return result.value?.[0]?.count ?? 0; } // ... implement other storage methods using SQLite ... }
Usage Example
// src/services/storage/StorageService.ts
import { PlatformServiceFactory } from '../PlatformServiceFactory';
export class StorageService {
private static instance: StorageService | null = null;
private platformService: PlatformService;
private constructor() {
this.platformService = PlatformServiceFactory.getInstance();
}
static getInstance(): StorageService {
if (!StorageService.instance) {
StorageService.instance = new StorageService();
}
return StorageService.instance;
}
async initialize(): Promise<void> {
// Initialize secret database
await this.platformService.openSecretDatabase();
// Initialize accounts database
await this.platformService.openAccountsDatabase();
// Check if migration is needed
if (await this.platformService.needsMigration()) {
await this.platformService.performMigration();
}
}
async getAccountByDid(did: string): Promise<Account | undefined> {
return await this.platformService.getAccountByDid(did);
}
async updateAccountSettings(did: string, settings: Partial<Settings>): Promise<void> {
await this.platformService.updateAccountSettings(did, settings);
}
// ... implement other storage methods delegating to platformService ...
}
Migration Strategy
The platform service interface supports a smooth migration from Dexie to SQLite:
-
Web Platform:
- Continues using Dexie implementation
- No changes to existing code
- Maintains backward compatibility
-
Capacitor Platform:
- Uses SQLite with SQLCipher
- Implements platform-specific security
- Handles migration from Dexie if needed
-
Migration Process:
// src/services/storage/migration/MigrationService.ts export class MigrationService { async migrateFromDexieToSQLite(): Promise<void> { // 1. Export data from Dexie const accounts = await this.platformService.getAllAccounts(); const settings = await this.platformService.getAllSettings(); const contacts = await this.platformService.getAllContacts(); // 2. Initialize SQLite await this.platformService.openAccountsDatabase(); // 3. Import data to SQLite for (const account of accounts) { await this.platformService.addAccount(account); } // 4. Import settings and contacts for (const setting of settings) { await this.platformService.addSettings(setting); } for (const contact of contacts) { await this.platformService.addContact(contact); } // 5. Verify migration await this.verifyMigration(); } }
Dependencies
Verified and updated dependencies for the implementation:
-
Node.js Dependencies:
npm install @capacitor-community/sqlite@6.0.0 npm install @capacitor/core@6.1.2 npm install @capacitor/preferences@6.0.2 npm install @jlongster/sql.js@1.10.3 --save-dev # For web SQLCipher -
Android (build.gradle):
implementation 'net.zetetic:android-database-sqlcipher:4.6.1' -
iOS (Podfile):
pod 'SQLCipher', '~> 4.6.0'
Run npx cap sync after installing dependencies to update native projects.
Type Definitions
// src/services/storage/types/storage.types.ts
export interface StorageOptions {
encrypted?: boolean; // Whether to use encryption (default: true)
database: string; // Database name
version: number; // Migration version
key?: string; // Encryption key (optional)
}
export interface StorageResult<T> {
success: boolean; // Operation success status
error?: string; // Error message if operation failed
value?: T; // Stored/retrieved value
}
export interface StorageService {
initialize(options: StorageOptions): Promise<void>;
setItem<T>(key: string, value: T): Promise<StorageResult<T>>;
getItem<T>(key: string): Promise<StorageResult<T>>;
removeItem(key: string): Promise<StorageResult<void>>;
query<T>(sql: string, params?: any[]): Promise<StorageResult<T[]>>;
}
Implementation Details
1. SQLite Service
// src/services/storage/SQLiteService.ts
import { CapacitorSQLite, SQLiteConnection, SQLiteDBConnection } from '@capacitor-community/sqlite';
import { EncryptionService } from './EncryptionService';
export class SQLiteService implements StorageService {
private static instance: SQLiteService;
private connection: SQLiteConnection;
private db?: SQLiteDBConnection;
private encryptionService: EncryptionService;
private constructor() {
this.connection = new SQLiteConnection(CapacitorSQLite);
this.encryptionService = new EncryptionService();
}
static getInstance(): SQLiteService {
if (!SQLiteService.instance) {
SQLiteService.instance = new SQLiteService();
}
return SQLiteService.instance;
}
async initialize(options: StorageOptions): Promise<void> {
try {
this.db = await this.connection.createConnection(
options.database,
options.encrypted ?? true,
options.encrypted ? 'encryption' : 'no-encryption',
options.version,
false
);
await this.db.open();
if (options.encrypted) {
const encryptionKey = options.key ?? await this.encryptionService.getEncryptionKey();
await this.db.execute(`PRAGMA key = '${encryptionKey}'`);
}
await this.runMigrations();
} catch (error) {
throw new Error(`Database initialization failed: ${(error as Error).message}`);
}
}
async setItem<T>(key: string, value: T): Promise<StorageResult<T>> {
if (!this.db) throw new Error('Database not initialized');
try {
const encryptedValue = await this.encryptionService.encrypt(JSON.stringify(value));
await this.db.run(
'INSERT OR REPLACE INTO storage (key, value) VALUES (?, ?)',
[key, encryptedValue]
);
return { success: true, value };
} catch (error) {
return { success: false, error: (error as Error).message };
}
}
async getItem<T>(key: string): Promise<StorageResult<T>> {
if (!this.db) throw new Error('Database not initialized');
try {
const result = await this.db.query('SELECT value FROM storage WHERE key = ?', [key]);
if (!result.values?.length) {
return { success: false, error: 'Key not found' };
}
const decryptedValue = await this.encryptionService.decrypt(result.values[0].value);
return { success: true, value: JSON.parse(decryptedValue) as T };
} catch (error) {
return { success: false, error: (error as Error).message };
}
}
async removeItem(key: string): Promise<StorageResult<void>> {
if (!this.db) throw new Error('Database not initialized');
try {
await this.db.run('DELETE FROM storage WHERE key = ?', [key]);
return { success: true };
} catch (error) {
return { success: false, error: (error as Error).message };
}
}
async query<T>(sql: string, params: any[] = []): Promise<StorageResult<T[]>> {
if (!this.db) throw new Error('Database not initialized');
try {
const result = await this.db.query(sql, params);
return { success: true, value: (result.values || []) as T[] };
} catch (error) {
return { success: false, error: (error as Error).message };
}
}
private async runMigrations(): Promise<void> {
if (!this.db) throw new Error('Database not initialized');
await this.db.execute(`
CREATE TABLE IF NOT EXISTS storage (
key TEXT PRIMARY KEY,
value TEXT
)
`);
// Add additional migration scripts from migrations/ folder as needed
}
}
2. Encryption Service
// src/services/storage/EncryptionService.ts
import { SQLiteDBConnection } from '@capacitor-community/sqlite';
import { Capacitor } from '@capacitor/core';
export class EncryptionService {
private encryptionKey: string | null = null;
async getEncryptionKey(): Promise<string> {
if (!this.encryptionKey) {
// In a real implementation, use platform-specific secure storage
// This is a simplified example
this.encryptionKey = await this.generateKey();
}
return this.encryptionKey;
}
async initialize(db: SQLiteDBConnection): Promise<void> {
const key = await this.getEncryptionKey();
await db.execute(`PRAGMA key = '${key}'`);
await db.execute('PRAGMA cipher_default_kdf_iter = 64000');
await db.execute('PRAGMA cipher_page_size = 4096');
}
async encrypt(value: string): Promise<string> {
if (Capacitor.isNativePlatform()) {
// Use platform-specific encryption (e.g., iOS Keychain, Android Keystore)
return value; // Placeholder for native encryption
}
// Web Crypto API for web platform
const encoder = new TextEncoder();
const key = await this.getWebCryptoKey();
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
encoder.encode(value)
);
return btoa(String.fromCharCode(...new Uint8Array(iv), ...new Uint8Array(encrypted)));
}
async decrypt(value: string): Promise<string> {
if (Capacitor.isNativePlatform()) {
// Use platform-specific decryption
return value; // Placeholder for native decryption
}
// Web Crypto API for web platform
const data = Uint8Array.from(atob(value), c => c.charCodeAt(0));
const iv = data.slice(0, 12);
const encrypted = data.slice(12);
const key = await this.getWebCryptoKey();
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
encrypted
);
return new TextDecoder().decode(decrypted);
}
private async generateKey(): Promise<string> {
const key = crypto.getRandomValues(new Uint8Array(32));
return btoa(String.fromCharCode(...key));
}
private async getWebCryptoKey(): Promise<CryptoKey> {
const key = await this.getEncryptionKey();
return crypto.subtle.importKey(
'raw',
Uint8Array.from(atob(key), c => c.charCodeAt(0)),
{ name: 'AES-GCM' },
false,
['encrypt', 'decrypt']
);
}
}
3. Preferences Service
// src/services/storage/PreferencesService.ts
import { Preferences } from '@capacitor/preferences';
import { StorageService, StorageResult } from './types/storage.types';
export class PreferencesService implements StorageService {
async initialize(): Promise<void> {
// No initialization needed for Preferences API
}
async setItem<T>(key: string, value: T): Promise<StorageResult<T>> {
try {
await Preferences.set({ key, value: JSON.stringify(value) });
return { success: true, value };
} catch (error) {
return { success: false, error: (error as Error).message };
}
}
async getItem<T>(key: string): Promise<StorageResult<T>> {
try {
const { value } = await Preferences.get({ key });
if (!value) {
return { success: false, error: 'Key not found' };
}
return { success: true, value: JSON.parse(value) as T };
} catch (error) {
return { success: false, error: (error as Error).message };
}
}
async removeItem(key: string): Promise<StorageResult<void>> {
try {
await Preferences.remove({ key });
return { success: true };
} catch (error) {
return { success: false, error: (error as Error).message };
}
}
async query<T>(): Promise<StorageResult<T[]>> {
return { success: false, error: 'Query not supported in Preferences API' };
}
}
4. Key Management Service
// src/services/storage/KeyManagementService.ts
import { Capacitor } from '@capacitor/core';
interface SecureKeyOptions {
useBiometrics: boolean;
keySize: number;
keyAlgorithm: 'AES-GCM' | 'AES-CBC';
}
export class KeyManagementService {
private static instance: KeyManagementService;
private constructor() {}
static getInstance(): KeyManagementService {
if (!KeyManagementService.instance) {
KeyManagementService.instance = new KeyManagementService();
}
return KeyManagementService.instance;
}
async generateSecureKey(options: SecureKeyOptions): Promise<string> {
const key = await this.generateRandomKey(options.keySize);
if (Capacitor.isNativePlatform()) {
return Capacitor.getPlatform() === 'ios'
? this.encryptWithSecureEnclave(key, options)
: this.encryptWithAndroidKeystore(key, options);
}
return this.encryptWithWebCrypto(key, options);
}
private async generateRandomKey(keySize: number): Promise<string> {
const key = crypto.getRandomValues(new Uint8Array(keySize / 8));
return btoa(String.fromCharCode(...key));
}
private async encryptWithSecureEnclave(key: string, options: SecureKeyOptions): Promise<string> {
// iOS Secure Enclave implementation (placeholder)
// Use Keychain with biometric protection
return key; // Implement platform-specific code
}
private async encryptWithAndroidKeystore(key: string, options: SecureKeyOptions): Promise<string> {
// Android Keystore implementation (placeholder)
// Use EncryptedSharedPreferences with biometric protection
return key; // Implement platform-specific code
}
private async encryptWithWebCrypto(key: string, options: SecureKeyOptions): Promise<string> {
// Web Crypto API implementation
const encoder = new TextEncoder();
const cryptoKey = await crypto.subtle.generateKey(
{ name: options.keyAlgorithm, length: options.keySize },
true,
['encrypt', 'decrypt']
);
const exportedKey = await crypto.subtle.exportKey('raw', cryptoKey);
return btoa(String.fromCharCode(...new Uint8Array(exportedKey)));
}
}
5. Biometric Service
// src/services/storage/BiometricService.ts
import { Capacitor } from '@capacitor/core';
export class BiometricService {
async authenticate(): Promise<boolean> {
if (!Capacitor.isNativePlatform()) {
return true; // Web fallback (no biometric auth)
}
try {
// Use Capacitor Biometric plugin (e.g., @capacitor-community/biometric-auth)
// Placeholder for actual implementation
return true;
} catch (error) {
console.error('Biometric authentication failed:', error);
return false;
}
}
}
Migration Strategy
Two-Day Implementation Timeline (Revised)
Day 1: Core Implementation and Basic Security
Morning (4 hours):
-
Essential Setup (1 hour)
# Install core dependencies npm install @capacitor-community/sqlite@6.0.0 @capacitor/core@6.1.2 npx cap sync -
Core Services Implementation (3 hours)
- Implement basic
SQLiteServicewith encryption support - Create simplified
KeyManagementServicefor platform-specific key storage - Set up initial database schema
// Priority 1: Core tables export const initialSchema = ` CREATE TABLE IF NOT EXISTS accounts ( id INTEGER PRIMARY KEY AUTOINCREMENT, did TEXT UNIQUE, publicKeyHex TEXT, identity TEXT ); CREATE TABLE IF NOT EXISTS secret ( id INTEGER PRIMARY KEY, secret TEXT ); `; - Implement basic
Afternoon (4 hours):
-
Platform-Specific Security (2 hours)
- Implement basic platform detection
- Set up platform-specific key storage:
- iOS: Basic Keychain integration
- Android: Basic Keystore integration
- Web: Web Crypto API with IndexedDB
- Defer advanced features (Secure Enclave, StrongBox) to post-migration
-
Migration Utilities (2 hours)
- Create basic data export from Dexie
- Implement simple data import to SQLite
- Add basic validation
// Simplified migration utility export class MigrationUtils { async migrateData(): Promise<void> { // 1. Export from Dexie const data = await this.exportFromDexie(); // 2. Initialize SQLite await this.initializeSQLite(); // 3. Import data await this.importToSQLite(data); // 4. Basic validation await this.validateMigration(); } }
Day 2: Migration and Testing
Morning (4 hours):
-
Migration Implementation (2 hours)
- Implement migration script
- Add basic error handling
- Create rollback capability
export class DatabaseMigration { async migrate(): Promise<void> { try { // 1. Backup existing data await this.backupExistingData(); // 2. Perform migration await this.migrationUtils.migrateData(); // 3. Verify migration const isValid = await this.verifyMigration(); if (!isValid) { await this.rollback(); throw new Error('Migration validation failed'); } } catch (error) { await this.rollback(); throw error; } } } -
Basic Security Integration (2 hours)
- Implement basic biometric check
- Add simple key derivation
- Set up basic encryption
export class SecurityService { async initialize(): Promise<void> { // Basic security setup await this.setupEncryption(); await this.setupBiometrics(); } }
Afternoon (4 hours):
-
Testing and Validation (2 hours)
- Unit tests for core functionality
- Basic integration tests
- Platform-specific tests
describe('Database Migration', () => { it('should migrate accounts successfully', async () => { // Basic migration test }); it('should handle encryption correctly', async () => { // Basic encryption test }); }); -
Documentation and Cleanup (2 hours)
- Update documentation
- Clean up code
- Create basic backup/restore functionality
Post-Migration Features (To Be Implemented Later)
-
Enhanced Security
- Advanced platform-specific features (Secure Enclave, StrongBox)
- Sophisticated key rotation
- Advanced biometric integration
-
Advanced Backup
- Encrypted backup system
- Recovery key management
- Cross-device backup
-
Performance Optimization
- Query optimization
- Index management
- Caching strategies
Critical Path Items (Detailed Breakdown)
Build System Integration (Day 1 Morning)
-
Vite Configuration for Capacitor
// vite.config.ts import { defineConfig } from 'vite'; import { Capacitor } from '@capacitor/core'; export default defineConfig({ // ... existing config ... build: { // Ensure proper bundling for Capacitor target: 'es2015', rollupOptions: { output: { // Handle platform-specific code format: 'es', manualChunks: { 'capacitor-core': ['@capacitor/core'], 'sqlite': ['@capacitor-community/sqlite'] } } } }, plugins: [ // ... existing plugins ... { name: 'capacitor-platform', config(config, { command }) { // Add platform-specific environment variables return { define: { 'process.env.CAPACITOR_PLATFORM': JSON.stringify(Capacitor.getPlatform()) } }; } } ] }); -
Platform Detection and Conditional Imports
// src/services/storage/platform-detection.ts import { Capacitor } from '@capacitor/core'; export const isNativePlatform = Capacitor.isNativePlatform(); export const platform = Capacitor.getPlatform(); // Conditional imports for platform-specific code export const getPlatformService = async () => { if (!isNativePlatform) { return null; // Web platform uses existing implementation } switch (platform) { case 'ios': return (await import('./platforms/ios')).IOSStorageService; case 'android': return (await import('./platforms/android')).AndroidStorageService; default: throw new Error(`Unsupported platform: ${platform}`); } };
Day 1 Critical Path
-
Core Database Setup (Morning)
- SQLite plugin installation and configuration
- Basic database schema implementation
- Platform detection integration
- Vite build configuration for Capacitor
- Basic encryption setup
-
Platform-Specific Implementation (Afternoon)
- iOS Keychain integration
- Basic key storage
- Error handling
- Platform detection
- Android Keystore integration
- Basic key storage
- Error handling
- Platform detection
- Web platform detection
- Skip implementation for web
- Maintain existing Dexie implementation
- iOS Keychain integration
-
Migration Utilities (Afternoon)
- Data export from Dexie
- Account data
- Secret data
- Basic validation
- SQLite import
- Table creation
- Data import
- Basic error handling
- Data export from Dexie
Day 2 Critical Path
-
Migration Implementation (Morning)
- Migration script
// src/services/storage/migration/MigrationScript.ts export class MigrationScript { async execute(): Promise<void> { // 1. Platform check if (!isNativePlatform) { console.log('Skipping migration on web platform'); return; } // 2. Backup await this.backupExistingData(); // 3. Migration await this.performMigration(); // 4. Verification await this.verifyMigration(); } } - Rollback mechanism
- Error handling
- Platform-specific validation
- Migration script
-
Security Integration (Morning)
- Basic biometric check
- Key derivation
- Platform-specific encryption
- Error handling for security features
-
Testing and Validation (Afternoon)
- Unit tests
// src/services/storage/__tests__/StorageService.spec.ts describe('StorageService', () => { it('should use SQLite on native platforms', async () => { if (isNativePlatform) { const service = await getPlatformService(); expect(service).toBeDefined(); // ... more tests } }); it('should skip migration on web', async () => { if (!isNativePlatform) { const migration = new MigrationScript(); await migration.execute(); // Verify web implementation unchanged } }); }); - Integration tests
- Platform-specific tests
- Migration validation
- Unit tests
-
Documentation and Cleanup (Afternoon)
- Update documentation
- Code cleanup
- Basic backup/restore
- Build system verification
Build System Integration Details
-
Vite Configuration for Capacitor
// vite.config.ts import { defineConfig } from 'vite'; import { Capacitor } from '@capacitor/core'; const capacitorConfig = { // Platform-specific entry points input: { main: 'src/main.ts', capacitor: 'src/capacitor.ts' }, // Platform-specific output output: { format: 'es', dir: 'dist', entryFileNames: (chunkInfo) => { return chunkInfo.name === 'capacitor' ? 'capacitor/[name].[hash].js' : '[name].[hash].js'; } } }; export default defineConfig({ // ... existing config ... build: { ...capacitorConfig, rollupOptions: { external: [ // External dependencies for Capacitor '@capacitor/core', '@capacitor-community/sqlite' ], output: { globals: { '@capacitor/core': 'Capacitor', '@capacitor-community/sqlite': 'CapacitorSQLite' } } } } }); -
Platform-Specific Entry Point
// src/capacitor.ts import { Capacitor } from '@capacitor/core'; import { SQLiteService } from './services/storage/SQLiteService'; // Only initialize Capacitor-specific services on native platforms if (Capacitor.isNativePlatform()) { const initializeCapacitor = async () => { const sqliteService = SQLiteService.getInstance(); await sqliteService.initialize({ database: 'timesafari.db', encrypted: true, version: 1 }); }; initializeCapacitor().catch(console.error); } -
Build Scripts
// package.json { "scripts": { "build": "vite build", "build:capacitor": "vite build --mode capacitor", "build:ios": "vite build --mode capacitor --platform ios", "build:android": "vite build --mode capacitor --platform android", "cap:sync": "npm run build:capacitor && npx cap sync", "cap:ios": "npm run build:ios && npx cap sync ios", "cap:android": "npm run build:android && npx cap sync android" } } -
Environment Configuration
// src/config/environment.ts import { Capacitor } from '@capacitor/core'; export const environment = { isNative: Capacitor.isNativePlatform(), platform: Capacitor.getPlatform(), storage: { type: Capacitor.isNativePlatform() ? 'sqlite' : 'dexie', // Platform-specific configuration sqlite: { database: 'timesafari.db', encrypted: true, version: 1 } } };
Success Criteria (Updated)
-
Build System
- Vite configuration properly handles Capacitor builds
- Platform-specific code is correctly bundled
- Web implementation remains unchanged
- Build scripts work for all platforms
-
Day 1
- SQLite database operational on native platforms
- Basic encryption working
- Migration utilities ready
- Platform detection working
- Build system integration complete
-
Day 2
- Successful migration on native platforms
- Basic security implemented
- Core functionality tested
- Rollback capability verified
- Web implementation unaffected
Security Considerations
- Key Management: Keys stored in iOS Keychain, Android Keystore, or Web Crypto API. Never stored in plaintext.
- Data Protection: 256-bit AES-GCM encryption via SQLCipher. Data bound to device and user authentication.
- Platform-Specific:
- iOS: Secure Enclave and Keychain with Face ID/Touch ID.
- Android: Android Keystore with BiometricPrompt.
- Web: Web Crypto API with secure storage fallback.
Performance Considerations
- Use transactions for batch operations.
- Implement proper indexing for query performance.
- Cache frequently accessed data.
- Monitor memory usage and optimize large datasets.
Future Improvements
- Key rotation support.
- Backup and restore capabilities.
- Enhanced biometric options.
- Cross-device synchronization.
Maintenance
- Regular security audits.
- Platform-specific updates.
- Dependency management with semantic versioning.
- Performance monitoring.
Platform-Specific Implementation Details
iOS Implementation
-
Secure Enclave Integration
// src/services/storage/platforms/ios/SecureEnclaveService.ts import { Capacitor } from '@capacitor/core'; import { Keychain } from '@capacitor-community/native-keychain'; export class SecureEnclaveService { private static readonly KEYCHAIN_SERVICE = 'com.timesafari.securestorage'; private static readonly KEYCHAIN_ACCESS_GROUP = 'group.com.timesafari.securestorage'; async storeKey(key: string, options: SecureKeyOptions): Promise<void> { const accessControl = { // Require device to be unlocked accessible: Keychain.Accessible.WHEN_UNLOCKED, // Use Secure Enclave accessControl: Keychain.AccessControl.BIOMETRY_ANY, // Require user presence authenticationType: Keychain.AuthenticationType.BIOMETRICS, // Keychain access group for app extension sharing accessGroup: this.KEYCHAIN_ACCESS_GROUP }; await Keychain.set({ key: 'sqlite_encryption_key', value: key, service: this.KEYCHAIN_SERVICE, ...accessControl }); } async retrieveKey(): Promise<string> { const result = await Keychain.get({ key: 'sqlite_encryption_key', service: this.KEYCHAIN_SERVICE }); if (!result.value) { throw new Error('Encryption key not found in Keychain'); } return result.value; } } -
Face ID/Touch ID Integration
// src/services/storage/platforms/ios/BiometricService.ts import { BiometricAuth } from '@capacitor-community/biometric-auth'; export class IOSBiometricService { async authenticate(): Promise<boolean> { const available = await BiometricAuth.isAvailable(); if (!available.has) { return false; } try { const result = await BiometricAuth.verify({ reason: 'Authenticate to access secure data', title: 'TimeSafari Authentication', subtitle: 'Verify your identity', description: 'Use Face ID or Touch ID to access your secure data', negativeButtonText: 'Cancel' }); return result.verified; } catch (error) { console.error('Biometric authentication failed:', error); return false; } } }
Android Implementation
-
Android Keystore Integration
// src/services/storage/platforms/android/KeystoreService.ts import { AndroidKeystore } from '@capacitor-community/android-keystore'; export class AndroidKeystoreService { private static readonly KEY_ALIAS = 'timesafari_sqlite_key'; async storeKey(key: string, options: SecureKeyOptions): Promise<void> { const keyGenParameterSpec = { keyAlias: this.KEY_ALIAS, purposes: ['ENCRYPT', 'DECRYPT'], blockModes: ['GCM'], encryptionPaddings: ['NoPadding'], keySize: 256, userAuthenticationRequired: true, userAuthenticationValidityDurationSeconds: -1, // Use StrongBox if available isStrongBoxBacked: true }; await AndroidKeystore.generateKey(keyGenParameterSpec); await AndroidKeystore.encrypt({ keyAlias: this.KEY_ALIAS, data: key }); } async retrieveKey(): Promise<string> { const result = await AndroidKeystore.decrypt({ keyAlias: this.KEY_ALIAS }); return result.decryptedData; } } -
Biometric Integration
// src/services/storage/platforms/android/BiometricService.ts import { BiometricAuth } from '@capacitor-community/biometric-auth'; export class AndroidBiometricService { async authenticate(): Promise<boolean> { const available = await BiometricAuth.isAvailable(); if (!available.has) { return false; } try { const result = await BiometricAuth.verify({ reason: 'Authenticate to access secure data', title: 'TimeSafari Authentication', subtitle: 'Verify your identity', description: 'Use biometric authentication to access your secure data', negativeButtonText: 'Cancel', // Android-specific options allowDeviceCredential: true }); return result.verified; } catch (error) { console.error('Biometric authentication failed:', error); return false; } } }
Web Implementation
- Web Crypto API Integration
// src/services/storage/platforms/web/WebCryptoService.ts export class WebCryptoService { private static readonly KEY_NAME = 'timesafari_sqlite_key'; async storeKey(key: string): Promise<void> { // Generate a master key for encrypting the SQLite key const masterKey = await crypto.subtle.generateKey( { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); // Encrypt the SQLite key const iv = crypto.getRandomValues(new Uint8Array(12)); const encryptedKey = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, masterKey, new TextEncoder().encode(key) ); // Store encrypted key in IndexedDB const db = await this.getSecureDB(); await db.put('keys', { name: this.KEY_NAME, iv, data: encryptedKey }); // Export and store master key in secure storage const exportedKey = await crypto.subtle.exportKey('raw', masterKey); await this.storeMasterKey(exportedKey); } private async getSecureDB(): Promise<IDBDatabase> { // Implementation using IndexedDB with encryption } private async storeMasterKey(key: ArrayBuffer): Promise<void> { // Store in secure storage (e.g., localStorage with encryption) } }
Key Rotation and Backup Process
Key Rotation
-
Automatic Key Rotation
// src/services/storage/KeyRotationService.ts export class KeyRotationService { private static readonly ROTATION_INTERVAL = 30 * 24 * 60 * 60 * 1000; // 30 days async checkAndRotateKey(): Promise<void> { const lastRotation = await this.getLastRotationDate(); if (Date.now() - lastRotation > this.ROTATION_INTERVAL) { await this.rotateKey(); } } private async rotateKey(): Promise<void> { // 1. Generate new key const newKey = await this.generateNewKey(); // 2. Re-encrypt database with new key await this.reencryptDatabase(newKey); // 3. Store new key securely await this.storeNewKey(newKey); // 4. Update rotation timestamp await this.updateRotationDate(); } private async reencryptDatabase(newKey: string): Promise<void> { const sqliteService = SQLiteService.getInstance(); // Export all data const data = await sqliteService.exportAllData(); // Create new database with new key await sqliteService.initialize({ database: 'timesafari_new.db', encrypted: true, version: 1, key: newKey }); // Import data to new database await sqliteService.importData(data); // Verify data integrity await this.verifyDataIntegrity(); // Replace old database with new one await sqliteService.replaceDatabase('timesafari_new.db', 'timesafari.db'); } } -
Manual Key Rotation
// src/services/storage/KeyRotationService.ts export class KeyRotationService { async manualRotateKey(): Promise<void> { // Require user authentication const biometrics = new BiometricService(); const authenticated = await biometrics.authenticate(); if (!authenticated) { throw new Error('Authentication required for key rotation'); } await this.rotateKey(); } }
Backup Process
-
Secure Backup
// src/services/storage/BackupService.ts export class BackupService { async createBackup(): Promise<BackupResult> { // 1. Export database const data = await this.exportDatabase(); // 2. Export encryption keys const keys = await this.exportKeys(); // 3. Create encrypted backup const backup = await this.encryptBackup(data, keys); // 4. Store backup securely return this.storeBackup(backup); } private async exportDatabase(): Promise<DatabaseExport> { const sqliteService = SQLiteService.getInstance(); return { version: await sqliteService.getVersion(), tables: await sqliteService.exportTables(), metadata: await sqliteService.getMetadata() }; } private async exportKeys(): Promise<KeyExport> { const keyManagement = KeyManagementService.getInstance(); return { sqliteKey: await keyManagement.exportKey(), backupKey: await this.generateBackupKey() }; } private async encryptBackup( data: DatabaseExport, keys: KeyExport ): Promise<EncryptedBackup> { // Encrypt data with backup key const encryptedData = await this.encryptData(data, keys.backupKey); // Encrypt backup key with user's recovery key const encryptedBackupKey = await this.encryptBackupKey( keys.backupKey, await this.getRecoveryKey() ); return { data: encryptedData, backupKey: encryptedBackupKey, timestamp: Date.now(), version: '1.0' }; } } -
Restore Process
// src/services/storage/BackupService.ts export class BackupService { async restoreBackup(backup: EncryptedBackup): Promise<void> { // 1. Verify backup integrity await this.verifyBackup(backup); // 2. Decrypt backup key const backupKey = await this.decryptBackupKey( backup.backupKey, await this.getRecoveryKey() ); // 3. Decrypt data const data = await this.decryptData(backup.data, backupKey); // 4. Restore database await this.restoreDatabase(data); // 5. Verify restoration await this.verifyRestoration(); } }
Integration with Existing Security Model
-
Account Security Integration
// src/services/security/AccountSecurityService.ts export class AccountSecurityService { private storageService: StorageService; private keyManagement: KeyManagementService; async initialize(): Promise<void> { this.storageService = await StorageServiceFactory.create(); this.keyManagement = KeyManagementService.getInstance(); // Link SQLite encryption to account security await this.linkAccountSecurity(); } private async linkAccountSecurity(): Promise<void> { const account = await this.storageService.getActiveAccount(); if (account) { // Derive SQLite key from account credentials const sqliteKey = await this.deriveSQLiteKey(account); // Store derived key securely await this.keyManagement.storeKey(sqliteKey, { useBiometrics: true, keySize: 256, keyAlgorithm: 'AES-GCM' }); } } private async deriveSQLiteKey(account: Account): Promise<string> { // Use account credentials to derive SQLite key const input = `${account.did}:${account.publicKeyHex}`; return this.keyManagement.deriveKey(input); } } -
Biometric Integration
// src/services/security/BiometricSecurityService.ts export class BiometricSecurityService { async setupBiometricProtection(): Promise<void> { const biometrics = new BiometricService(); const available = await biometrics.isAvailable(); if (available) { // Enable biometric protection for SQLite await this.keyManagement.updateKeyProtection({ useBiometrics: true, requireAuthentication: true }); // Update app settings await this.storageService.updateSettings({ biometricProtection: true, lastBiometricSetup: Date.now() }); } } } -
Migration from Existing Security
// src/services/security/SecurityMigrationService.ts export class SecurityMigrationService { async migrateToNewSecurity(): Promise<void> { // 1. Export existing secure data const existingData = await this.exportExistingData(); // 2. Initialize new security model await this.initializeNewSecurity(); // 3. Import data with new security await this.importWithNewSecurity(existingData); // 4. Verify migration await this.verifySecurityMigration(); } private async exportExistingData(): Promise<SecureData> { // Export data from existing secure storage const accounts = await this.exportAccounts(); const secrets = await this.exportSecrets(); const settings = await this.exportSettings(); return { accounts, secrets, settings }; } private async initializeNewSecurity(): Promise<void> { // Set up new security infrastructure await this.keyManagement.initialize(); await this.storageService.initialize({ database: 'timesafari.db', encrypted: true, version: 1 }); } }
Detailed Platform Implementations
Web Platform (Dexie)
-
Database Initialization
// src/services/platforms/WebPlatformService.ts export class WebPlatformService implements PlatformService { private secretDB: Dexie; private accountsDB: Dexie | null = null; constructor() { // Initialize secret database this.secretDB = new Dexie('TimeSafariSecret'); this.secretDB.version(1).stores({ secret: 'id, secret' }); } async openAccountsDatabase(): Promise<void> { if (!this.accountsDB) { this.accountsDB = new Dexie('TimeSafariAccounts'); this.accountsDB.version(1).stores({ accounts: '++id, dateCreated, derivationPath, did, $identity, $mnemonic, publicKeyHex', settings: '++id, accountDid, *keys', contacts: '++id, did, name, *keys' }); // Apply encryption using master secret const secret = await this.getMasterSecret(); if (secret) { encrypted(this.accountsDB, { secretKey: secret.secret }); } } await this.accountsDB.open(); } async getAccountByDid(did: string): Promise<Account | undefined> { if (!this.accountsDB) { throw new Error('Accounts database not initialized'); } return await this.accountsDB.accounts .where('did') .equals(did) .first(); } // ... other methods ... } -
Error Handling
// src/services/platforms/WebPlatformService.ts export class WebPlatformService implements PlatformService { private async handleDatabaseError(operation: string, error: unknown): Promise<never> { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error(`Database error during ${operation}:`, error); if (error instanceof DexieError) { switch (error.name) { case 'QuotaExceededError': throw new Error('Storage quota exceeded. Please clear some space and try again.'); case 'InvalidTableError': throw new Error('Database schema mismatch. Please try clearing your browser data.'); case 'ConstraintError': throw new Error('Operation would violate database constraints.'); default: throw new Error(`Database error: ${errorMessage}`); } } throw new Error(`Failed to ${operation}: ${errorMessage}`); } async addAccount(account: Account): Promise<void> { try { if (!this.accountsDB) { throw new Error('Accounts database not initialized'); } await this.accountsDB.accounts.add(account); } catch (error) { await this.handleDatabaseError('add account', error); } } }
Capacitor Platform (SQLite)
-
Database Initialization with Security
// src/services/platforms/CapacitorPlatformService.ts export class CapacitorPlatformService implements PlatformService { private sqliteService: SQLiteService; private keyManagement: KeyManagementService; private biometricService: BiometricService; constructor() { this.sqliteService = SQLiteService.getInstance(); this.keyManagement = KeyManagementService.getInstance(); this.biometricService = BiometricService.getInstance(); } async openAccountsDatabase(): Promise<void> { try { // Check biometric authentication if required if (await this.shouldRequireBiometrics()) { const authenticated = await this.biometricService.authenticate(); if (!authenticated) { throw new Error('Biometric authentication required'); } } // Get encryption key from secure storage const key = await this.keyManagement.getEncryptionKey(); // Initialize SQLite with encryption await this.sqliteService.initialize({ database: 'timesafari_accounts.db', encrypted: true, version: 1, key }); // Set up database schema await this.setupDatabaseSchema(); } catch (error) { await this.handleDatabaseError('open accounts database', error); } } private async setupDatabaseSchema(): Promise<void> { const schema = ` CREATE TABLE IF NOT EXISTS accounts ( id INTEGER PRIMARY KEY AUTOINCREMENT, dateCreated TEXT NOT NULL, derivationPath TEXT, did TEXT UNIQUE NOT NULL, identity TEXT, mnemonic TEXT, publicKeyHex TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS settings ( id INTEGER PRIMARY KEY AUTOINCREMENT, accountDid TEXT NOT NULL, key TEXT NOT NULL, value TEXT, FOREIGN KEY (accountDid) REFERENCES accounts(did) ); CREATE TABLE IF NOT EXISTS contacts ( id INTEGER PRIMARY KEY AUTOINCREMENT, did TEXT UNIQUE NOT NULL, name TEXT, keys TEXT ); `; await this.sqliteService.execute(schema); } // ... other methods ... } -
Platform-Specific Security
// src/services/platforms/CapacitorPlatformService.ts export class CapacitorPlatformService implements PlatformService { private async shouldRequireBiometrics(): Promise<boolean> { const capabilities = this.getCapabilities(); if (!capabilities.hasBiometrics) { return false; } // Check user preferences const settings = await this.getDefaultSettings(); return settings?.requireBiometrics ?? false; } private async getPlatformKey(): Promise<string> { const capabilities = this.getCapabilities(); if (capabilities.isIOS) { // Use iOS Keychain with Secure Enclave return await this.keyManagement.getIOSKey({ useSecureEnclave: true, requireBiometrics: await this.shouldRequireBiometrics() }); } else if (capabilities.isAndroid) { // Use Android Keystore return await this.keyManagement.getAndroidKey({ useStrongBox: true, requireBiometrics: await this.shouldRequireBiometrics() }); } throw new Error('Unsupported platform for secure key storage'); } }
Usage Examples
-
Account Management
// src/services/AccountService.ts export class AccountService { constructor(private platformService: PlatformService) {} async createNewAccount(mnemonic: string): Promise<Account> { try { // Generate account details const [address, privateHex, publicHex, derivationPath] = deriveAddress(mnemonic); const account: Account = { dateCreated: new Date().toISOString(), derivationPath, did: `did:eth:${address}`, identity: JSON.stringify({ address, privateHex, publicHex }), mnemonic, publicKeyHex: publicHex }; // Save account await this.platformService.addAccount(account); // Set as active account await this.platformService.updateSettings('activeDid', { value: account.did }); return account; } catch (error) { if (error instanceof Error) { throw new Error(`Failed to create account: ${error.message}`); } throw error; } } async getActiveAccount(): Promise<Account | undefined> { const settings = await this.platformService.getDefaultSettings(); if (!settings?.activeDid) { return undefined; } return await this.platformService.getAccountByDid(settings.activeDid); } } -
Settings Management
// src/services/SettingsService.ts export class SettingsService { constructor(private platformService: PlatformService) {} async updateAccountSettings(did: string, changes: Partial<Settings>): Promise<void> { try { // Verify account exists const account = await this.platformService.getAccountByDid(did); if (!account) { throw new Error(`Account ${did} not found`); } // Update settings await this.platformService.updateAccountSettings(did, changes); // Log changes for audit await this.logSettingsChange(did, changes); } catch (error) { await this.handleSettingsError('update account settings', error); } } private async logSettingsChange(did: string, changes: Partial<Settings>): Promise<void> { // Implementation for audit logging } }
Error Handling and Edge Cases
-
Common Error Scenarios
// src/services/storage/errors/StorageError.ts export class StorageError extends Error { constructor( message: string, public readonly code: string, public readonly originalError?: unknown ) { super(message); this.name = 'StorageError'; } } export const StorageErrorCodes = { DATABASE_NOT_INITIALIZED: 'DB_NOT_INIT', ENCRYPTION_FAILED: 'ENC_FAILED', DECRYPTION_FAILED: 'DEC_FAILED', QUOTA_EXCEEDED: 'QUOTA_EXCEEDED', BIOMETRIC_REQUIRED: 'BIOMETRIC_REQUIRED', MIGRATION_FAILED: 'MIGRATION_FAILED', INVALID_DATA: 'INVALID_DATA' } as const; -
Error Recovery Strategies
// src/services/storage/error-recovery/ErrorRecoveryService.ts export class ErrorRecoveryService { constructor(private platformService: PlatformService) {} async handleStorageError(error: StorageError): Promise<void> { switch (error.code) { case StorageErrorCodes.DATABASE_NOT_INITIALIZED: await this.reinitializeDatabase(); break; case StorageErrorCodes.ENCRYPTION_FAILED: case StorageErrorCodes.DECRYPTION_FAILED: await this.handleEncryptionError(); break; case StorageErrorCodes.QUOTA_EXCEEDED: await this.handleQuotaExceeded(); break; case StorageErrorCodes.BIOMETRIC_REQUIRED: await this.handleBiometricRequired(); break; case StorageErrorCodes.MIGRATION_FAILED: await this.handleMigrationFailure(); break; default: throw error; // Re-throw unknown errors } } private async reinitializeDatabase(): Promise<void> { try { // Attempt to reinitialize with backup const backup = await this.platformService.exportDatabase(); await this.platformService.deleteDatabase(); await this.platformService.importDatabase(backup); } catch (error) { // If reinitialization fails, try clean initialization await this.platformService.deleteDatabase(); await this.platformService.openAccountsDatabase(); } } private async handleEncryptionError(): Promise<void> { // Attempt to recover encryption key const newKey = await this.keyManagement.regenerateKey(); await this.platformService.setMasterSecret({ id: MASTER_SECRET_KEY, secret: newKey }); } private async handleQuotaExceeded(): Promise<void> { // Implement cleanup strategy const accounts = await this.platformService.getAllAccounts(); const oldAccounts = accounts.filter(acc => new Date(acc.dateCreated).getTime() < Date.now() - 30 * 24 * 60 * 60 * 1000 ); for (const account of oldAccounts) { await this.platformService.deleteAccount(account.did); } } } -
Edge Cases and Mitigations
a. Concurrent Access
// src/services/storage/ConcurrencyManager.ts export class ConcurrencyManager { private locks: Map<string, Promise<void>> = new Map(); async withLock<T>(key: string, operation: () => Promise<T>): Promise<T> { const existingLock = this.locks.get(key); if (existingLock) { await existingLock; } const lock = new Promise<void>((resolve) => { this.locks.set(key, lock); }); try { return await operation(); } finally { this.locks.delete(key); lock.resolve(); } } }b. Data Integrity
// src/services/storage/DataIntegrityService.ts export class DataIntegrityService { async verifyDataIntegrity(): Promise<boolean> { const accounts = await this.platformService.getAllAccounts(); for (const account of accounts) { // Verify account data structure if (!this.isValidAccount(account)) { await this.repairAccount(account); continue; } // Verify settings exist const settings = await this.platformService.getAccountSettings(account.did); if (!settings) { await this.createDefaultSettings(account.did); } // Verify contacts const contacts = await this.platformService.getAllContacts(); const invalidContacts = contacts.filter(c => !this.isValidContact(c)); for (const contact of invalidContacts) { await this.repairContact(contact); } } return true; } private isValidAccount(account: Account): boolean { return ( account.did && account.publicKeyHex && account.dateCreated && (!account.identity || this.isValidJSON(account.identity)) ); } private async repairAccount(account: Account): Promise<void> { // Implementation for account repair } }c. Platform Transitions
// src/services/storage/PlatformTransitionService.ts export class PlatformTransitionService { async handlePlatformTransition(): Promise<void> { const capabilities = this.platformService.getCapabilities(); if (capabilities.isIOS) { await this.handleIOSTransition(); } else if (capabilities.isAndroid) { await this.handleAndroidTransition(); } } private async handleIOSTransition(): Promise<void> { // Handle iOS-specific transitions // e.g., moving from Keychain to Secure Enclave } private async handleAndroidTransition(): Promise<void> { // Handle Android-specific transitions // e.g., upgrading to StrongBox } }