Browse Source
- Add Capacitor-specific DatabaseBackupService implementation - Update PlatformServiceFactory to correctly load platform services - Fix Filesystem API usage in Capacitor backup service - Add detailed logging throughout backup process - Improve error handling and cleanup of temporary files - Update web platform backup implementation - Add proper TypeScript types and documentation This commit implements a robust platform-specific backup service that: - Uses Capacitor's Filesystem and Share APIs for mobile platforms - Properly handles file paths and URIs - Includes comprehensive logging for debugging - Cleans up temporary files after sharing - Maintains consistent interface across platformsdb-backup-cross-platform
33 changed files with 1155 additions and 157 deletions
@ -1 +1,5 @@ |
|||
PLATFORM=mobile |
|||
VITE_ENDORSER_API_URL=https://test-api.endorser.ch/api/v2/claim |
|||
VITE_PARTNER_API_URL=https://test-api.partner.ch/api/v2 |
|||
VITE_IMAGE_API_URL=https://test-api.images.ch/api/v2 |
|||
VITE_PUSH_SERVER_URL=https://test-api.push.ch/api/v2 |
Binary file not shown.
@ -1,2 +1,2 @@ |
|||
#Fri Mar 21 07:27:50 UTC 2025 |
|||
gradle.version=8.2.1 |
|||
#Thu Apr 03 08:01:00 UTC 2025 |
|||
gradle.version=8.11.1 |
|||
|
Binary file not shown.
@ -0,0 +1,288 @@ |
|||
/** |
|||
* @file service.ts |
|||
* @description Service interfaces for Decentralized Identifiers (DIDs) |
|||
* |
|||
* This module defines the service interfaces used in the TimeSafari application. |
|||
* Services are associated with DIDs to provide additional functionality and endpoints. |
|||
* |
|||
* Architecture: |
|||
* 1. Base IService interface defines common service properties |
|||
* 2. Specialized interfaces extend IService for specific service types |
|||
* 3. Services are stored in IIdentifier.services array |
|||
* 4. Services are loaded and managed by PlatformServiceFactory |
|||
* |
|||
* Service Types: |
|||
* - EndorserService: Handles claims and endorsements |
|||
* - PushNotificationService: Manages web push notifications |
|||
* - ProfileService: Handles user profiles and settings |
|||
* - BackupService: Manages data backup and restore |
|||
* |
|||
* @see IIdentifier |
|||
* @see PlatformServiceFactory |
|||
* @see DatabaseBackupService |
|||
*/ |
|||
|
|||
/** |
|||
* Base interface for all DID services |
|||
* |
|||
* This interface defines the core properties that all services must implement. |
|||
* It follows the W3C DID specification for service endpoints. |
|||
* |
|||
* @example |
|||
* const service: IService = { |
|||
* id: 'endorser-service', |
|||
* type: 'EndorserService', |
|||
* serviceEndpoint: 'https://api.endorser.ch', |
|||
* description: 'Endorser service for claims and endorsements', |
|||
* metadata: { |
|||
* version: '1.0.0', |
|||
* capabilities: ['claims', 'endorsements'], |
|||
* config: { apiServer: 'https://api.endorser.ch' } |
|||
* } |
|||
* }; |
|||
*/ |
|||
export interface IService { |
|||
/** |
|||
* Unique identifier for the service |
|||
* @example 'endorser-service' |
|||
* @example 'push-notification-service' |
|||
*/ |
|||
id: string; |
|||
|
|||
/** |
|||
* Type of service |
|||
* @example 'EndorserService' |
|||
* @example 'PushNotificationService' |
|||
*/ |
|||
type: string; |
|||
|
|||
/** |
|||
* Endpoint URL for the service |
|||
* @example 'https://api.endorser.ch' |
|||
* @example 'https://push.timesafari.app' |
|||
*/ |
|||
serviceEndpoint: string; |
|||
|
|||
/** |
|||
* Optional human-readable description of the service |
|||
* @example 'Service for handling claims and endorsements' |
|||
*/ |
|||
description?: string; |
|||
|
|||
/** |
|||
* Optional metadata for service configuration |
|||
*/ |
|||
metadata?: { |
|||
/** |
|||
* Service version in semantic versioning format |
|||
* @example '1.0.0' |
|||
*/ |
|||
version?: string; |
|||
|
|||
/** |
|||
* Array of service capabilities |
|||
* @example ['claims', 'endorsements'] |
|||
* @example ['notifications', 'alerts'] |
|||
*/ |
|||
capabilities?: string[]; |
|||
|
|||
/** |
|||
* Service-specific configuration |
|||
* @example { apiServer: 'https://api.endorser.ch' } |
|||
*/ |
|||
config?: Record<string, unknown>; |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Service for handling claims and endorsements |
|||
* |
|||
* This service provides endpoints for: |
|||
* - Submitting claims |
|||
* - Managing endorsements |
|||
* - Checking rate limits |
|||
* |
|||
* @example |
|||
* const endorserService: IEndorserService = { |
|||
* id: 'endorser-service', |
|||
* type: 'EndorserService', |
|||
* serviceEndpoint: 'https://api.endorser.ch', |
|||
* metadata: { |
|||
* version: '1.0.0', |
|||
* capabilities: ['claims', 'endorsements'], |
|||
* config: { |
|||
* apiServer: 'https://api.endorser.ch', |
|||
* rateLimits: { |
|||
* claimsPerDay: 100, |
|||
* endorsementsPerDay: 1000 |
|||
* } |
|||
* } |
|||
* } |
|||
* }; |
|||
*/ |
|||
export interface IEndorserService extends IService { |
|||
/** @override */ |
|||
type: "EndorserService"; |
|||
|
|||
/** @override */ |
|||
metadata: { |
|||
version: string; |
|||
capabilities: ["claims", "endorsements"]; |
|||
config: { |
|||
/** |
|||
* API server URL |
|||
* @example 'https://api.endorser.ch' |
|||
*/ |
|||
apiServer: string; |
|||
|
|||
/** |
|||
* Optional rate limits |
|||
*/ |
|||
rateLimits?: { |
|||
/** |
|||
* Maximum claims per day |
|||
* @default 100 |
|||
*/ |
|||
claimsPerDay: number; |
|||
|
|||
/** |
|||
* Maximum endorsements per day |
|||
* @default 1000 |
|||
*/ |
|||
endorsementsPerDay: number; |
|||
}; |
|||
}; |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Service for managing web push notifications |
|||
* |
|||
* This service provides endpoints for: |
|||
* - Registering push subscriptions |
|||
* - Sending push notifications |
|||
* - Managing notification preferences |
|||
* |
|||
* @example |
|||
* const pushService: IPushNotificationService = { |
|||
* id: 'push-service', |
|||
* type: 'PushNotificationService', |
|||
* serviceEndpoint: 'https://push.timesafari.app', |
|||
* metadata: { |
|||
* version: '1.0.0', |
|||
* capabilities: ['notifications'], |
|||
* config: { |
|||
* pushServer: 'https://push.timesafari.app', |
|||
* vapidPublicKey: '...' |
|||
* } |
|||
* } |
|||
* }; |
|||
*/ |
|||
export interface IPushNotificationService extends IService { |
|||
/** @override */ |
|||
type: "PushNotificationService"; |
|||
|
|||
/** @override */ |
|||
metadata: { |
|||
version: string; |
|||
capabilities: ["notifications"]; |
|||
config: { |
|||
/** |
|||
* Push server URL |
|||
* @example 'https://push.timesafari.app' |
|||
*/ |
|||
pushServer: string; |
|||
|
|||
/** |
|||
* Optional VAPID public key for push notifications |
|||
*/ |
|||
vapidPublicKey?: string; |
|||
}; |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Service for managing user profiles and settings |
|||
* |
|||
* This service provides endpoints for: |
|||
* - Managing user profiles |
|||
* - Updating user settings |
|||
* - Retrieving user preferences |
|||
* |
|||
* @example |
|||
* const profileService: IProfileService = { |
|||
* id: 'profile-service', |
|||
* type: 'ProfileService', |
|||
* serviceEndpoint: 'https://partner-api.endorser.ch', |
|||
* metadata: { |
|||
* version: '1.0.0', |
|||
* capabilities: ['profile', 'settings'], |
|||
* config: { |
|||
* partnerApiServer: 'https://partner-api.endorser.ch' |
|||
* } |
|||
* } |
|||
* }; |
|||
*/ |
|||
export interface IProfileService extends IService { |
|||
/** @override */ |
|||
type: "ProfileService"; |
|||
|
|||
/** @override */ |
|||
metadata: { |
|||
version: string; |
|||
capabilities: ["profile", "settings"]; |
|||
config: { |
|||
/** |
|||
* Partner API server URL |
|||
* @example 'https://partner-api.endorser.ch' |
|||
*/ |
|||
partnerApiServer: string; |
|||
}; |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Service for managing data backup and restore operations |
|||
* |
|||
* This service provides endpoints for: |
|||
* - Creating backups |
|||
* - Restoring from backups |
|||
* - Managing backup storage |
|||
* |
|||
* @example |
|||
* const backupService: IBackupService = { |
|||
* id: 'backup-service', |
|||
* type: 'BackupService', |
|||
* serviceEndpoint: 'https://backup.timesafari.app', |
|||
* metadata: { |
|||
* version: '1.0.0', |
|||
* capabilities: ['backup', 'restore'], |
|||
* config: { |
|||
* storageType: 'cloud', |
|||
* encryptionKey: '...' |
|||
* } |
|||
* } |
|||
* }; |
|||
*/ |
|||
export interface IBackupService extends IService { |
|||
/** @override */ |
|||
type: "BackupService"; |
|||
|
|||
/** @override */ |
|||
metadata: { |
|||
version: string; |
|||
capabilities: ["backup", "restore"]; |
|||
config: { |
|||
/** |
|||
* Storage type for backups |
|||
* @default 'local' |
|||
*/ |
|||
storageType: "local" | "cloud"; |
|||
|
|||
/** |
|||
* Optional encryption key for backups |
|||
*/ |
|||
encryptionKey?: string; |
|||
}; |
|||
}; |
|||
} |
@ -0,0 +1,69 @@ |
|||
/** |
|||
* @file DatabaseBackupService.ts |
|||
* @description Capacitor-specific implementation of DatabaseBackupService |
|||
* |
|||
* This implementation handles database backup operations specifically for Capacitor |
|||
* platforms (Android/iOS). It uses the Filesystem and Share plugins to save and |
|||
* share the backup file. |
|||
*/ |
|||
|
|||
import { DatabaseBackupService as BaseDatabaseBackupService } from "../../services/DatabaseBackupService"; |
|||
import { Filesystem, Directory } from "@capacitor/filesystem"; |
|||
import { Share } from "@capacitor/share"; |
|||
import { log, error } from "../../utils/logger"; |
|||
|
|||
export class DatabaseBackupService extends BaseDatabaseBackupService { |
|||
/** |
|||
* Handles the backup process for Capacitor platforms |
|||
* |
|||
* @param base64Data - Backup data in base64 format |
|||
* @param arrayBuffer - Backup data as ArrayBuffer |
|||
* @param blob - Backup data as Blob |
|||
*/ |
|||
protected async handleBackup( |
|||
base64Data: string, |
|||
arrayBuffer: ArrayBuffer, |
|||
blob: Blob |
|||
): Promise<void> { |
|||
try { |
|||
log("Starting Capacitor backup process"); |
|||
|
|||
// Create a temporary file
|
|||
const fileName = `timesafari-backup-${new Date().toISOString()}.json`; |
|||
const filePath = `backups/${fileName}`; |
|||
|
|||
log("Writing backup file"); |
|||
const result = await Filesystem.writeFile({ |
|||
path: filePath, |
|||
data: base64Data, |
|||
directory: Directory.Cache, |
|||
recursive: true |
|||
}); |
|||
|
|||
log("Getting file path"); |
|||
const fileInfo = await Filesystem.stat({ |
|||
path: filePath, |
|||
directory: Directory.Cache |
|||
}); |
|||
|
|||
log("Sharing backup file"); |
|||
await Share.share({ |
|||
title: "TimeSafari Backup", |
|||
text: "Your TimeSafari backup file", |
|||
url: fileInfo.uri, |
|||
dialogTitle: "Share TimeSafari Backup" |
|||
}); |
|||
|
|||
log("Backup shared successfully"); |
|||
|
|||
// Clean up the temporary file
|
|||
await Filesystem.deleteFile({ |
|||
path: filePath, |
|||
directory: Directory.Cache |
|||
}); |
|||
} catch (err) { |
|||
error("Error during Capacitor backup:", err); |
|||
throw err; |
|||
} |
|||
} |
|||
} |
@ -1,26 +1,95 @@ |
|||
/** |
|||
* @file DatabaseBackupService.ts |
|||
* @description Base service class for handling database backup operations |
|||
* @author Matthew Raymer |
|||
* @version 1.0.0 |
|||
* |
|||
* This service implements the Template Method pattern to provide a common interface |
|||
* for database backup operations across different platforms. It defines the structure |
|||
* of backup operations while delegating platform-specific implementations to subclasses. |
|||
* |
|||
* Build Process Integration: |
|||
* 1. Platform-Specific Implementation: |
|||
* - Each platform (web, electron, capacitor) has its own implementation |
|||
* - Implementations are loaded dynamically via PlatformServiceFactory |
|||
* - Located in ./platforms/{platform}/DatabaseBackupService.ts |
|||
* |
|||
* 2. Build Configuration: |
|||
* - Vite config files (vite.config.*.mts) set VITE_PLATFORM |
|||
* - PlatformServiceFactory uses this to load correct implementation |
|||
* - Build process creates separate chunks for each platform |
|||
* |
|||
* 3. Data Handling: |
|||
* - Supports multiple data formats (base64, ArrayBuffer, Blob) |
|||
* - Platform implementations handle format conversion |
|||
* - Ensures consistent backup format across platforms |
|||
* |
|||
* Usage: |
|||
* - Create backup: DatabaseBackupService.createAndShareBackup(data) |
|||
* - Platform-specific: new WebDatabaseBackupService().handleBackup() |
|||
* |
|||
* @see PlatformServiceFactory.ts |
|||
* @see vite.config.web.mts |
|||
* @see vite.config.electron.mts |
|||
* @see vite.config.capacitor.mts |
|||
*/ |
|||
|
|||
import { PlatformServiceFactory } from "./PlatformServiceFactory"; |
|||
import { log, error } from "../utils/logger"; |
|||
|
|||
export class DatabaseBackupService { |
|||
protected async handleBackup(): Promise<void> { |
|||
/** |
|||
* Template method that must be implemented by platform-specific services |
|||
* @param base64Data - Backup data in base64 format |
|||
* @param arrayBuffer - Backup data as ArrayBuffer |
|||
* @param blob - Backup data as Blob |
|||
* @throws Error if not implemented by subclass |
|||
*/ |
|||
protected async handleBackup( |
|||
_base64Data: string, |
|||
_arrayBuffer: ArrayBuffer, |
|||
_blob: Blob, |
|||
): Promise<void> { |
|||
throw new Error( |
|||
"handleBackup must be implemented by platform-specific service", |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Factory method to create and share a backup |
|||
* Uses PlatformServiceFactory to get platform-specific implementation |
|||
* |
|||
* @param base64Data - Backup data in base64 format |
|||
* @param arrayBuffer - Backup data as ArrayBuffer |
|||
* @param blob - Backup data as Blob |
|||
* @returns Promise that resolves when backup is complete |
|||
*/ |
|||
public static async createAndShareBackup( |
|||
base64Data: string, |
|||
arrayBuffer: ArrayBuffer, |
|||
blob: Blob, |
|||
blob: Blob |
|||
): Promise<void> { |
|||
try { |
|||
log('Creating platform-specific backup service'); |
|||
const backupService = await this.getPlatformSpecificBackupService(); |
|||
log('Backup service created successfully'); |
|||
|
|||
log('Executing platform-specific backup'); |
|||
await backupService.handleBackup(base64Data, arrayBuffer, blob); |
|||
log('Backup completed successfully'); |
|||
} catch (err) { |
|||
error('Error during backup creation:', err); |
|||
if (err instanceof Error) { |
|||
error('Error details:', { |
|||
name: err.name, |
|||
message: err.message, |
|||
stack: err.stack |
|||
}); |
|||
} |
|||
throw err; |
|||
} |
|||
} |
|||
|
|||
private static async getPlatformSpecificBackupService(): Promise<DatabaseBackupService> { |
|||
const factory = PlatformServiceFactory.getInstance(); |
|||
const service = await factory.createDatabaseBackupService(); |
|||
await service.handleBackup(base64Data, arrayBuffer, blob); |
|||
return await factory.createDatabaseBackupService(); |
|||
} |
|||
} |
|||
|
@ -1,27 +0,0 @@ |
|||
import { defineConfig, loadEnv } from "vite"; |
|||
import baseConfig from "./vite.config.base"; |
|||
|
|||
export default defineConfig(({ mode }) => { |
|||
const env = loadEnv(mode, process.cwd(), ''); |
|||
|
|||
return { |
|||
...baseConfig, |
|||
define: { |
|||
'import.meta.env.VITE_PLATFORM': JSON.stringify('web'), |
|||
}, |
|||
build: { |
|||
...baseConfig.build, |
|||
outDir: 'dist/web', |
|||
rollupOptions: { |
|||
...baseConfig.build.rollupOptions, |
|||
output: { |
|||
...baseConfig.build.rollupOptions.output, |
|||
manualChunks: { |
|||
// Web-specific chunk splitting
|
|||
vendor: ['vue', 'vue-router', 'pinia'], |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
}); |
Loading…
Reference in new issue