You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
35 KiB
35 KiB
Secure Storage Implementation Guide for TimeSafari App
Overview
This document outlines the implementation of secure storage for the TimeSafari app using Capacitor solutions. The implementation focuses on:
-
Platform-Specific Storage Solutions:
- Web: wa-sqlite with IndexedDB backend
- iOS: SQLCipher with Keychain integration
- Android: SQLCipher with Keystore integration
- Electron: SQLite with secure storage
-
Key Features:
- Encrypted storage using SQLCipher
- Platform-specific security features
- Migration support from existing implementations
- Consistent API across platforms
Quick Start
1. Installation
# Core dependencies
npm install @capacitor-community/sqlite@6.0.0
npm install @wa-sqlite/sql.js@0.8.12
npm install @wa-sqlite/sql.js-httpvfs@0.8.12
# Platform-specific dependencies
npm install @capacitor/preferences@6.0.2
npm install @capacitor-community/biometric-auth@5.0.0
2. Basic Usage
// src/services/storage/StorageService.ts
import { PlatformServiceFactory } from '../PlatformServiceFactory';
import { StorageError, StorageErrorCodes } from './errors/StorageError';
export class StorageService {
private static instance: StorageService;
private platformService: PlatformService;
private constructor() {
this.platformService = PlatformServiceFactory.create();
}
static getInstance(): StorageService {
if (!StorageService.instance) {
StorageService.instance = new StorageService();
}
return StorageService.instance;
}
async initialize(): Promise<void> {
try {
// Initialize databases
await this.platformService.openSecretDatabase();
await this.platformService.openAccountsDatabase();
// Check for migration
if (await this.platformService.needsMigration()) {
await this.handleMigration();
}
} catch (error) {
throw new StorageError(
'Failed to initialize storage service',
StorageErrorCodes.INITIALIZATION_FAILED,
error
);
}
}
private async handleMigration(): Promise<void> {
try {
// Show migration UI
const shouldMigrate = await this.showMigrationPrompt();
if (!shouldMigrate) return;
// Perform migration
await this.platformService.performMigration();
// Verify migration
await this.verifyMigration();
} catch (error) {
// Handle migration failure
await this.handleMigrationError(error);
}
}
// Example: Adding an account
async addAccount(account: Account): Promise<void> {
try {
await this.platformService.addAccount(account);
} catch (error) {
if (error instanceof StorageError) {
throw error;
}
throw new StorageError(
'Failed to add account',
StorageErrorCodes.QUERY_FAILED,
error
);
}
}
// Example: Retrieving an account
async getAccountByDid(did: string): Promise<Account | undefined> {
try {
return await this.platformService.getAccountByDid(did);
} catch (error) {
if (error instanceof StorageError) {
throw error;
}
throw new StorageError(
'Failed to retrieve account',
StorageErrorCodes.QUERY_FAILED,
error
);
}
}
}
// Usage example:
const storageService = StorageService.getInstance();
await storageService.initialize();
try {
const account = await storageService.getAccountByDid('did:example:123');
if (!account) {
await storageService.addAccount({
did: 'did:example:123',
publicKeyHex: '0x123...',
// ... other account properties
});
}
} catch (error) {
if (error instanceof StorageError) {
console.error(`Storage error: ${error.code}`, error.message);
} else {
console.error('Unexpected error:', error);
}
}
3. Platform Detection
// src/services/storage/PlatformDetection.ts
import { Capacitor } from '@capacitor/core';
import { StorageError, StorageErrorCodes } from './errors/StorageError';
export class PlatformDetection {
static isNativePlatform(): boolean {
return Capacitor.isNativePlatform();
}
static getPlatform(): 'ios' | 'android' | 'web' | 'electron' {
if (Capacitor.isNativePlatform()) {
return Capacitor.getPlatform() as 'ios' | 'android';
}
return window.electron ? 'electron' : 'web';
}
static async getCapabilities(): Promise<PlatformCapabilities> {
try {
const platform = this.getPlatform();
return {
hasFileSystem: platform !== 'web',
hasSecureStorage: platform !== 'web',
hasBiometrics: await this.checkBiometrics(),
isIOS: platform === 'ios',
isAndroid: platform === 'android'
};
} catch (error) {
throw new StorageError(
'Failed to detect platform capabilities',
StorageErrorCodes.INITIALIZATION_FAILED,
error
);
}
}
private static async checkBiometrics(): Promise<boolean> {
if (!this.isNativePlatform()) return false;
try {
const { BiometricAuth } = await import('@capacitor-community/biometric-auth');
const available = await BiometricAuth.isAvailable();
return available.has;
} catch (error) {
console.warn('Biometric check failed:', error);
return false;
}
}
}
// Usage example:
try {
const capabilities = await PlatformDetection.getCapabilities();
if (capabilities.hasSecureStorage) {
// Use platform-specific secure storage
await initializeSecureStorage();
} else {
// Fall back to web storage
await initializeWebStorage();
}
} catch (error) {
if (error instanceof StorageError) {
console.error(`Platform detection error: ${error.code}`, error.message);
} else {
console.error('Unexpected error during platform detection:', error);
}
}
4. Platform-Specific Implementations
Web Platform (wa-sqlite)
// src/services/platforms/web/WebSQLiteService.ts
export class WebSQLiteService implements PlatformService {
private db: SQLite.Database | null = null;
private vfs: IDBBatchAtomicVFS | null = null;
private initialized = false;
async initialize(): Promise<void> {
if (this.initialized) return;
try {
// 1. Initialize SQLite
const sqlite3 = await this.initializeSQLite();
// 2. Set up VFS
await this.setupVFS(sqlite3);
// 3. Open database
await this.openDatabase();
// 4. Set up schema
await this.setupSchema();
// 5. Optimize performance
await this.optimizePerformance();
this.initialized = true;
} catch (error) {
throw new StorageError(
'Failed to initialize web SQLite',
StorageErrorCodes.INITIALIZATION_FAILED,
error
);
}
}
private async initializeSQLite(): Promise<SQLite.SqlJsStatic> {
try {
return await SQLite.init({
locateFile: file => `https://cdn.jsdelivr.net/npm/@wa-sqlite/sql.js@0.8.12/dist/${file}`
});
} catch (error) {
throw new StorageError(
'Failed to load SQLite WebAssembly',
StorageErrorCodes.WASM_LOAD_FAILED,
error
);
}
}
private async setupVFS(sqlite3: SQLite.SqlJsStatic): Promise<void> {
try {
this.vfs = new IDBBatchAtomicVFS('timesafari');
await this.vfs.registerVFS(sqlite3);
} catch (error) {
throw new StorageError(
'Failed to set up IndexedDB VFS',
StorageErrorCodes.VFS_SETUP_FAILED,
error
);
}
}
async openDatabase(): Promise<void> {
if (!this.vfs) {
throw new StorageError(
'VFS not initialized',
StorageErrorCodes.INITIALIZATION_FAILED
);
}
try {
this.db = await this.vfs.openDatabase('timesafari.db');
await this.setupPragmas();
} catch (error) {
throw new StorageError(
'Failed to open database',
StorageErrorCodes.INITIALIZATION_FAILED,
error
);
}
}
private async setupPragmas(): Promise<void> {
if (!this.db) return;
try {
await this.db.exec(`
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;
`);
} catch (error) {
throw new StorageError(
'Failed to set up database pragmas',
StorageErrorCodes.INITIALIZATION_FAILED,
error
);
}
}
async close(): Promise<void> {
if (this.db) {
await this.db.close();
this.db = null;
}
this.initialized = false;
}
}
// Migration strategy for web platform
export class WebMigrationService {
async migrate(): Promise<void> {
// 1. Check prerequisites
await this.checkPrerequisites();
// 2. Create backup
const backup = await this.createBackup();
// 3. Perform migration
try {
await this.performMigration(backup);
} catch (error) {
// 4. Handle failure
await this.handleMigrationFailure(error, backup);
}
// 5. Verify migration
await this.verifyMigration(backup);
}
private async checkPrerequisites(): Promise<void> {
// Check IndexedDB availability
if (!window.indexedDB) {
throw new StorageError(
'IndexedDB not available',
StorageErrorCodes.INITIALIZATION_FAILED
);
}
// Check storage quota
const quota = await navigator.storage.estimate();
if (quota.quota && quota.usage && quota.usage > quota.quota * 0.9) {
throw new StorageError(
'Insufficient storage space',
StorageErrorCodes.INITIALIZATION_FAILED
);
}
}
private async createBackup(): Promise<MigrationBackup> {
const backup = {
timestamp: Date.now(),
accounts: await this.dexieDB.accounts.toArray(),
settings: await this.dexieDB.settings.toArray(),
contacts: await this.dexieDB.contacts.toArray()
};
// Store backup in IndexedDB
await this.storeBackup(backup);
return backup;
}
}
Native Platform (iOS/Android)
// src/services/platforms/native/NativeSQLiteService.ts
export class NativeSQLiteService implements PlatformService {
private db: SQLiteConnection | null = null;
private initialized = false;
async initialize(): Promise<void> {
if (this.initialized) return;
try {
// 1. Check platform capabilities
await this.checkPlatformCapabilities();
// 2. Initialize SQLite with encryption
await this.initializeEncryptedDatabase();
// 3. Set up schema
await this.setupSchema();
this.initialized = true;
} catch (error) {
throw new StorageError(
'Failed to initialize native SQLite',
StorageErrorCodes.INITIALIZATION_FAILED,
error
);
}
}
private async checkPlatformCapabilities(): Promise<void> {
const { Capacitor } = await import('@capacitor/core');
if (!Capacitor.isNativePlatform()) {
throw new StorageError(
'Not running on native platform',
StorageErrorCodes.INITIALIZATION_FAILED
);
}
}
private async initializeEncryptedDatabase(): Promise<void> {
const { SQLite } = await import('@capacitor-community/sqlite');
this.db = await SQLite.createConnection(
'timesafari',
false,
'encryption',
1,
false
);
await this.db.open();
}
async setupSchema(): Promise<void> {
if (!this.db) {
throw new StorageError(
'Database not initialized',
StorageErrorCodes.INITIALIZATION_FAILED
);
}
try {
await this.db.execute(`
CREATE TABLE IF NOT EXISTS accounts (
did TEXT PRIMARY KEY,
public_key_hex TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS contacts (
did TEXT PRIMARY KEY,
name TEXT NOT NULL,
public_key_hex TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
`);
} catch (error) {
throw new StorageError(
'Failed to set up database schema',
StorageErrorCodes.INITIALIZATION_FAILED,
error
);
}
}
async close(): Promise<void> {
if (this.db) {
await this.db.close();
this.db = null;
}
this.initialized = false;
}
}
5. Error Handling
// src/services/storage/errors/StorageError.ts
export enum StorageErrorCodes {
INITIALIZATION_FAILED = 'STORAGE_INIT_FAILED',
QUERY_FAILED = 'STORAGE_QUERY_FAILED',
MIGRATION_FAILED = 'STORAGE_MIGRATION_FAILED',
ENCRYPTION_FAILED = 'STORAGE_ENCRYPTION_FAILED',
DECRYPTION_FAILED = 'STORAGE_DECRYPTION_FAILED',
INVALID_DATA = 'STORAGE_INVALID_DATA',
DATABASE_CORRUPTED = 'STORAGE_DB_CORRUPTED',
INSUFFICIENT_PERMISSIONS = 'STORAGE_INSUFFICIENT_PERMISSIONS',
STORAGE_FULL = 'STORAGE_FULL',
CONCURRENT_ACCESS = 'STORAGE_CONCURRENT_ACCESS'
}
export class StorageError extends Error {
constructor(
message: string,
public code: StorageErrorCodes,
public originalError?: unknown
) {
super(message);
this.name = 'StorageError';
}
static isStorageError(error: unknown): error is StorageError {
return error instanceof StorageError;
}
static fromUnknown(error: unknown, context: string): StorageError {
if (this.isStorageError(error)) {
return error;
}
return new StorageError(
`${context}: ${error instanceof Error ? error.message : String(error)}`,
StorageErrorCodes.QUERY_FAILED,
error
);
}
}
// Error recovery strategies
export class StorageErrorRecovery {
static async handleError(error: StorageError): Promise<void> {
switch (error.code) {
case StorageErrorCodes.DATABASE_CORRUPTED:
await this.handleCorruptedDatabase();
break;
case StorageErrorCodes.STORAGE_FULL:
await this.handleStorageFull();
break;
case StorageErrorCodes.CONCURRENT_ACCESS:
await this.handleConcurrentAccess();
break;
default:
throw error; // Re-throw unhandled errors
}
}
private static async handleCorruptedDatabase(): Promise<void> {
// 1. Attempt to repair
try {
await this.repairDatabase();
} catch {
// 2. If repair fails, restore from backup
await this.restoreFromBackup();
}
}
private static async handleStorageFull(): Promise<void> {
// 1. Clean up temporary files
await this.cleanupTempFiles();
// 2. If still full, notify user
const isStillFull = await this.checkStorageFull();
if (isStillFull) {
throw new StorageError(
'Storage is full. Please free up space.',
StorageErrorCodes.STORAGE_FULL
);
}
}
private static async handleConcurrentAccess(): Promise<void> {
// Implement retry logic with exponential backoff
await this.retryWithBackoff(async () => {
// Attempt operation again
});
}
}
6. Testing Strategy
// src/services/storage/__tests__/StorageService.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { StorageService } from '../StorageService';
import { StorageError, StorageErrorCodes } from '../errors/StorageError';
import { PlatformDetection } from '../PlatformDetection';
describe('StorageService', () => {
let storageService: StorageService;
beforeEach(async () => {
storageService = StorageService.getInstance();
await storageService.initialize();
});
afterEach(async () => {
// Clean up test data
await cleanupTestData();
});
describe('Account Operations', () => {
it('should add and retrieve an account', async () => {
const account = {
did: 'did:test:123',
publicKeyHex: '0x123...',
// ... other properties
};
await storageService.addAccount(account);
const retrieved = await storageService.getAccountByDid(account.did);
expect(retrieved).toBeDefined();
expect(retrieved?.did).toBe(account.did);
});
it('should handle duplicate accounts', async () => {
const account = {
did: 'did:test:123',
publicKeyHex: '0x123...',
};
await storageService.addAccount(account);
await expect(
storageService.addAccount(account)
).rejects.toThrow(StorageError);
});
});
describe('Error Handling', () => {
it('should handle database corruption', async () => {
// Simulate database corruption
await simulateDatabaseCorruption();
await expect(
storageService.getAccountByDid('did:test:123')
).rejects.toThrow(StorageError);
// Verify recovery
const recovered = await storageService.getAccountByDid('did:test:123');
expect(recovered).toBeDefined();
});
it('should handle concurrent access', async () => {
const promises = Array(5).fill(null).map(() =>
storageService.addAccount({
did: `did:test:${Math.random()}`,
publicKeyHex: '0x123...',
})
);
const results = await Promise.allSettled(promises);
const errors = results.filter(r => r.status === 'rejected');
expect(errors.length).toBeLessThan(promises.length);
});
});
describe('Platform-Specific Tests', () => {
it('should use correct storage implementation', async () => {
const capabilities = await PlatformDetection.getCapabilities();
if (capabilities.hasSecureStorage) {
// Verify native storage implementation
expect(storageService.getImplementation()).toBe('native');
} else {
// Verify web storage implementation
expect(storageService.getImplementation()).toBe('web');
}
});
it('should handle platform transitions', async () => {
// Simulate platform change (e.g., web to native)
await simulatePlatformChange();
// Verify data persistence
const account = await storageService.getAccountByDid('did:test:123');
expect(account).toBeDefined();
});
});
});
// Helper functions for testing
async function cleanupTestData(): Promise<void> {
// Implementation
}
async function simulateDatabaseCorruption(): Promise<void> {
// Implementation
}
async function simulatePlatformChange(): Promise<void> {
// Implementation
}
Additional Platform-Specific Tests
// src/services/storage/__tests__/WebSQLiteService.spec.ts
import { WebSQLiteService } from '../platforms/web/WebSQLiteService';
import { StorageError, StorageErrorCodes } from '../errors/StorageError';
describe('WebSQLiteService', () => {
let service: WebSQLiteService;
beforeEach(async () => {
service = new WebSQLiteService();
await service.initialize();
});
afterEach(async () => {
await service.close();
});
it('should initialize successfully', async () => {
expect(service.isInitialized()).toBe(true);
});
it('should handle IndexedDB errors', async () => {
// Mock IndexedDB failure
const mockIndexedDB = jest.spyOn(window, 'indexedDB', 'get');
mockIndexedDB.mockImplementation(() => undefined);
await expect(service.initialize()).rejects.toThrow(
new StorageError(
'IndexedDB not available',
StorageErrorCodes.INITIALIZATION_FAILED
)
);
});
it('should migrate data correctly', async () => {
// Set up test data
const testAccount = createTestAccount();
await dexieDB.accounts.add(testAccount);
// Perform migration
await service.migrate();
// Verify migration
const migratedAccount = await service.getAccountByDid(testAccount.did);
expect(migratedAccount).toEqual(testAccount);
});
});
// Integration tests
describe('StorageService Integration', () => {
it('should handle concurrent access', async () => {
const service1 = StorageService.getInstance();
const service2 = StorageService.getInstance();
// Simulate concurrent access
const [result1, result2] = await Promise.all([
service1.addAccount(testAccount1),
service2.addAccount(testAccount2)
]);
// Verify both operations succeeded
expect(result1).toBeDefined();
expect(result2).toBeDefined();
});
it('should recover from errors', async () => {
const service = StorageService.getInstance();
// Simulate database corruption
await simulateDatabaseCorruption();
// Attempt recovery
await service.recover();
// Verify data integrity
const accounts = await service.getAllAccounts();
expect(accounts).toBeDefined();
});
});
7. Troubleshooting Guide
Detailed Recovery Procedures
-
Database Corruption Recovery
async recoverFromCorruption(): Promise<void> { // 1. Stop all database operations await this.stopDatabaseOperations(); // 2. Create backup of corrupted database const backup = await this.createEmergencyBackup(); // 3. Attempt repair try { await this.repairDatabase(); } catch (error) { // 4. Restore from backup if repair fails await this.restoreFromBackup(backup); } }
-
Migration Recovery
async recoverFromFailedMigration(): Promise<void> { // 1. Identify migration stage const stage = await this.getMigrationStage(); // 2. Execute appropriate recovery switch (stage) { case 'backup': await this.recoverFromBackupStage(); break; case 'migration': await this.recoverFromMigrationStage(); break; case 'verification': await this.recoverFromVerificationStage(); break; } }
-
Performance Troubleshooting
- Monitor database size and growth
- Check query performance with EXPLAIN
- Review indexing strategy
- Monitor memory usage
- Check for connection leaks
Success Criteria
-
Functionality
- All CRUD operations work correctly
- Migration process completes successfully
- Error handling works as expected
- Platform-specific features function correctly
-
Performance
- Database operations complete within acceptable time
- Memory usage remains stable
- IndexedDB quota usage is monitored
- Concurrent operations work correctly
-
Security
- Data is properly encrypted
- Keys are securely stored
- Platform-specific security features work
- No sensitive data leaks
-
Testing
- All unit tests pass
- Integration tests complete successfully
- Edge cases are handled
- Error recovery works as expected
Appendix
A. Database Schema
-- Accounts Table
CREATE TABLE accounts (
did TEXT PRIMARY KEY,
public_key_hex TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
-- Settings Table
CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL
);
-- Contacts Table
CREATE TABLE contacts (
did TEXT PRIMARY KEY,
name TEXT NOT NULL,
public_key_hex TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
-- Indexes
CREATE INDEX idx_accounts_created_at ON accounts(created_at);
CREATE INDEX idx_contacts_created_at ON contacts(created_at);
CREATE INDEX idx_settings_updated_at ON settings(updated_at);
B. Error Codes Reference
Code | Description | Recovery Action |
---|---|---|
STORAGE_INIT_FAILED |
Database initialization failed | Check permissions, storage space |
STORAGE_QUERY_FAILED |
Database query failed | Verify query, check connection |
STORAGE_MIGRATION_FAILED |
Data migration failed | Use backup, manual migration |
STORAGE_ENCRYPTION_FAILED |
Data encryption failed | Check key management |
STORAGE_DECRYPTION_FAILED |
Data decryption failed | Verify encryption key |
STORAGE_INVALID_DATA |
Invalid data format | Validate input data |
STORAGE_DB_CORRUPTED |
Database corruption detected | Use backup, repair |
STORAGE_INSUFFICIENT_PERMISSIONS |
Missing required permissions | Request permissions |
STORAGE_FULL |
Storage quota exceeded | Clean up, increase quota |
STORAGE_CONCURRENT_ACCESS |
Concurrent access conflict | Implement retry logic |
C. Platform Capabilities Matrix
Feature | Web | iOS | Android | Electron |
---|---|---|---|---|
SQLite | wa-sqlite | SQLCipher | SQLCipher | SQLite |
Encryption | SQLCipher | SQLCipher | SQLCipher | SQLCipher |
Secure Storage | IndexedDB | Keychain | Keystore | Secure Storage |
Biometrics | No | Yes | Yes | No |
File System | Limited | Full | Full | Full |
Background Sync | No | Yes | Yes | Yes |
Storage Quota | Yes | No | No | No |
Multi-tab Support | Yes | N/A | N/A | Yes |
D. Usage Examples
Before (Using Dexie.js)
// src/services/storage/legacy/AccountService.ts
import Dexie from 'dexie';
class AccountDatabase extends Dexie {
accounts: Dexie.Table<Account, string>;
settings: Dexie.Table<Setting, string>;
contacts: Dexie.Table<Contact, string>;
constructor() {
super('TimeSafariDB');
this.version(1).stores({
accounts: 'did, publicKeyHex, createdAt, updatedAt',
settings: 'key, value, updatedAt',
contacts: 'did, name, publicKeyHex, createdAt, updatedAt'
});
}
}
export class AccountService {
private db: AccountDatabase;
constructor() {
this.db = new AccountDatabase();
}
// Account Management
async addAccount(account: Account): Promise<void> {
try {
await this.db.accounts.add(account);
} catch (error) {
if (error instanceof Dexie.ConstraintError) {
throw new Error('Account already exists');
}
throw error;
}
}
async getAccount(did: string): Promise<Account | undefined> {
return await this.db.accounts.get(did);
}
// Settings Management
async updateSetting(key: string, value: string): Promise<void> {
await this.db.settings.put({
key,
value,
updatedAt: Date.now()
});
}
async getSetting(key: string): Promise<string | undefined> {
const setting = await this.db.settings.get(key);
return setting?.value;
}
// Contact Management
async addContact(contact: Contact): Promise<void> {
await this.db.contacts.add(contact);
}
async getContacts(): Promise<Contact[]> {
return await this.db.contacts.toArray();
}
}
// Usage Example
const accountService = new AccountService();
// Add an account
await accountService.addAccount({
did: 'did:example:123',
publicKeyHex: '0x123...',
createdAt: Date.now(),
updatedAt: Date.now()
});
// Update settings
await accountService.updateSetting('theme', 'dark');
// Add a contact
await accountService.addContact({
did: 'did:example:456',
name: 'Alice',
publicKeyHex: '0x456...',
createdAt: Date.now(),
updatedAt: Date.now()
});
After (Using Platform Service)
// src/services/storage/AccountService.ts
import { StorageService } from './StorageService';
import { StorageError, StorageErrorCodes } from './errors/StorageError';
import { PlatformDetection } from './PlatformDetection';
export class AccountService {
private static instance: AccountService;
private storageService: StorageService;
private constructor() {
this.storageService = StorageService.getInstance();
}
static getInstance(): AccountService {
if (!AccountService.instance) {
AccountService.instance = new AccountService();
}
return AccountService.instance;
}
async initialize(): Promise<void> {
try {
// Initialize storage with platform-specific implementation
await this.storageService.initialize();
// Check for migration if needed
if (await this.storageService.needsMigration()) {
await this.handleMigration();
}
} catch (error) {
throw StorageError.fromUnknown(error, 'Failed to initialize account service');
}
}
// Account Management with Platform-Specific Features
async addAccount(account: Account): Promise<void> {
try {
// Check platform capabilities
const capabilities = await PlatformDetection.getCapabilities();
// Add platform-specific metadata
const enhancedAccount = {
...account,
platform: capabilities.isIOS ? 'ios' :
capabilities.isAndroid ? 'android' :
capabilities.hasSecureStorage ? 'electron' : 'web',
secureStorage: capabilities.hasSecureStorage,
biometricsEnabled: capabilities.hasBiometrics
};
await this.storageService.addAccount(enhancedAccount);
// If platform supports biometrics, offer to enable it
if (capabilities.hasBiometrics) {
await this.offerBiometricSetup(account.did);
}
} catch (error) {
if (error instanceof StorageError) {
throw error;
}
throw new StorageError(
'Failed to add account',
StorageErrorCodes.QUERY_FAILED,
error
);
}
}
async getAccount(did: string): Promise<Account | undefined> {
try {
const account = await this.storageService.getAccountByDid(did);
// Verify account integrity
if (account) {
await this.verifyAccountIntegrity(account);
}
return account;
} catch (error) {
throw StorageError.fromUnknown(error, `Failed to get account ${did}`);
}
}
// Settings Management with Encryption
async updateSetting(key: string, value: string): Promise<void> {
try {
const capabilities = await PlatformDetection.getCapabilities();
// Encrypt sensitive settings if platform supports it
const processedValue = capabilities.hasSecureStorage ?
await this.encryptSetting(value) : value;
await this.storageService.updateSettings({
key,
value: processedValue,
updatedAt: Date.now()
});
} catch (error) {
throw StorageError.fromUnknown(error, `Failed to update setting ${key}`);
}
}
async getSetting(key: string): Promise<string | undefined> {
try {
const setting = await this.storageService.getAccountSettings(key);
if (setting?.value) {
const capabilities = await PlatformDetection.getCapabilities();
// Decrypt if the setting was encrypted
return capabilities.hasSecureStorage ?
await this.decryptSetting(setting.value) :
setting.value;
}
return undefined;
} catch (error) {
throw StorageError.fromUnknown(error, `Failed to get setting ${key}`);
}
}
// Contact Management with Platform Integration
async addContact(contact: Contact): Promise<void> {
try {
const capabilities = await PlatformDetection.getCapabilities();
// Add platform-specific features
const enhancedContact = {
...contact,
platform: capabilities.isIOS ? 'ios' :
capabilities.isAndroid ? 'android' :
capabilities.hasSecureStorage ? 'electron' : 'web',
syncEnabled: capabilities.hasBackgroundSync
};
await this.storageService.addContact(enhancedContact);
// If platform supports background sync, schedule contact sync
if (capabilities.hasBackgroundSync) {
await this.scheduleContactSync(contact.did);
}
} catch (error) {
throw StorageError.fromUnknown(error, 'Failed to add contact');
}
}
async getContacts(): Promise<Contact[]> {
try {
const contacts = await this.storageService.getAllContacts();
// Verify contact data integrity
await Promise.all(contacts.map(contact =>
this.verifyContactIntegrity(contact)
));
return contacts;
} catch (error) {
throw StorageError.fromUnknown(error, 'Failed to get contacts');
}
}
// Platform-Specific Helper Methods
private async offerBiometricSetup(did: string): Promise<void> {
const { BiometricAuth } = await import('@capacitor-community/biometric-auth');
const available = await BiometricAuth.isAvailable();
if (available.has) {
// Show biometric setup prompt
// Implementation depends on UI framework
}
}
private async verifyAccountIntegrity(account: Account): Promise<void> {
// Verify account data integrity
// Implementation depends on security requirements
}
private async verifyContactIntegrity(contact: Contact): Promise<void> {
// Verify contact data integrity
// Implementation depends on security requirements
}
private async encryptSetting(value: string): Promise<string> {
// Encrypt sensitive settings
// Implementation depends on encryption requirements
return value; // Placeholder
}
private async decryptSetting(value: string): Promise<string> {
// Decrypt sensitive settings
// Implementation depends on encryption requirements
return value; // Placeholder
}
private async scheduleContactSync(did: string): Promise<void> {
// Schedule background sync for contacts
// Implementation depends on platform capabilities
}
}
// Usage Example
const accountService = AccountService.getInstance();
// Initialize with platform detection
await accountService.initialize();
try {
// Add an account with platform-specific features
await accountService.addAccount({
did: 'did:example:123',
publicKeyHex: '0x123...',
createdAt: Date.now(),
updatedAt: Date.now()
});
// Update settings with encryption if available
await accountService.updateSetting('theme', 'dark');
await accountService.updateSetting('apiKey', 'sensitive-data');
// Add a contact with platform integration
await accountService.addContact({
did: 'did:example:456',
name: 'Alice',
publicKeyHex: '0x456...',
createdAt: Date.now(),
updatedAt: Date.now()
});
// Retrieve data with integrity verification
const account = await accountService.getAccount('did:example:123');
const contacts = await accountService.getContacts();
const theme = await accountService.getSetting('theme');
console.log('Account:', account);
console.log('Contacts:', contacts);
console.log('Theme:', theme);
} catch (error) {
if (error instanceof StorageError) {
console.error(`Storage error: ${error.code}`, error.message);
} else {
console.error('Unexpected error:', error);
}
}
Key improvements in the new implementation:
-
Platform Awareness:
- Automatically detects platform capabilities
- Uses platform-specific features (biometrics, secure storage)
- Handles platform transitions gracefully
-
Enhanced Security:
- Encrypts sensitive data when platform supports it
- Verifies data integrity
- Uses platform-specific secure storage
-
Better Error Handling:
- Consistent error types and codes
- Platform-specific error recovery
- Detailed error messages
-
Migration Support:
- Automatic migration detection
- Data integrity verification
- Backup and recovery
-
Platform Integration:
- Background sync for contacts
- Biometric authentication
- Secure storage for sensitive data
-
Type Safety:
- Strong typing throughout
- Platform capability type checking
- Error type narrowing
-
Singleton Pattern:
- Single instance management
- Consistent state across the app
- Resource sharing
-
Extensibility:
- Easy to add new platform features
- Modular design
- Clear separation of concerns