diff --git a/src/components/BackupFilesList.vue b/src/components/BackupFilesList.vue
index b2485f4a..bcce9627 100644
--- a/src/components/BackupFilesList.vue
+++ b/src/components/BackupFilesList.vue
@@ -1,18 +1,10 @@
-/**
- * Backup Files List Component
- *
- * Displays a list of backup files saved by the app and provides options to:
- * - View backup files by type (contacts, seed, other)
- * - Open individual files in the device's file viewer
- * - Access the backup directory in the device's file explorer
- *
- * @component
- * @displayName BackupFilesList
- * @example
- * ```vue
- *
- * ```
- */
+/** * Backup Files List Component * * Displays a list of backup files saved by
+the app and provides options to: * - View backup files by type (contacts, seed,
+other) * - Open individual files in the device's file viewer * - Access the
+backup directory in the device's file explorer * * @component * @displayName
+BackupFilesList * @example * ```vue *
+
+* ``` */
@@ -21,47 +13,47 @@
@@ -73,21 +65,40 @@
Loading backup files...
-
+
No backup files found
-
Create backups using the export functions above
-
-
💡 How to create backup files:
+
+ Create backups using the export functions above
+
+
+
+ 💡 How to create backup files:
+
-
• Use the "Export Contacts" button above to create contact backups
+
+ • Use the "Export Contacts" button above to create contact backups
+
• Use the "Export Seed" button to backup your recovery phrase
-
• Backup files are saved to persistent storage that survives app installations
-
- • On Android: Files are saved to Downloads/TimeSafari or app data directory
+
+ • Backup files are saved to persistent storage that survives app
+ installations
+
+
+ • On Android: Files are saved to Downloads/TimeSafari or app data
+ directory
- • On iOS: Files are saved to Documents folder (accessible via Files app)
+ • On iOS: Files are saved to Documents folder (accessible via Files
+ app)
@@ -99,18 +110,20 @@
@@ -118,9 +131,9 @@
{{ crumb }}
@@ -129,14 +142,23 @@
-
+
- [Debug mode: forcibly treating all entries as files]
+ [Debug mode: forcibly treating all entries as files]
📁 Backup files are saved to persistent storage that survives app installations:
+
+ 📁 Backup files are saved to persistent storage that survives app
+ installations:
+
-
iOS: Documents folder (accessible via Files app)
-
Android: Downloads/TimeSafari or external storage (accessible via file managers)
-
Desktop: User's download directory
+
+ iOS: Documents folder (accessible via Files app)
+
+
+ Android: Downloads/TimeSafari or external storage (accessible via
+ file managers)
+
+
+ Desktop: User's download directory
+
@@ -222,7 +264,8 @@ export default class BackupFilesList extends Vue {
/**
* Platform service instance for platform-specific operations
*/
- private platformService: PlatformService = PlatformServiceFactory.getInstance();
+ private platformService: PlatformService =
+ PlatformServiceFactory.getInstance();
/**
* Platform capabilities for the current platform
@@ -234,12 +277,18 @@ export default class BackupFilesList extends Vue {
/**
* List of backup files found on the device
*/
- backupFiles: Array<{name: string, uri: string, size?: number, type: 'contacts' | 'seed' | 'other', path?: string}> = [];
+ backupFiles: Array<{
+ name: string;
+ uri: string;
+ size?: number;
+ type: "contacts" | "seed" | "other";
+ path?: string;
+ }> = [];
/**
* Currently selected file type filter
*/
- selectedType: 'all' | 'contacts' | 'seed' | 'other' = 'all';
+ selectedType: "all" | "contacts" | "seed" | "other" = "all";
/**
* Loading state for file operations
@@ -259,7 +308,13 @@ export default class BackupFilesList extends Vue {
/**
* List of files/folders in the current directory
*/
- directoryEntries: Array<{name: string, uri: string, size?: number, path: string, type: 'file' | 'folder'}> = [];
+ directoryEntries: Array<{
+ name: string;
+ uri: string;
+ size?: number;
+ path: string;
+ type: "file" | "folder";
+ }> = [];
/**
* Temporary debug mode to show all entries as files
@@ -271,28 +326,37 @@ export default class BackupFilesList extends Vue {
* Returns true if permission is granted, false otherwise.
*/
private async ensureStoragePermission(): Promise {
- logger.log('[BackupFilesList] ensureStoragePermission called. platformCapabilities:', this.platformCapabilities);
+ logger.log(
+ "[BackupFilesList] ensureStoragePermission called. platformCapabilities:",
+ this.platformCapabilities,
+ );
if (!this.platformCapabilities.hasFileSystem) return true;
// Only relevant for native platforms (Android/iOS)
const platformService = this.platformService as any;
- if (typeof platformService.checkStoragePermissions === 'function') {
+ if (typeof platformService.checkStoragePermissions === "function") {
try {
await platformService.checkStoragePermissions();
- logger.log('[BackupFilesList] Storage permission granted.');
+ logger.log("[BackupFilesList] Storage permission granted.");
return true;
} catch (error) {
- logger.error('[BackupFilesList] Storage permission denied:', error);
-
+ logger.error("[BackupFilesList] Storage permission denied:", error);
+
// Get specific guidance for the platform
- let guidance = "This app needs permission to access your files to list and restore backups.";
- if (typeof platformService.getStoragePermissionGuidance === 'function') {
+ let guidance =
+ "This app needs permission to access your files to list and restore backups.";
+ if (
+ typeof platformService.getStoragePermissionGuidance === "function"
+ ) {
try {
guidance = await platformService.getStoragePermissionGuidance();
} catch (guidanceError) {
- logger.warn('[BackupFilesList] Could not get permission guidance:', guidanceError);
+ logger.warn(
+ "[BackupFilesList] Could not get permission guidance:",
+ guidanceError,
+ );
}
}
-
+
this.$notify(
{
group: "alert",
@@ -312,21 +376,27 @@ export default class BackupFilesList extends Vue {
* Lifecycle hook to load backup files when component is mounted
*/
async mounted() {
- logger.log('[BackupFilesList] mounted hook called. platformCapabilities:', this.platformCapabilities);
+ logger.log(
+ "[BackupFilesList] mounted hook called. platformCapabilities:",
+ this.platformCapabilities,
+ );
if (this.platformCapabilities.hasFileSystem) {
// Check/request permission before loading
const hasPermission = await this.ensureStoragePermission();
if (hasPermission) {
// Set default root path
if (this.platformCapabilities.isIOS) {
- this.currentPath = ['.'];
+ this.currentPath = ["."];
} else {
- this.currentPath = ['Download', 'TimeSafari'];
+ this.currentPath = ["Download", "TimeSafari"];
}
await this.loadDirectory();
- this.refreshInterval = window.setInterval(() => {
- this.loadDirectory();
- }, 5 * 60 * 1000);
+ this.refreshInterval = window.setInterval(
+ () => {
+ this.loadDirectory();
+ },
+ 5 * 60 * 1000,
+ );
}
}
}
@@ -346,12 +416,17 @@ export default class BackupFilesList extends Vue {
* Note: The 'All' tab count is sometimes too small. Logging for debugging.
*/
get filteredFiles() {
- if (this.selectedType === 'all') {
- logger.log('[BackupFilesList] filteredFiles (All):', this.backupFiles);
+ if (this.selectedType === "all") {
+ logger.log("[BackupFilesList] filteredFiles (All):", this.backupFiles);
return this.backupFiles;
}
- const filtered = this.backupFiles.filter(file => file.type === this.selectedType);
- logger.log(`[BackupFilesList] filteredFiles (${this.selectedType}):`, filtered);
+ const filtered = this.backupFiles.filter(
+ (file) => file.type === this.selectedType,
+ );
+ logger.log(
+ `[BackupFilesList] filteredFiles (${this.selectedType}):`,
+ filtered,
+ );
return filtered;
}
@@ -369,11 +444,18 @@ export default class BackupFilesList extends Vue {
if (!this.platformCapabilities.hasFileSystem) return;
this.isLoading = true;
try {
- const path = this.currentPath.join('/') || (this.platformCapabilities.isIOS ? '.' : 'Download/TimeSafari');
- this.directoryEntries = await (this.platformService as any).listFilesInDirectory(path, this.debugShowAll);
- logger.log('[BackupFilesList] Loaded directory:', { path, entries: this.directoryEntries });
+ const path =
+ this.currentPath.join("/") ||
+ (this.platformCapabilities.isIOS ? "." : "Download/TimeSafari");
+ this.directoryEntries = await (
+ this.platformService as PlatformService
+ ).listFilesInDirectory(path, this.debugShowAll);
+ logger.log("[BackupFilesList] Loaded directory:", {
+ path,
+ entries: this.directoryEntries,
+ });
} catch (error) {
- logger.error('[BackupFilesList] Failed to load directory:', error);
+ logger.error("[BackupFilesList] Failed to load directory:", error);
this.directoryEntries = [];
} finally {
this.isLoading = false;
@@ -417,17 +499,17 @@ export default class BackupFilesList extends Vue {
* Computed property for showing files and folders
*/
get folders() {
- return this.directoryEntries.filter(e => e.type === 'folder');
+ return this.directoryEntries.filter((e) => e.type === "folder");
}
get files() {
- return this.directoryEntries.filter(e => e.type === 'file');
+ return this.directoryEntries.filter((e) => e.type === "file");
}
/**
* Refreshes the list of backup files from the device
*/
async refreshFiles() {
- logger.log('[BackupFilesList] refreshFiles called.');
+ logger.log("[BackupFilesList] refreshFiles called.");
if (!this.platformCapabilities.hasFileSystem) {
return;
}
@@ -441,29 +523,32 @@ export default class BackupFilesList extends Vue {
this.isLoading = true;
try {
this.backupFiles = await this.platformService.listBackupFiles();
- logger.log('[BackupFilesList] Refreshed backup files:', {
+ logger.log("[BackupFilesList] Refreshed backup files:", {
count: this.backupFiles.length,
- files: this.backupFiles.map(f => ({
- name: f.name,
- type: f.type,
+ files: this.backupFiles.map((f) => ({
+ name: f.name,
+ type: f.type,
path: f.path,
- size: f.size
+ size: f.size,
})),
platform: this.platformCapabilities.isIOS ? "iOS" : "Android",
timestamp: new Date().toISOString(),
});
// Debug: Log file type distribution
const typeCounts = {
- contacts: this.backupFiles.filter(f => f.type === 'contacts').length,
- seed: this.backupFiles.filter(f => f.type === 'seed').length,
- other: this.backupFiles.filter(f => f.type === 'other').length,
- total: this.backupFiles.length
+ contacts: this.backupFiles.filter((f) => f.type === "contacts").length,
+ seed: this.backupFiles.filter((f) => f.type === "seed").length,
+ other: this.backupFiles.filter((f) => f.type === "other").length,
+ total: this.backupFiles.length,
};
- logger.log('[BackupFilesList] File type distribution:', typeCounts);
+ logger.log("[BackupFilesList] File type distribution:", typeCounts);
// Log the full backupFiles array for debugging the 'All' tab count
- logger.log('[BackupFilesList] backupFiles array for All tab:', this.backupFiles);
+ logger.log(
+ "[BackupFilesList] backupFiles array for All tab:",
+ this.backupFiles,
+ );
} catch (error) {
- logger.error('[BackupFilesList] Failed to refresh backup files:', error);
+ logger.error("[BackupFilesList] Failed to refresh backup files:", error);
this.$notify(
{
group: "alert",
@@ -484,17 +569,17 @@ export default class BackupFilesList extends Vue {
async createTestBackup() {
try {
this.isLoading = true;
- logger.log('[BackupFilesList] Creating test backup file');
-
+ logger.log("[BackupFilesList] Creating test backup file");
+
const result = await this.platformService.createTestBackupFile();
-
+
if (result.success) {
- logger.log('[BackupFilesList] Test backup file created successfully:', {
+ logger.log("[BackupFilesList] Test backup file created successfully:", {
fileName: result.fileName,
uri: result.uri,
timestamp: new Date().toISOString(),
});
-
+
this.$notify(
{
group: "alert",
@@ -504,14 +589,17 @@ export default class BackupFilesList extends Vue {
},
5000,
);
-
+
// Refresh the file list to show the new test file
await this.refreshFiles();
} else {
throw new Error(result.error || "Failed to create test backup file");
}
} catch (error) {
- logger.error('[BackupFilesList] Failed to create test backup file:', error);
+ logger.error(
+ "[BackupFilesList] Failed to create test backup file:",
+ error,
+ );
this.$notify(
{
group: "alert",
@@ -532,12 +620,15 @@ export default class BackupFilesList extends Vue {
async testDirectoryContexts() {
try {
this.isLoading = true;
- logger.log('[BackupFilesList] Testing directory contexts');
-
+ logger.log("[BackupFilesList] Testing directory contexts");
+
const debugOutput = await this.platformService.testDirectoryContexts();
-
- logger.log('[BackupFilesList] Directory context test results:', debugOutput);
-
+
+ logger.log(
+ "[BackupFilesList] Directory context test results:",
+ debugOutput,
+ );
+
// Show the debug output in a notification or alert
this.$notify(
{
@@ -548,14 +639,16 @@ export default class BackupFilesList extends Vue {
},
5000,
);
-
+
// Also log the full output to console for easy access
- console.log("=== Directory Context Test Results ===");
- console.log(debugOutput);
- console.log("=== End Test Results ===");
-
+ logger.log("=== Directory Context Test Results ===");
+ logger.log(debugOutput);
+ logger.log("=== End Test Results ===");
} catch (error) {
- logger.error('[BackupFilesList] Failed to test directory contexts:', error);
+ logger.error(
+ "[BackupFilesList] Failed to test directory contexts:",
+ error,
+ );
this.$notify(
{
group: "alert",
@@ -575,7 +668,7 @@ export default class BackupFilesList extends Vue {
* This method can be called from parent components
*/
async refreshAfterSave() {
- logger.log('[BackupFilesList] refreshAfterSave called');
+ logger.log("[BackupFilesList] refreshAfterSave called");
await this.refreshFiles();
}
@@ -587,7 +680,7 @@ export default class BackupFilesList extends Vue {
async openFile(fileUri: string, fileName: string) {
try {
const result = await this.platformService.openFile(fileUri, fileName);
-
+
if (result.success) {
logger.log("[BackupFilesList] File opened successfully:", {
fileName,
@@ -616,7 +709,7 @@ export default class BackupFilesList extends Vue {
async openBackupDirectory() {
try {
const result = await this.platformService.openBackupDirectory();
-
+
if (result.success) {
logger.log("[BackupFilesList] Backup directory opened successfully:", {
timestamp: new Date().toISOString(),
@@ -642,14 +735,18 @@ export default class BackupFilesList extends Vue {
* Gets the count of files for a specific type
* Note: The 'All' tab count is sometimes too small. Logging for debugging.
*/
- getFileCountByType(type: 'all' | 'contacts' | 'seed' | 'other'): number {
+ getFileCountByType(type: "all" | "contacts" | "seed" | "other"): number {
let count;
- if (type === 'all') {
+ if (type === "all") {
count = this.backupFiles.length;
- logger.log('[BackupFilesList] getFileCountByType (All):', count, this.backupFiles);
+ logger.log(
+ "[BackupFilesList] getFileCountByType (All):",
+ count,
+ this.backupFiles,
+ );
return count;
}
- count = this.backupFiles.filter(file => file.type === type).length;
+ count = this.backupFiles.filter((file) => file.type === type).length;
logger.log(`[BackupFilesList] getFileCountByType (${type}):`, count);
return count;
}
@@ -659,14 +756,14 @@ export default class BackupFilesList extends Vue {
* @param type - File type
* @returns FontAwesome icon name
*/
- getFileIcon(type: 'contacts' | 'seed' | 'other'): string {
+ getFileIcon(type: "contacts" | "seed" | "other"): string {
switch (type) {
- case 'contacts':
- return 'address-book';
- case 'seed':
- return 'key';
+ case "contacts":
+ return "address-book";
+ case "seed":
+ return "key";
default:
- return 'file-alt';
+ return "file-alt";
}
}
@@ -675,14 +772,14 @@ export default class BackupFilesList extends Vue {
* @param type - File type
* @returns CSS color class
*/
- getFileIconColor(type: 'contacts' | 'seed' | 'other'): string {
+ getFileIconColor(type: "contacts" | "seed" | "other"): string {
switch (type) {
- case 'contacts':
- return 'text-blue-500';
- case 'seed':
- return 'text-orange-500';
+ case "contacts":
+ return "text-blue-500";
+ case "seed":
+ return "text-orange-500";
default:
- return 'text-gray-500';
+ return "text-gray-500";
}
}
@@ -691,14 +788,14 @@ export default class BackupFilesList extends Vue {
* @param type - File type
* @returns CSS color class
*/
- getTypeBadgeColor(type: 'contacts' | 'seed' | 'other'): string {
+ getTypeBadgeColor(type: "contacts" | "seed" | "other"): string {
switch (type) {
- case 'contacts':
- return 'bg-blue-100 text-blue-800';
- case 'seed':
- return 'bg-orange-100 text-orange-800';
+ case "contacts":
+ return "bg-blue-100 text-blue-800";
+ case "seed":
+ return "bg-orange-100 text-orange-800";
default:
- return 'bg-gray-100 text-gray-800';
+ return "bg-gray-100 text-gray-800";
}
}
@@ -708,13 +805,13 @@ export default class BackupFilesList extends Vue {
* @returns Formatted file size string
*/
formatFileSize(bytes: number): string {
- if (bytes === 0) return '0 Bytes';
-
+ if (bytes === 0) return "0 Bytes";
+
const k = 1024;
- const sizes = ['Bytes', 'KB', 'MB', 'GB'];
+ const sizes = ["Bytes", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
-
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
}
/**
@@ -724,44 +821,62 @@ export default class BackupFilesList extends Vue {
public async debugFileDiscovery() {
try {
logger.log("[BackupFilesList] Starting debug file discovery...");
-
+
// Test the platform service's test methods
const platformService = PlatformServiceFactory.getInstance();
-
+
// Test listing all user files
const allFilesResult = await platformService.testListUserFiles();
- logger.log("[BackupFilesList] All user files test result:", allFilesResult);
-
+ logger.log(
+ "[BackupFilesList] All user files test result:",
+ allFilesResult,
+ );
+
// Test listing backup files specifically
const backupFilesResult = await platformService.testBackupFiles();
- logger.log("[BackupFilesList] Backup files test result:", backupFilesResult);
-
- // Test listing all backup files (if available)
- if ('testListAllBackupFiles' in platformService) {
- const allBackupFilesResult = await (platformService as any).testListAllBackupFiles();
- logger.log("[BackupFilesList] All backup files test result:", allBackupFilesResult);
- }
-
+ logger.log(
+ "[BackupFilesList] Backup files test result:",
+ backupFilesResult,
+ );
+
+ // Note: testListAllBackupFiles method is not part of the PlatformService interface
+ // It exists only in CapacitorPlatformService implementation
+ // If needed, this could be added to the interface or called via type assertion
+
// Test debug listing all files without filtering (if available)
- if ('debugListAllFiles' in platformService) {
- const debugAllFiles = await (platformService as any).debugListAllFiles();
+ if ("debugListAllFiles" in platformService) {
+ const debugAllFiles = await (
+ platformService as any
+ ).debugListAllFiles();
logger.log("[BackupFilesList] Debug all files (no filtering):", {
count: debugAllFiles.length,
- files: debugAllFiles.map((f: any) => ({ name: f.name, path: f.path, size: f.size }))
+ files: debugAllFiles.map((f: any) => ({
+ name: f.name,
+ path: f.path,
+ size: f.size,
+ })),
});
}
// Test comprehensive step-by-step debug (if available)
- if ('debugFileDiscoveryStepByStep' in platformService) {
- const stepByStepDebug = await (platformService as any).debugFileDiscoveryStepByStep();
- logger.log("[BackupFilesList] Step-by-step debug output:", stepByStepDebug);
+ if ("debugFileDiscoveryStepByStep" in platformService) {
+ const stepByStepDebug = await (
+ platformService as any
+ ).debugFileDiscoveryStepByStep();
+ logger.log(
+ "[BackupFilesList] Step-by-step debug output:",
+ stepByStepDebug,
+ );
}
-
+
return {
allFiles: allFilesResult,
backupFiles: backupFilesResult,
currentBackupFiles: this.backupFiles,
- debugAllFiles: 'debugListAllFiles' in platformService ? await (platformService as any).debugListAllFiles() : null
+ debugAllFiles:
+ "debugListAllFiles" in platformService
+ ? await (platformService as any).debugListAllFiles()
+ : null,
};
} catch (error) {
logger.error("[BackupFilesList] Debug file discovery failed:", error);
@@ -769,11 +884,11 @@ export default class BackupFilesList extends Vue {
}
}
- @Watch('platformCapabilities.hasFileSystem', { immediate: true })
+ @Watch("platformCapabilities.hasFileSystem", { immediate: true })
async onFileSystemCapabilityChanged(newVal: boolean) {
if (newVal) {
await this.refreshFiles();
}
}
}
-
\ No newline at end of file
+
diff --git a/src/components/DataExportSection.vue b/src/components/DataExportSection.vue
index 949a8481..a52f46e3 100644
--- a/src/components/DataExportSection.vue
+++ b/src/components/DataExportSection.vue
@@ -44,19 +44,25 @@ explorer. * * @component * @displayName DataExportSection * @example * ```vue *
v-if="platformCapabilities.isIOS"
class="list-disc list-outside ml-4"
>
- On iOS: Files are saved to Documents folder (accessible via Files app) and persist between app installations.
+ On iOS: Files are saved to Documents folder (accessible via Files app)
+ and persist between app installations.
- On Android: Files are saved to Downloads/TimeSafari or external storage (accessible via file managers) and persist between app installations.
+ On Android: Files are saved to Downloads/TimeSafari or external
+ storage (accessible via file managers) and persist between app
+ installations.
-
+
@@ -171,15 +177,15 @@ export default class DataExportSection extends Vue {
} else if (this.platformCapabilities.hasFileSystem) {
// Native platform: Write to user-accessible location and share
const result = await this.platformService.writeAndShareFile(
- fileName,
+ fileName,
jsonStr,
{
allowLocationSelection: true,
showLocationSelectionDialog: true,
- mimeType: "application/json"
- }
+ mimeType: "application/json",
+ },
);
-
+
// Handle the result
if (!result.saved) {
throw new Error(result.error || "Failed to save file");
@@ -202,7 +208,10 @@ export default class DataExportSection extends Vue {
// Refresh the backup files list
const backupFilesList = this.$refs.backupFilesList as any;
- if (backupFilesList && typeof backupFilesList.refreshAfterSave === 'function') {
+ if (
+ backupFilesList &&
+ typeof backupFilesList.refreshAfterSave === "function"
+ ) {
await backupFilesList.refreshAfterSave();
}
} catch (error) {
@@ -243,7 +252,10 @@ export default class DataExportSection extends Vue {
// Ensure permissions are requested and refresh backup files list on mount
if (this.platformCapabilities.hasFileSystem) {
const backupFilesList = this.$refs.backupFilesList as any;
- if (backupFilesList && typeof backupFilesList.refreshFiles === 'function') {
+ if (
+ backupFilesList &&
+ typeof backupFilesList.refreshFiles === "function"
+ ) {
await backupFilesList.refreshFiles();
}
}
diff --git a/src/libs/capacitor/app.ts b/src/libs/capacitor/app.ts
index 3ba64b00..7faf28db 100644
--- a/src/libs/capacitor/app.ts
+++ b/src/libs/capacitor/app.ts
@@ -15,7 +15,7 @@ interface AppInterface {
/**
* Force exit the app. This should only be used in conjunction with the `backButton` handler for Android to
* exit the app when navigation is complete.
- *
+ *
* @returns Promise that resolves when the app has been exited
*/
exitApp(): Promise;
@@ -52,7 +52,7 @@ export const App: AppInterface = {
/**
* Force exit the app. This should only be used in conjunction with the `backButton` handler for Android to
* exit the app when navigation is complete.
- *
+ *
* @returns Promise that resolves when the app has been exited
*/
exitApp(): Promise {
diff --git a/src/services/PlatformService.ts b/src/services/PlatformService.ts
index 9d5ac276..a37b3596 100644
--- a/src/services/PlatformService.ts
+++ b/src/services/PlatformService.ts
@@ -69,7 +69,7 @@ export interface PlatformService {
* @returns Promise that resolves to save/share result
*/
writeAndShareFile(
- fileName: string,
+ fileName: string,
content: string,
options?: {
allowLocationSelection?: boolean;
@@ -78,7 +78,7 @@ export interface PlatformService {
mimeType?: string;
showShareDialog?: boolean;
showLocationSelectionDialog?: boolean;
- }
+ },
): Promise<{ saved: boolean; uri?: string; shared: boolean; error?: string }>;
/**
@@ -190,14 +190,24 @@ export interface PlatformService {
* Returns files from Downloads (Android) or Documents (iOS) directories.
* @returns Promise resolving to array of file information
*/
- listUserAccessibleFiles(): Promise>;
+ listUserAccessibleFiles(): Promise<
+ Array<{ name: string; uri: string; size?: number }>
+ >;
/**
* Lists backup files specifically saved by the app.
* Filters for files that appear to be TimeSafari backups.
* @returns Promise resolving to array of backup file information
*/
- listBackupFiles(): Promise>;
+ listBackupFiles(): Promise<
+ Array<{
+ name: string;
+ uri: string;
+ size?: number;
+ type: "contacts" | "seed" | "other";
+ path?: string;
+ }>
+ >;
/**
* Opens a file in the device's default file viewer/app.
@@ -206,7 +216,10 @@ export interface PlatformService {
* @param fileName - Name of the file (for display purposes)
* @returns Promise resolving to success status
*/
- openFile(fileUri: string, fileName: string): Promise<{ success: boolean; error?: string }>;
+ openFile(
+ fileUri: string,
+ fileName: string,
+ ): Promise<{ success: boolean; error?: string }>;
/**
* Opens the directory containing backup files in the device's file explorer.
@@ -220,7 +233,12 @@ export interface PlatformService {
* This is useful for debugging file visibility issues.
* @returns Promise resolving to success status and file information
*/
- createTestBackupFile(): Promise<{ success: boolean; fileName?: string; uri?: string; error?: string }>;
+ createTestBackupFile(): Promise<{
+ success: boolean;
+ fileName?: string;
+ uri?: string;
+ error?: string;
+ }>;
/**
* Tests different directory contexts to see what files are available.
@@ -235,7 +253,18 @@ export interface PlatformService {
* @param debugShowAll - Debug flag to treat all entries as files
* @returns Promise resolving to array of directory entries
*/
- listFilesInDirectory(path: string, debugShowAll?: boolean): Promise>;
+ listFilesInDirectory(
+ path: string,
+ debugShowAll?: boolean,
+ ): Promise<
+ Array<{
+ name: string;
+ uri: string;
+ size?: number;
+ path: string;
+ type: "file" | "folder";
+ }>
+ >;
/**
* Debug method to check what's actually in the TimeSafari directory
diff --git a/src/services/platforms/CapacitorPlatformService.ts b/src/services/platforms/CapacitorPlatformService.ts
index d5c9d191..a8901773 100644
--- a/src/services/platforms/CapacitorPlatformService.ts
+++ b/src/services/platforms/CapacitorPlatformService.ts
@@ -51,7 +51,7 @@ export class CapacitorPlatformService implements PlatformService {
private initializationPromise: Promise | null = null;
private operationQueue: Array = [];
private isProcessingQueue: boolean = false;
-
+
/** Permission request lock to prevent concurrent requests */
private permissionRequestLock: Promise | null = null;
private permissionGranted: boolean = false;
@@ -276,9 +276,12 @@ export class CapacitorPlatformService implements PlatformService {
// Check if we already have recent permission status (cache for 30 seconds)
const now = Date.now();
const cacheExpiry = 30000; // 30 seconds
-
- if (this.permissionChecked && this.permissionGranted &&
- (now - this.lastPermissionCheck) < cacheExpiry) {
+
+ if (
+ this.permissionChecked &&
+ this.permissionGranted &&
+ now - this.lastPermissionCheck < cacheExpiry
+ ) {
return; // Use cached permission status
}
@@ -290,7 +293,7 @@ export class CapacitorPlatformService implements PlatformService {
// Create a new permission request lock
this.permissionRequestLock = this._requestStoragePermissions();
-
+
try {
await this.permissionRequestLock;
this.permissionGranted = true;
@@ -300,17 +303,22 @@ export class CapacitorPlatformService implements PlatformService {
this.permissionGranted = false;
this.permissionChecked = true;
this.lastPermissionCheck = now;
- const errorMessage = error instanceof Error ? error.message : String(error);
- logger.warn("Storage permissions denied, continuing with limited functionality", {
- error: errorMessage,
- timestamp: new Date().toISOString(),
- });
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ logger.warn(
+ "Storage permissions denied, continuing with limited functionality",
+ {
+ error: errorMessage,
+ timestamp: new Date().toISOString(),
+ },
+ );
// Don't throw error - allow app to continue with limited functionality
} finally {
this.permissionRequestLock = null;
}
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
logger.error("Error checking storage permissions:", {
error: errorMessage,
timestamp: new Date().toISOString(),
@@ -330,18 +338,21 @@ export class CapacitorPlatformService implements PlatformService {
path: ".",
directory: Directory.ExternalStorage,
});
-
+
// Permissions are already granted
return;
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
-
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+
// Check if this is a permission denial
- if (errorMessage.includes("user denied permission") ||
- errorMessage.includes("permission request")) {
+ if (
+ errorMessage.includes("user denied permission") ||
+ errorMessage.includes("permission request")
+ ) {
throw new Error("Storage permission denied by user");
}
-
+
// For other errors, log but don't treat as permission denial
logger.warn("Storage access error (not permission-related):", {
error: errorMessage,
@@ -370,20 +381,20 @@ export class CapacitorPlatformService implements PlatformService {
/**
* Enhanced file save and share functionality with location selection.
- *
+ *
* Provides multiple options for saving files:
* 1. Save to user-accessible storage (Downloads/Documents) and share (default behavior)
* 2. Save to app-private storage and share (for sensitive data)
* 3. Allow user to choose save location via file picker
* 4. Direct share without saving locally
- *
+ *
* @param fileName - The name of the file to create (e.g. "backup.json")
* @param content - The content to write to the file
* @param options - Additional options for file saving behavior
* @returns Promise resolving to save/share result
*/
async writeAndShareFile(
- fileName: string,
+ fileName: string,
content: string,
options: {
allowLocationSelection?: boolean;
@@ -392,8 +403,13 @@ export class CapacitorPlatformService implements PlatformService {
mimeType?: string;
showShareDialog?: boolean;
showLocationSelectionDialog?: boolean;
- } = {}
- ): Promise<{ saved: boolean; uri?: string; shared: boolean; error?: string }> {
+ } = {},
+ ): Promise<{
+ saved: boolean;
+ uri?: string;
+ shared: boolean;
+ error?: string;
+ }> {
const timestamp = new Date().toISOString();
const logData = {
action: "writeAndShareFile",
@@ -414,7 +430,11 @@ export class CapacitorPlatformService implements PlatformService {
// Determine save strategy based on options and platform
if (options.allowLocationSelection) {
// Use true user choice for file location
- fileUri = await this.saveWithUserChoice(fileName, content, options.mimeType);
+ fileUri = await this.saveWithUserChoice(
+ fileName,
+ content,
+ options.mimeType,
+ );
saved = true;
} else if (options.saveToPrivateStorage) {
// Save to app-private storage (for sensitive data)
@@ -441,7 +461,10 @@ export class CapacitorPlatformService implements PlatformService {
// Share the file (unless explicitly disabled)
let shared = false;
- if (options.showShareDialog !== false && !options.allowLocationSelection) {
+ if (
+ options.showShareDialog !== false &&
+ !options.allowLocationSelection
+ ) {
try {
logger.log("[CapacitorPlatformService] Starting share operation:", {
uri: fileUri,
@@ -455,37 +478,51 @@ export class CapacitorPlatformService implements PlatformService {
url: fileUri,
dialogTitle: "Share your backup file",
});
-
+
shared = true;
- logger.log("[CapacitorPlatformService] Share dialog completed successfully:", {
- activityType: shareResult?.activityType,
- timestamp: new Date().toISOString(),
- });
-
+ logger.log(
+ "[CapacitorPlatformService] Share dialog completed successfully:",
+ {
+ activityType: shareResult?.activityType,
+ timestamp: new Date().toISOString(),
+ },
+ );
} catch (shareError) {
const shareErr = shareError as Error;
- logger.warn("[CapacitorPlatformService] Share operation completed (may have been cancelled or timed out):", {
- error: shareErr.message,
- timestamp: new Date().toISOString(),
- });
-
+ logger.warn(
+ "[CapacitorPlatformService] Share operation completed (may have been cancelled or timed out):",
+ {
+ error: shareErr.message,
+ timestamp: new Date().toISOString(),
+ },
+ );
+
// Check if it's a user cancellation, timeout, or other expected completion
- if (shareErr.message.includes("cancel") ||
- shareErr.message.includes("dismiss") ||
- shareErr.message.includes("timeout") ||
- shareErr.message.includes("Share dialog timeout")) {
- logger.log("[CapacitorPlatformService] Share dialog completed (cancelled/timeout) - this is expected behavior");
+ if (
+ shareErr.message.includes("cancel") ||
+ shareErr.message.includes("dismiss") ||
+ shareErr.message.includes("timeout") ||
+ shareErr.message.includes("Share dialog timeout")
+ ) {
+ logger.log(
+ "[CapacitorPlatformService] Share dialog completed (cancelled/timeout) - this is expected behavior",
+ );
// Don't treat cancellation/timeout as an error, file is still saved
// The dialog should close automatically on timeout
} else {
// Log other share errors but don't fail the operation
- logger.error("[CapacitorPlatformService] Unexpected share error:", shareErr);
+ logger.error(
+ "[CapacitorPlatformService] Unexpected share error:",
+ shareErr,
+ );
}
}
} else if (options.allowLocationSelection) {
// Location selection already handled the sharing, mark as shared
shared = true;
- logger.log("[CapacitorPlatformService] Location selection handled sharing");
+ logger.log(
+ "[CapacitorPlatformService] Location selection handled sharing",
+ );
}
return { saved, uri: fileUri, shared };
@@ -510,43 +547,59 @@ export class CapacitorPlatformService implements PlatformService {
* @param timeoutMs - Timeout in milliseconds (default: 15 seconds)
* @returns Promise that resolves when share operation completes
*/
- private async handleShareDialog(shareOptions: any, timeoutMs: number = 15000): Promise {
+ private async handleShareDialog(
+ shareOptions: any,
+ timeoutMs: number = 15000,
+ ): Promise {
try {
- logger.log("[CapacitorPlatformService] Starting share dialog with timeout:", {
- timeoutMs,
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] Starting share dialog with timeout:",
+ {
+ timeoutMs,
+ timestamp: new Date().toISOString(),
+ },
+ );
const sharePromise = Share.share(shareOptions);
-
+
// Add timeout to prevent hanging
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
- logger.warn("[CapacitorPlatformService] Share dialog timeout reached - forcing completion");
+ logger.warn(
+ "[CapacitorPlatformService] Share dialog timeout reached - forcing completion",
+ );
reject(new Error("Share dialog timeout - forcing completion"));
}, timeoutMs);
});
// Race between share completion and timeout
const result = await Promise.race([sharePromise, timeoutPromise]);
-
- logger.log("[CapacitorPlatformService] Share dialog completed successfully:", {
- activityType: result?.activityType,
- timestamp: new Date().toISOString(),
- });
-
+
+ logger.log(
+ "[CapacitorPlatformService] Share dialog completed successfully:",
+ {
+ activityType: result?.activityType,
+ timestamp: new Date().toISOString(),
+ },
+ );
+
return result;
} catch (error) {
const err = error as Error;
-
+
// Check if it's an expected completion (timeout, cancellation, etc.)
- if (err.message.includes("timeout") ||
- err.message.includes("cancel") ||
- err.message.includes("dismiss")) {
- logger.log("[CapacitorPlatformService] Share dialog completed (expected):", {
- reason: err.message,
- timestamp: new Date().toISOString(),
- });
+ if (
+ err.message.includes("timeout") ||
+ err.message.includes("cancel") ||
+ err.message.includes("dismiss")
+ ) {
+ logger.log(
+ "[CapacitorPlatformService] Share dialog completed (expected):",
+ {
+ reason: err.message,
+ timestamp: new Date().toISOString(),
+ },
+ );
// Return a success result even for timeout/cancellation
return { activityType: "timeout_or_cancellation" };
} else {
@@ -564,17 +617,20 @@ export class CapacitorPlatformService implements PlatformService {
* @returns Promise resolving to the saved file URI
*/
private async saveFileWithPicker(
- fileName: string,
- content: string,
- mimeType: string = "application/json"
+ fileName: string,
+ content: string,
+ mimeType: string = "application/json",
): Promise {
try {
- logger.log("[CapacitorPlatformService] Using native share dialog for save location selection:", {
- fileName,
- mimeType,
- platform: this.getCapabilities().isIOS ? "iOS" : "Android",
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] Using native share dialog for save location selection:",
+ {
+ fileName,
+ mimeType,
+ platform: this.getCapabilities().isIOS ? "iOS" : "Android",
+ timestamp: new Date().toISOString(),
+ },
+ );
// First, save the file to a temporary location
const tempFileName = `temp-${getTimestampForFilename()}-${fileName}`;
@@ -599,13 +655,18 @@ export class CapacitorPlatformService implements PlatformService {
dialogTitle: "Choose where to save your file",
});
- logger.log("[CapacitorPlatformService] Native share dialog completed for location selection");
+ logger.log(
+ "[CapacitorPlatformService] Native share dialog completed for location selection",
+ );
// Return the temp URI - the user can save it wherever they want via the share dialog
// The share dialog will provide options like "Save to Files", "Copy to Downloads", etc.
return tempResult.uri;
} catch (error) {
- logger.error("[CapacitorPlatformService] Native share dialog for location selection failed:", error);
+ logger.error(
+ "[CapacitorPlatformService] Native share dialog for location selection failed:",
+ error,
+ );
throw new Error(`Failed to open location selection dialog: ${error}`);
}
}
@@ -619,19 +680,22 @@ export class CapacitorPlatformService implements PlatformService {
* @returns Promise resolving to the saved file URI
*/
private async saveWithLocationOptions(
- fileName: string,
- content: string,
+ fileName: string,
+ content: string,
mimeType: string = "application/json",
- showDialog: boolean = true
+ showDialog: boolean = true,
): Promise {
try {
- logger.log("[CapacitorPlatformService] Providing save location options:", {
- fileName,
- mimeType,
- showDialog,
- platform: this.getCapabilities().isIOS ? "iOS" : "Android",
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] Providing save location options:",
+ {
+ fileName,
+ mimeType,
+ showDialog,
+ platform: this.getCapabilities().isIOS ? "iOS" : "Android",
+ timestamp: new Date().toISOString(),
+ },
+ );
// Save to multiple locations and let user choose via share dialog
const locations = [];
@@ -650,11 +714,14 @@ export class CapacitorPlatformService implements PlatformService {
});
locations.push(backupResult.uri);
- logger.log("[CapacitorPlatformService] File saved to multiple locations:", {
- primaryLocation,
- backupLocation: backupResult.uri,
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] File saved to multiple locations:",
+ {
+ primaryLocation,
+ backupLocation: backupResult.uri,
+ timestamp: new Date().toISOString(),
+ },
+ );
// Use share dialog to let user choose which location to use (if enabled)
if (showDialog) {
@@ -665,32 +732,49 @@ export class CapacitorPlatformService implements PlatformService {
url: primaryLocation,
dialogTitle: "Access your saved file",
});
-
- logger.log("[CapacitorPlatformService] Share dialog completed for location selection");
+
+ logger.log(
+ "[CapacitorPlatformService] Share dialog completed for location selection",
+ );
} catch (shareError) {
const shareErr = shareError as Error;
- logger.warn("[CapacitorPlatformService] Share dialog failed, but file was saved:", {
- error: shareErr.message,
- timestamp: new Date().toISOString(),
- });
-
+ logger.warn(
+ "[CapacitorPlatformService] Share dialog failed, but file was saved:",
+ {
+ error: shareErr.message,
+ timestamp: new Date().toISOString(),
+ },
+ );
+
// Check if it's a user cancellation or timeout
- if (shareErr.message.includes("cancel") ||
- shareErr.message.includes("dismiss") ||
- shareErr.message.includes("timeout")) {
- logger.log("[CapacitorPlatformService] User cancelled location selection share dialog or timeout occurred");
+ if (
+ shareErr.message.includes("cancel") ||
+ shareErr.message.includes("dismiss") ||
+ shareErr.message.includes("timeout")
+ ) {
+ logger.log(
+ "[CapacitorPlatformService] User cancelled location selection share dialog or timeout occurred",
+ );
} else {
- logger.error("[CapacitorPlatformService] Location selection share error:", shareErr);
+ logger.error(
+ "[CapacitorPlatformService] Location selection share error:",
+ shareErr,
+ );
}
}
} else {
- logger.log("[CapacitorPlatformService] Location selection dialog disabled, file saved silently");
+ logger.log(
+ "[CapacitorPlatformService] Location selection dialog disabled, file saved silently",
+ );
}
// Return the primary location (user can access others via share dialog)
return primaryLocation;
} catch (error) {
- logger.error("[CapacitorPlatformService] Save with location options failed:", error);
+ logger.error(
+ "[CapacitorPlatformService] Save with location options failed:",
+ error,
+ );
throw new Error(`Failed to save with location options: ${error}`);
}
}
@@ -702,7 +786,10 @@ export class CapacitorPlatformService implements PlatformService {
* @param directory - The base directory type
* @returns Promise that resolves when the directory is ready
*/
- private async ensureDirectoryExists(path: string, directory: Directory): Promise {
+ private async ensureDirectoryExists(
+ path: string,
+ directory: Directory,
+ ): Promise {
try {
// Try to read the directory to see if it exists
await Filesystem.readdir({
@@ -716,13 +803,17 @@ export class CapacitorPlatformService implements PlatformService {
});
} catch (error) {
// Directory doesn't exist, try to create it
- const errorMessage = error instanceof Error ? error.message : String(error);
- logger.log("[CapacitorPlatformService] Directory doesn't exist, attempting to create:", {
- path,
- directory,
- error: errorMessage,
- timestamp: new Date().toISOString(),
- });
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ logger.log(
+ "[CapacitorPlatformService] Directory doesn't exist, attempting to create:",
+ {
+ path,
+ directory,
+ error: errorMessage,
+ timestamp: new Date().toISOString(),
+ },
+ );
if (this.getCapabilities().isIOS) {
// iOS: Use the standard approach
@@ -737,11 +828,14 @@ export class CapacitorPlatformService implements PlatformService {
/**
* Creates a directory on iOS using the standard approach
*/
- private async createDirectoryIOS(path: string, directory: Directory): Promise {
+ private async createDirectoryIOS(
+ path: string,
+ directory: Directory,
+ ): Promise {
try {
const tempFileName = `.temp-${Date.now()}`;
const tempPath = `${path}/${tempFileName}`;
-
+
await Filesystem.writeFile({
path: tempPath,
data: "",
@@ -749,26 +843,33 @@ export class CapacitorPlatformService implements PlatformService {
encoding: Encoding.UTF8,
recursive: true,
});
-
+
await Filesystem.deleteFile({
path: tempPath,
directory,
});
-
- logger.log("[CapacitorPlatformService] Directory created successfully (iOS):", {
- path,
- directory,
- method: "temporary_file",
- timestamp: new Date().toISOString(),
- });
+
+ logger.log(
+ "[CapacitorPlatformService] Directory created successfully (iOS):",
+ {
+ path,
+ directory,
+ method: "temporary_file",
+ timestamp: new Date().toISOString(),
+ },
+ );
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- logger.warn("[CapacitorPlatformService] Failed to create directory on iOS:", {
- path,
- directory,
- error: errorMessage,
- timestamp: new Date().toISOString(),
- });
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ logger.warn(
+ "[CapacitorPlatformService] Failed to create directory on iOS:",
+ {
+ path,
+ directory,
+ error: errorMessage,
+ timestamp: new Date().toISOString(),
+ },
+ );
throw error;
}
}
@@ -776,23 +877,29 @@ export class CapacitorPlatformService implements PlatformService {
/**
* Creates a directory on Android using multiple strategies for Android 10+ compatibility
*/
- private async createDirectoryAndroid(path: string, directory: Directory): Promise {
+ private async createDirectoryAndroid(
+ path: string,
+ directory: Directory,
+ ): Promise {
const androidVersion = await this.getAndroidVersion();
const hasRestrictions = await this.hasStorageRestrictions();
-
- logger.log("[CapacitorPlatformService] Android directory creation analysis:", {
- path,
- directory,
- androidVersion,
- hasRestrictions,
- timestamp: new Date().toISOString(),
- });
+
+ logger.log(
+ "[CapacitorPlatformService] Android directory creation analysis:",
+ {
+ path,
+ directory,
+ androidVersion,
+ hasRestrictions,
+ timestamp: new Date().toISOString(),
+ },
+ );
// Strategy 1: Try recursive file creation (works on some Android 10+ devices)
try {
const tempFileName = `.temp-${Date.now()}`;
const tempPath = `${path}/${tempFileName}`;
-
+
await Filesystem.writeFile({
path: tempPath,
data: "",
@@ -800,7 +907,7 @@ export class CapacitorPlatformService implements PlatformService {
encoding: Encoding.UTF8,
recursive: true,
});
-
+
// Clean up the temporary file
try {
await Filesystem.deleteFile({
@@ -809,59 +916,75 @@ export class CapacitorPlatformService implements PlatformService {
});
} catch (deleteError) {
// Ignore delete errors - the directory was created successfully
- const deleteErrorMessage = deleteError instanceof Error ? deleteError.message : String(deleteError);
- logger.log("[CapacitorPlatformService] Temporary file cleanup failed (non-critical):", {
- tempPath,
- error: deleteErrorMessage,
- timestamp: new Date().toISOString(),
- });
+ const deleteErrorMessage =
+ deleteError instanceof Error
+ ? deleteError.message
+ : String(deleteError);
+ logger.log(
+ "[CapacitorPlatformService] Temporary file cleanup failed (non-critical):",
+ {
+ tempPath,
+ error: deleteErrorMessage,
+ timestamp: new Date().toISOString(),
+ },
+ );
}
-
- logger.log("[CapacitorPlatformService] Directory created successfully (Android Strategy 1):", {
- path,
- directory,
- method: "recursive_file_creation",
- androidVersion,
- timestamp: new Date().toISOString(),
- });
+
+ logger.log(
+ "[CapacitorPlatformService] Directory created successfully (Android Strategy 1):",
+ {
+ path,
+ directory,
+ method: "recursive_file_creation",
+ androidVersion,
+ timestamp: new Date().toISOString(),
+ },
+ );
return;
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- logger.log("[CapacitorPlatformService] Strategy 1 failed, trying Strategy 2:", {
- path,
- directory,
- error: errorMessage,
- androidVersion,
- timestamp: new Date().toISOString(),
- });
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ logger.log(
+ "[CapacitorPlatformService] Strategy 1 failed, trying Strategy 2:",
+ {
+ path,
+ directory,
+ error: errorMessage,
+ androidVersion,
+ timestamp: new Date().toISOString(),
+ },
+ );
}
// Strategy 2: Try creating parent directories first (for nested paths)
- if (path.includes('/')) {
+ if (path.includes("/")) {
try {
- const pathParts = path.split('/');
- let currentPath = '';
-
+ const pathParts = path.split("/");
+ let currentPath = "";
+
for (const part of pathParts) {
if (part) {
currentPath = currentPath ? `${currentPath}/${part}` : part;
-
+
try {
// Check if this level exists
await Filesystem.readdir({
path: currentPath,
directory,
});
- logger.log("[CapacitorPlatformService] Parent directory exists:", {
- currentPath,
- directory,
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] Parent directory exists:",
+ {
+ currentPath,
+ directory,
+ timestamp: new Date().toISOString(),
+ },
+ );
} catch (readError) {
// This level doesn't exist, try to create it
const tempFileName = `.temp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const tempPath = `${currentPath}/${tempFileName}`;
-
+
await Filesystem.writeFile({
path: tempPath,
data: "",
@@ -869,7 +992,7 @@ export class CapacitorPlatformService implements PlatformService {
encoding: Encoding.UTF8,
recursive: true,
});
-
+
// Clean up
try {
await Filesystem.deleteFile({
@@ -879,33 +1002,43 @@ export class CapacitorPlatformService implements PlatformService {
} catch (deleteError) {
// Ignore cleanup errors
}
-
- logger.log("[CapacitorPlatformService] Created parent directory level:", {
- currentPath,
- directory,
- timestamp: new Date().toISOString(),
- });
+
+ logger.log(
+ "[CapacitorPlatformService] Created parent directory level:",
+ {
+ currentPath,
+ directory,
+ timestamp: new Date().toISOString(),
+ },
+ );
}
}
}
-
- logger.log("[CapacitorPlatformService] Directory created successfully (Android Strategy 2):", {
- path,
- directory,
- method: "parent_by_parent",
- androidVersion,
- timestamp: new Date().toISOString(),
- });
+
+ logger.log(
+ "[CapacitorPlatformService] Directory created successfully (Android Strategy 2):",
+ {
+ path,
+ directory,
+ method: "parent_by_parent",
+ androidVersion,
+ timestamp: new Date().toISOString(),
+ },
+ );
return;
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- logger.log("[CapacitorPlatformService] Strategy 2 failed, trying Strategy 3:", {
- path,
- directory,
- error: errorMessage,
- androidVersion,
- timestamp: new Date().toISOString(),
- });
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ logger.log(
+ "[CapacitorPlatformService] Strategy 2 failed, trying Strategy 3:",
+ {
+ path,
+ directory,
+ error: errorMessage,
+ androidVersion,
+ timestamp: new Date().toISOString(),
+ },
+ );
}
}
@@ -913,7 +1046,7 @@ export class CapacitorPlatformService implements PlatformService {
try {
const tempFileName = `timesafari-dir-test-${Date.now()}.tmp`;
const tempPath = `${path}/${tempFileName}`;
-
+
await Filesystem.writeFile({
path: tempPath,
data: "directory test",
@@ -921,7 +1054,7 @@ export class CapacitorPlatformService implements PlatformService {
encoding: Encoding.UTF8,
recursive: true,
});
-
+
// Clean up
try {
await Filesystem.deleteFile({
@@ -931,24 +1064,31 @@ export class CapacitorPlatformService implements PlatformService {
} catch (deleteError) {
// Ignore cleanup errors
}
-
- logger.log("[CapacitorPlatformService] Directory created successfully (Android Strategy 3):", {
- path,
- directory,
- method: "simple_file_test",
- androidVersion,
- timestamp: new Date().toISOString(),
- });
+
+ logger.log(
+ "[CapacitorPlatformService] Directory created successfully (Android Strategy 3):",
+ {
+ path,
+ directory,
+ method: "simple_file_test",
+ androidVersion,
+ timestamp: new Date().toISOString(),
+ },
+ );
return;
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- logger.log("[CapacitorPlatformService] Strategy 3 failed, trying Strategy 4:", {
- path,
- directory,
- error: errorMessage,
- androidVersion,
- timestamp: new Date().toISOString(),
- });
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ logger.log(
+ "[CapacitorPlatformService] Strategy 3 failed, trying Strategy 4:",
+ {
+ path,
+ directory,
+ error: errorMessage,
+ androidVersion,
+ timestamp: new Date().toISOString(),
+ },
+ );
}
// Strategy 4: For Android 10+ with severe restrictions, try app-specific directories
@@ -956,7 +1096,7 @@ export class CapacitorPlatformService implements PlatformService {
try {
// Try creating in app's external files directory
const appSpecificPath = `Android/data/app.timesafari.app/files/${path}`;
-
+
await Filesystem.writeFile({
path: `${appSpecificPath}/.test`,
data: "",
@@ -964,17 +1104,21 @@ export class CapacitorPlatformService implements PlatformService {
encoding: Encoding.UTF8,
recursive: true,
});
-
- logger.log("[CapacitorPlatformService] Directory created successfully (Android Strategy 4):", {
- path: appSpecificPath,
- directory,
- method: "app_specific_external",
- androidVersion,
- timestamp: new Date().toISOString(),
- });
+
+ logger.log(
+ "[CapacitorPlatformService] Directory created successfully (Android Strategy 4):",
+ {
+ path: appSpecificPath,
+ directory,
+ method: "app_specific_external",
+ androidVersion,
+ timestamp: new Date().toISOString(),
+ },
+ );
return;
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
logger.log("[CapacitorPlatformService] Strategy 4 failed:", {
path,
directory,
@@ -986,16 +1130,21 @@ export class CapacitorPlatformService implements PlatformService {
}
// All strategies failed
- const finalError = new Error(`Failed to create directory '${path}' on Android ${androidVersion} with restrictions: ${hasRestrictions}`);
- logger.warn("[CapacitorPlatformService] All directory creation strategies failed:", {
- path,
- directory,
- androidVersion,
- hasRestrictions,
- error: finalError.message,
- timestamp: new Date().toISOString(),
- });
-
+ const finalError = new Error(
+ `Failed to create directory '${path}' on Android ${androidVersion} with restrictions: ${hasRestrictions}`,
+ );
+ logger.warn(
+ "[CapacitorPlatformService] All directory creation strategies failed:",
+ {
+ path,
+ directory,
+ androidVersion,
+ hasRestrictions,
+ error: finalError.message,
+ timestamp: new Date().toISOString(),
+ },
+ );
+
throw finalError;
}
@@ -1007,7 +1156,10 @@ export class CapacitorPlatformService implements PlatformService {
* @param content - File content
* @returns Promise resolving to the saved file URI
*/
- private async saveToDownloads(fileName: string, content: string): Promise {
+ private async saveToDownloads(
+ fileName: string,
+ content: string,
+ ): Promise {
try {
if (this.getCapabilities().isIOS) {
// iOS: Save to Documents directory (persists between installations, accessible via Files app)
@@ -1017,19 +1169,19 @@ export class CapacitorPlatformService implements PlatformService {
directory: Directory.Documents,
encoding: Encoding.UTF8,
});
-
+
logger.log("[CapacitorPlatformService] File saved to iOS Documents:", {
fileName,
uri: result.uri,
timestamp: new Date().toISOString(),
});
-
+
return result.uri;
} else {
// Android: Check for storage restrictions
const hasRestrictions = await this.hasStorageRestrictions();
const androidVersion = await this.getAndroidVersion();
-
+
logger.log("[CapacitorPlatformService] Android storage analysis:", {
hasRestrictions,
androidVersion,
@@ -1041,62 +1193,82 @@ export class CapacitorPlatformService implements PlatformService {
// For Android 10+ with storage restrictions, try app-specific external storage first
try {
// Try to save to app's external storage directory
- await this.ensureDirectoryExists("TimeSafari", Directory.ExternalStorage);
-
+ await this.ensureDirectoryExists(
+ "TimeSafari",
+ Directory.ExternalStorage,
+ );
+
const result = await Filesystem.writeFile({
path: `TimeSafari/${fileName}`,
data: content,
directory: Directory.ExternalStorage,
encoding: Encoding.UTF8,
});
-
- logger.log("[CapacitorPlatformService] File saved to app external storage (Android 10+):", {
- fileName,
- uri: result.uri,
- androidVersion,
- timestamp: new Date().toISOString(),
- });
-
+
+ logger.log(
+ "[CapacitorPlatformService] File saved to app external storage (Android 10+):",
+ {
+ fileName,
+ uri: result.uri,
+ androidVersion,
+ timestamp: new Date().toISOString(),
+ },
+ );
+
return result.uri;
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- logger.warn("[CapacitorPlatformService] Failed to save to app external storage, trying Downloads:", {
- fileName,
- error: errorMessage,
- androidVersion,
- timestamp: new Date().toISOString(),
- });
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ logger.warn(
+ "[CapacitorPlatformService] Failed to save to app external storage, trying Downloads:",
+ {
+ fileName,
+ error: errorMessage,
+ androidVersion,
+ timestamp: new Date().toISOString(),
+ },
+ );
}
}
// Try Downloads directory (works on older Android versions)
try {
- await this.ensureDirectoryExists("Download/TimeSafari", Directory.ExternalStorage);
-
+ await this.ensureDirectoryExists(
+ "Download/TimeSafari",
+ Directory.ExternalStorage,
+ );
+
const result = await Filesystem.writeFile({
path: `Download/TimeSafari/${fileName}`,
data: content,
directory: Directory.ExternalStorage,
encoding: Encoding.UTF8,
});
-
- logger.log("[CapacitorPlatformService] File saved to Downloads (Android):", {
- fileName,
- uri: result.uri,
- androidVersion,
- timestamp: new Date().toISOString(),
- });
-
+
+ logger.log(
+ "[CapacitorPlatformService] File saved to Downloads (Android):",
+ {
+ fileName,
+ uri: result.uri,
+ androidVersion,
+ timestamp: new Date().toISOString(),
+ },
+ );
+
return result.uri;
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- logger.warn("[CapacitorPlatformService] Could not save to Downloads, using app external storage:", {
- fileName,
- error: errorMessage,
- androidVersion,
- timestamp: new Date().toISOString(),
- });
-
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ logger.warn(
+ "[CapacitorPlatformService] Could not save to Downloads, using app external storage:",
+ {
+ fileName,
+ error: errorMessage,
+ androidVersion,
+ timestamp: new Date().toISOString(),
+ },
+ );
+
// Final fallback: app's external storage without subdirectory
const result = await Filesystem.writeFile({
path: fileName,
@@ -1104,41 +1276,53 @@ export class CapacitorPlatformService implements PlatformService {
directory: Directory.ExternalStorage,
encoding: Encoding.UTF8,
});
-
- logger.log("[CapacitorPlatformService] File saved to external storage (fallback):", {
- fileName,
- uri: result.uri,
- androidVersion,
- timestamp: new Date().toISOString(),
- });
-
+
+ logger.log(
+ "[CapacitorPlatformService] File saved to external storage (fallback):",
+ {
+ fileName,
+ uri: result.uri,
+ androidVersion,
+ timestamp: new Date().toISOString(),
+ },
+ );
+
return result.uri;
}
}
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- logger.error("[CapacitorPlatformService] Save to persistent storage failed:", {
- fileName,
- error: errorMessage,
- timestamp: new Date().toISOString(),
- });
-
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ logger.error(
+ "[CapacitorPlatformService] Save to persistent storage failed:",
+ {
+ fileName,
+ error: errorMessage,
+ timestamp: new Date().toISOString(),
+ },
+ );
+
// Final fallback: app's data directory (not user-accessible but guaranteed to work)
- logger.warn("[CapacitorPlatformService] Attempting final fallback to app data directory");
-
+ logger.warn(
+ "[CapacitorPlatformService] Attempting final fallback to app data directory",
+ );
+
const result = await Filesystem.writeFile({
path: fileName,
data: content,
directory: Directory.Data,
encoding: Encoding.UTF8,
});
-
- logger.log("[CapacitorPlatformService] File saved to app data directory (fallback):", {
- fileName,
- uri: result.uri,
- timestamp: new Date().toISOString(),
- });
-
+
+ logger.log(
+ "[CapacitorPlatformService] File saved to app data directory (fallback):",
+ {
+ fileName,
+ uri: result.uri,
+ timestamp: new Date().toISOString(),
+ },
+ );
+
return result.uri;
}
}
@@ -1176,7 +1360,9 @@ export class CapacitorPlatformService implements PlatformService {
* Returns files from persistent storage that survive app installations.
* @returns Promise resolving to array of file information
*/
- async listUserAccessibleFiles(): Promise> {
+ async listUserAccessibleFiles(): Promise<
+ Array<{ name: string; uri: string; size?: number }>
+ > {
try {
if (this.getCapabilities().isIOS) {
// iOS: List files in Documents directory (persistent, accessible via Files app)
@@ -1185,7 +1371,9 @@ export class CapacitorPlatformService implements PlatformService {
directory: Directory.Documents,
});
logger.log("[CapacitorPlatformService] Files in iOS Documents:", {
- files: result.files.map((file) => (typeof file === "string" ? file : file.name)),
+ files: result.files.map((file) =>
+ typeof file === "string" ? file : file.name,
+ ),
timestamp: new Date().toISOString(),
});
return result.files.map((file) => ({
@@ -1195,18 +1383,24 @@ export class CapacitorPlatformService implements PlatformService {
}));
} else {
// Android: List files from persistent storage locations
- const allFiles: Array<{name: string, uri: string, size?: number}> = [];
-
+ const allFiles: Array<{ name: string; uri: string; size?: number }> =
+ [];
+
// Try to list files from Downloads/TimeSafari directory
try {
const downloadsResult = await Filesystem.readdir({
path: "Download/TimeSafari",
directory: Directory.ExternalStorage,
});
- logger.log("[CapacitorPlatformService] Files in Downloads/TimeSafari:", {
- files: downloadsResult.files.map((file) => (typeof file === "string" ? file : file.name)),
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] Files in Downloads/TimeSafari:",
+ {
+ files: downloadsResult.files.map((file) =>
+ typeof file === "string" ? file : file.name,
+ ),
+ timestamp: new Date().toISOString(),
+ },
+ );
const downloadFiles = downloadsResult.files.map((file) => ({
name: typeof file === "string" ? file : file.name,
uri: `file://${file.uri || file}`,
@@ -1216,22 +1410,32 @@ export class CapacitorPlatformService implements PlatformService {
} catch (downloadsError) {
const err = downloadsError as Error;
if (err.message.includes("User denied storage permission")) {
- logger.warn("[CapacitorPlatformService] Storage permission denied for Downloads/TimeSafari, skipping");
+ logger.warn(
+ "[CapacitorPlatformService] Storage permission denied for Downloads/TimeSafari, skipping",
+ );
} else {
- logger.warn("[CapacitorPlatformService] Could not read Downloads/TimeSafari directory:", downloadsError);
+ logger.warn(
+ "[CapacitorPlatformService] Could not read Downloads/TimeSafari directory:",
+ downloadsError,
+ );
}
}
-
+
// Try to list files from app's external storage directory
try {
const appStorageResult = await Filesystem.readdir({
path: "TimeSafari",
directory: Directory.ExternalStorage,
});
- logger.log("[CapacitorPlatformService] Files in TimeSafari (external storage):", {
- files: appStorageResult.files.map((file) => (typeof file === "string" ? file : file.name)),
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] Files in TimeSafari (external storage):",
+ {
+ files: appStorageResult.files.map((file) =>
+ typeof file === "string" ? file : file.name,
+ ),
+ timestamp: new Date().toISOString(),
+ },
+ );
const appStorageFiles = appStorageResult.files.map((file) => ({
name: typeof file === "string" ? file : file.name,
uri: `file://${file.uri || file}`,
@@ -1241,30 +1445,44 @@ export class CapacitorPlatformService implements PlatformService {
} catch (appStorageError) {
const err = appStorageError as Error;
if (err.message.includes("User denied storage permission")) {
- logger.warn("[CapacitorPlatformService] Storage permission denied for TimeSafari, skipping");
+ logger.warn(
+ "[CapacitorPlatformService] Storage permission denied for TimeSafari, skipping",
+ );
} else {
- logger.warn("[CapacitorPlatformService] Could not read app external storage directory:", appStorageError);
+ logger.warn(
+ "[CapacitorPlatformService] Could not read app external storage directory:",
+ appStorageError,
+ );
}
}
-
+
// Remove duplicates based on filename
- const uniqueFiles = allFiles.filter((file, index, self) =>
- index === self.findIndex(f => f.name === file.name)
+ const uniqueFiles = allFiles.filter(
+ (file, index, self) =>
+ index === self.findIndex((f) => f.name === file.name),
+ );
+ logger.log(
+ "[CapacitorPlatformService] Total unique files found in persistent storage:",
+ {
+ total: uniqueFiles.length,
+ files: uniqueFiles.map((f) => f.name),
+ timestamp: new Date().toISOString(),
+ },
);
- logger.log("[CapacitorPlatformService] Total unique files found in persistent storage:", {
- total: uniqueFiles.length,
- files: uniqueFiles.map(f => f.name),
- timestamp: new Date().toISOString(),
- });
return uniqueFiles;
}
} catch (error) {
const err = error as Error;
if (err.message.includes("User denied storage permission")) {
- logger.warn("[CapacitorPlatformService] Storage permission denied, returning empty file list");
+ logger.warn(
+ "[CapacitorPlatformService] Storage permission denied, returning empty file list",
+ );
return [];
}
- logger.error("[CapacitorPlatformService] Failed to list user accessible files:", error);
+ logger.error(
+ "[CapacitorPlatformService] Failed to list user accessible files:",
+ error,
+ );
return [];
}
}
@@ -1274,14 +1492,16 @@ export class CapacitorPlatformService implements PlatformService {
*/
async testListAllBackupFiles(): Promise {
try {
- let output = '';
+ let output = "";
if (this.getCapabilities().isIOS) {
const result = await Filesystem.readdir({
path: ".",
directory: Directory.Documents,
});
output += `iOS Documents files:\n`;
- output += result.files.map((file) => (typeof file === "string" ? file : file.name)).join("\n");
+ output += result.files
+ .map((file) => (typeof file === "string" ? file : file.name))
+ .join("\n");
} else {
output += `Android Downloads/TimeSafari files:\n`;
try {
@@ -1289,7 +1509,9 @@ export class CapacitorPlatformService implements PlatformService {
path: "Download/TimeSafari",
directory: Directory.ExternalStorage,
});
- output += downloadsResult.files.map((file) => (typeof file === "string" ? file : file.name)).join("\n");
+ output += downloadsResult.files
+ .map((file) => (typeof file === "string" ? file : file.name))
+ .join("\n");
} catch (e) {
output += "(Could not read Downloads/TimeSafari)\n";
}
@@ -1299,15 +1521,22 @@ export class CapacitorPlatformService implements PlatformService {
path: "TimeSafari",
directory: Directory.ExternalStorage,
});
- output += appStorageResult.files.map((file) => (typeof file === "string" ? file : file.name)).join("\n");
+ output += appStorageResult.files
+ .map((file) => (typeof file === "string" ? file : file.name))
+ .join("\n");
} catch (e) {
output += "(Could not read TimeSafari external storage)\n";
}
}
- logger.log("[CapacitorPlatformService] testListAllBackupFiles output:\n" + output);
+ logger.log(
+ "[CapacitorPlatformService] testListAllBackupFiles output:\n" + output,
+ );
return output;
} catch (error) {
- logger.error("[CapacitorPlatformService] testListAllBackupFiles error:", error);
+ logger.error(
+ "[CapacitorPlatformService] testListAllBackupFiles error:",
+ error,
+ );
return `Error: ${error}`;
}
}
@@ -1322,18 +1551,18 @@ export class CapacitorPlatformService implements PlatformService {
message: "This is a test file for TimeSafari file sharing",
timestamp: new Date().toISOString(),
platform: this.getCapabilities().isIOS ? "iOS" : "Android",
- test: true
+ test: true,
};
const fileName = `timesafari-test-${getTimestampForFilename()}.json`;
const content = JSON.stringify(testContent, null, 2);
const result = await this.writeAndShareFile(fileName, content, {
- mimeType: "application/json"
+ mimeType: "application/json",
});
if (result.saved) {
- return `✅ File saved successfully! URI: ${result.uri}. Shared: ${result.shared ? 'Yes' : 'No'}`;
+ return `✅ File saved successfully! URI: ${result.uri}. Shared: ${result.shared ? "Yes" : "No"}`;
} else {
return `❌ File save failed: ${result.error}`;
}
@@ -1354,7 +1583,7 @@ export class CapacitorPlatformService implements PlatformService {
timestamp: new Date().toISOString(),
platform: this.getCapabilities().isIOS ? "iOS" : "Android",
test: true,
- saveOnly: true
+ saveOnly: true,
};
const fileName = `timesafari-save-only-${getTimestampForFilename()}.json`;
@@ -1362,7 +1591,7 @@ export class CapacitorPlatformService implements PlatformService {
const result = await this.writeAndShareFile(fileName, content, {
mimeType: "application/json",
- showShareDialog: false
+ showShareDialog: false,
});
if (result.saved) {
@@ -1387,14 +1616,18 @@ export class CapacitorPlatformService implements PlatformService {
timestamp: new Date().toISOString(),
platform: this.getCapabilities().isIOS ? "iOS" : "Android",
test: true,
- locationSelection: true
+ locationSelection: true,
};
const fileName = `timesafari-location-test-${getTimestampForFilename()}.json`;
const content = JSON.stringify(testContent, null, 2);
// Use the FilePicker to let user choose where to save the file
- const result = await this.saveFileWithLocationPicker(fileName, content, "application/json");
+ const result = await this.saveFileWithLocationPicker(
+ fileName,
+ content,
+ "application/json",
+ );
return `✅ Location selection test successful! File saved using directory picker. URI: ${result}`;
} catch (error) {
@@ -1415,7 +1648,7 @@ export class CapacitorPlatformService implements PlatformService {
platform: this.getCapabilities().isIOS ? "iOS" : "Android",
test: true,
locationSelection: true,
- silent: true
+ silent: true,
};
const fileName = `timesafari-silent-location-test-${getTimestampForFilename()}.json`;
@@ -1424,7 +1657,7 @@ export class CapacitorPlatformService implements PlatformService {
const result = await this.writeAndShareFile(fileName, content, {
allowLocationSelection: true,
showLocationSelectionDialog: false,
- mimeType: "application/json"
+ mimeType: "application/json",
});
if (result.saved) {
@@ -1522,9 +1755,10 @@ export class CapacitorPlatformService implements PlatformService {
* @returns Promise that resolves when the camera is rotated
*/
async rotateCamera(): Promise {
- this.currentDirection = this.currentDirection === "BACK" as CameraDirection
- ? "FRONT" as CameraDirection
- : "BACK" as CameraDirection;
+ this.currentDirection =
+ this.currentDirection === ("BACK" as CameraDirection)
+ ? ("FRONT" as CameraDirection)
+ : ("BACK" as CameraDirection);
logger.debug(`Camera rotated to ${this.currentDirection} camera`);
}
@@ -1626,14 +1860,21 @@ export class CapacitorPlatformService implements PlatformService {
* @param mimeType - MIME type of the file
* @returns Promise resolving to the saved file URI
*/
- private async saveWithUserChoice(fileName: string, content: string, mimeType: string = "application/json"): Promise {
+ private async saveWithUserChoice(
+ fileName: string,
+ content: string,
+ mimeType: string = "application/json",
+ ): Promise {
try {
- logger.log("[CapacitorPlatformService] Providing true user choice for file location:", {
- fileName,
- mimeType,
- platform: this.getCapabilities().isIOS ? "iOS" : "Android",
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] Providing true user choice for file location:",
+ {
+ fileName,
+ mimeType,
+ platform: this.getCapabilities().isIOS ? "iOS" : "Android",
+ timestamp: new Date().toISOString(),
+ },
+ );
if (this.getCapabilities().isIOS) {
// iOS: Use the native share dialog which includes "Save to Files" options
@@ -1641,11 +1882,18 @@ export class CapacitorPlatformService implements PlatformService {
return await this.saveFileWithPicker(fileName, content, mimeType);
} else {
// Android: Use FilePicker to let user choose the exact directory
- return await this.saveFileWithLocationPicker(fileName, content, mimeType);
+ return await this.saveFileWithLocationPicker(
+ fileName,
+ content,
+ mimeType,
+ );
}
} catch (error) {
- logger.error("[CapacitorPlatformService] User choice save failed, falling back to default location:", error);
-
+ logger.error(
+ "[CapacitorPlatformService] User choice save failed, falling back to default location:",
+ error,
+ );
+
// Fallback to default location if user choice fails
return await this.saveToDownloads(fileName, content);
}
@@ -1660,17 +1908,20 @@ export class CapacitorPlatformService implements PlatformService {
* @returns Promise resolving to the saved file URI
*/
private async saveFileWithLocationPicker(
- fileName: string,
- content: string,
- mimeType: string = "application/json"
+ fileName: string,
+ content: string,
+ mimeType: string = "application/json",
): Promise {
try {
- logger.log("[CapacitorPlatformService] Using FilePicker pickDirectory for location selection:", {
- fileName,
- mimeType,
- platform: this.getCapabilities().isIOS ? "iOS" : "Android",
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] Using FilePicker pickDirectory for location selection:",
+ {
+ fileName,
+ mimeType,
+ platform: this.getCapabilities().isIOS ? "iOS" : "Android",
+ timestamp: new Date().toISOString(),
+ },
+ );
// Use FilePicker to let user choose a directory to save the file
const directoryResult = await FilePicker.pickDirectory();
@@ -1682,12 +1933,15 @@ export class CapacitorPlatformService implements PlatformService {
// Save the file to the selected directory
const fullPath = `${directoryResult.path}/${fileName}`;
-
+
// Try to save to the user-selected directory
try {
// Ensure the selected directory exists before writing
- await this.ensureDirectoryExists(directoryResult.path, Directory.ExternalStorage);
-
+ await this.ensureDirectoryExists(
+ directoryResult.path,
+ Directory.ExternalStorage,
+ );
+
const result = await Filesystem.writeFile({
path: fullPath,
data: content,
@@ -1696,37 +1950,53 @@ export class CapacitorPlatformService implements PlatformService {
recursive: true,
});
- logger.log("[CapacitorPlatformService] File saved to selected directory:", {
- finalUri: result.uri,
- fullPath,
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] File saved to selected directory:",
+ {
+ finalUri: result.uri,
+ fullPath,
+ timestamp: new Date().toISOString(),
+ },
+ );
return result.uri;
} catch (writeError) {
- logger.warn("[CapacitorPlatformService] Failed to write to selected directory, trying alternative:", writeError);
-
+ logger.warn(
+ "[CapacitorPlatformService] Failed to write to selected directory, trying alternative:",
+ writeError,
+ );
+
// Fallback: Save to Downloads with the selected filename
const downloadsResult = await this.saveToDownloads(fileName, content);
-
- logger.log("[CapacitorPlatformService] File saved to Downloads as fallback:", {
- finalUri: downloadsResult,
- originalPath: fullPath,
- timestamp: new Date().toISOString(),
- });
-
+
+ logger.log(
+ "[CapacitorPlatformService] File saved to Downloads as fallback:",
+ {
+ finalUri: downloadsResult,
+ originalPath: fullPath,
+ timestamp: new Date().toISOString(),
+ },
+ );
+
return downloadsResult;
}
} catch (error) {
- logger.error("[CapacitorPlatformService] FilePicker directory selection failed:", error);
-
+ logger.error(
+ "[CapacitorPlatformService] FilePicker directory selection failed:",
+ error,
+ );
+
// If directory picker fails, fallback to Downloads
try {
- logger.log("[CapacitorPlatformService] Falling back to Downloads directory");
+ logger.log(
+ "[CapacitorPlatformService] Falling back to Downloads directory",
+ );
const fallbackResult = await this.saveToDownloads(fileName, content);
return fallbackResult;
} catch (fallbackError) {
- throw new Error(`Failed to select directory with FilePicker and fallback failed: ${error}`);
+ throw new Error(
+ `Failed to select directory with FilePicker and fallback failed: ${error}`,
+ );
}
}
}
@@ -1738,15 +2008,18 @@ export class CapacitorPlatformService implements PlatformService {
async testListUserFiles(): Promise {
try {
const files = await this.listUserAccessibleFiles();
-
+
if (files.length === 0) {
return `📁 No user-accessible files found. Try saving some files first using the other test buttons.`;
}
-
- const fileList = files.map(file =>
- `- ${file.name} (${file.size ? `${file.size} bytes` : 'size unknown'})`
- ).join('\n');
-
+
+ const fileList = files
+ .map(
+ (file) =>
+ `- ${file.name} (${file.size ? `${file.size} bytes` : "size unknown"})`,
+ )
+ .join("\n");
+
return `📁 Found ${files.length} user-accessible file(s):\n${fileList}`;
} catch (error) {
const err = error as Error;
@@ -1761,16 +2034,21 @@ export class CapacitorPlatformService implements PlatformService {
async testBackupFiles(): Promise {
try {
const files = await this.listBackupFiles();
-
+
if (files.length === 0) {
return `📁 No backup files found. Try creating some backups first using the export functions.`;
}
-
- const fileList = files.map(file => {
- const pathInfo = file.path && !this.getCapabilities().isIOS ? ` (📁 ${file.path})` : '';
- return `- ${file.name} (${file.type}) (${file.size ? `${file.size} bytes` : 'size unknown'})${pathInfo}`;
- }).join('\n');
-
+
+ const fileList = files
+ .map((file) => {
+ const pathInfo =
+ file.path && !this.getCapabilities().isIOS
+ ? ` (📁 ${file.path})`
+ : "";
+ return `- ${file.name} (${file.type}) (${file.size ? `${file.size} bytes` : "size unknown"})${pathInfo}`;
+ })
+ .join("\n");
+
return `📁 Found ${files.length} backup file(s):\n${fileList}`;
} catch (error) {
const err = error as Error;
@@ -1785,7 +2063,7 @@ export class CapacitorPlatformService implements PlatformService {
async testOpenBackupDirectory(): Promise {
try {
const result = await this.openBackupDirectory();
-
+
if (result.success) {
return `✅ Backup directory access dialog opened successfully. Use the share options to access your backup files.`;
} else {
@@ -1802,110 +2080,159 @@ export class CapacitorPlatformService implements PlatformService {
* Filters for files that appear to be TimeSafari backups.
* @returns Promise resolving to array of backup file information
*/
- async listBackupFiles(): Promise> {
+ async listBackupFiles(): Promise<
+ Array<{
+ name: string;
+ uri: string;
+ size?: number;
+ type: "contacts" | "seed" | "other";
+ path?: string;
+ }>
+ > {
try {
// Use enhanced file discovery to find files regardless of where users saved them
const allFiles = await this.listUserAccessibleFilesEnhanced();
-
+
// Only log if files are found or if this is a debug session
if (allFiles.length > 0) {
- logger.log("[CapacitorPlatformService] All user accessible files found (enhanced):", {
- total: allFiles.length,
- files: allFiles.map(f => ({ name: f.name, path: f.path, size: f.size })),
- platform: this.getCapabilities().isIOS ? "iOS" : "Android",
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] All user accessible files found (enhanced):",
+ {
+ total: allFiles.length,
+ files: allFiles.map((f) => ({
+ name: f.name,
+ path: f.path,
+ size: f.size,
+ })),
+ platform: this.getCapabilities().isIOS ? "iOS" : "Android",
+ timestamp: new Date().toISOString(),
+ },
+ );
}
-
+
// Show ALL JSON files and any files with backup-related keywords
const backupFiles = allFiles
- .filter(file => {
+ .filter((file) => {
const name = file.name.toLowerCase();
// Exclude directory-access notification files
- if (name.startsWith('timesafari-directory-access-') && name.endsWith('.txt')) {
+ if (
+ name.startsWith("timesafari-directory-access-") &&
+ name.endsWith(".txt")
+ ) {
return false;
}
-
+
// Check if file matches any backup criteria
- const isJson = name.endsWith('.json');
- const hasTimeSafari = name.includes('timesafari');
- const hasBackup = name.includes('backup');
- const hasContacts = name.includes('contacts');
- const hasSeed = name.includes('seed');
- const hasExport = name.includes('export');
- const hasData = name.includes('data');
-
- const isBackupFile = isJson || hasTimeSafari || hasBackup || hasContacts || hasSeed || hasExport || hasData;
-
+ const isJson = name.endsWith(".json");
+ const hasTimeSafari = name.includes("timesafari");
+ const hasBackup = name.includes("backup");
+ const hasContacts = name.includes("contacts");
+ const hasSeed = name.includes("seed");
+ const hasExport = name.includes("export");
+ const hasData = name.includes("data");
+
+ const isBackupFile =
+ isJson ||
+ hasTimeSafari ||
+ hasBackup ||
+ hasContacts ||
+ hasSeed ||
+ hasExport ||
+ hasData;
+
// Only log exclusions in debug mode or if no backup files are found
if (!isBackupFile && allFiles.length > 0) {
- logger.log("[CapacitorPlatformService] Excluding file (no backup keywords):", {
- name: file.name,
- path: file.path,
- isJson,
- hasTimeSafari,
- hasBackup,
- hasContacts,
- hasSeed,
- hasExport,
- hasData
- });
+ logger.log(
+ "[CapacitorPlatformService] Excluding file (no backup keywords):",
+ {
+ name: file.name,
+ path: file.path,
+ isJson,
+ hasTimeSafari,
+ hasBackup,
+ hasContacts,
+ hasSeed,
+ hasExport,
+ hasData,
+ },
+ );
}
-
+
return isBackupFile;
})
- .map(file => {
+ .map((file) => {
const name = file.name.toLowerCase();
- let type: 'contacts' | 'seed' | 'other' = 'other';
-
+ let type: "contacts" | "seed" | "other" = "other";
+
// Categorize files based on content
- if (name.includes('contacts') || (name.includes('timesafari') && name.includes('backup'))) {
- type = 'contacts';
- } else if (name.includes('seed') || name.includes('mnemonic') || name.includes('private')) {
- type = 'seed';
- } else if (name.endsWith('.json')) {
+ if (
+ name.includes("contacts") ||
+ (name.includes("timesafari") && name.includes("backup"))
+ ) {
+ type = "contacts";
+ } else if (
+ name.includes("seed") ||
+ name.includes("mnemonic") ||
+ name.includes("private")
+ ) {
+ type = "seed";
+ } else if (name.endsWith(".json")) {
// All JSON files are considered backup files
- type = 'other';
+ type = "other";
}
-
+
return {
...file,
- type
+ type,
};
});
-
+
// Only log if backup files are found
- if (backupFiles.length > 0) {
- logger.log("[CapacitorPlatformService] Found backup files (enhanced discovery):", {
- total: backupFiles.length,
- files: backupFiles.map(f => ({
- name: f.name,
- type: f.type,
- path: f.path
- })),
- platform: this.getCapabilities().isIOS ? "iOS" : "Android",
- timestamp: new Date().toISOString(),
- });
+ if (backupFiles.length > 0) {
+ logger.log(
+ "[CapacitorPlatformService] Found backup files (enhanced discovery):",
+ {
+ total: backupFiles.length,
+ files: backupFiles.map((f) => ({
+ name: f.name,
+ type: f.type,
+ path: f.path,
+ })),
+ platform: this.getCapabilities().isIOS ? "iOS" : "Android",
+ timestamp: new Date().toISOString(),
+ },
+ );
}
// If no backup files found, log helpful information for debugging
if (backupFiles.length === 0 && allFiles.length > 0) {
- logger.log("[CapacitorPlatformService] No backup files found, but other files exist:", {
- totalFiles: allFiles.length,
- files: allFiles.map(f => ({ name: f.name, path: f.path })),
- guidance: "To create backup files, use the export functionality in the app. Backup files should be JSON files or contain keywords like 'timesafari', 'backup', 'contacts', 'seed', 'export', or 'data'.",
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] No backup files found, but other files exist:",
+ {
+ totalFiles: allFiles.length,
+ files: allFiles.map((f) => ({ name: f.name, path: f.path })),
+ guidance:
+ "To create backup files, use the export functionality in the app. Backup files should be JSON files or contain keywords like 'timesafari', 'backup', 'contacts', 'seed', 'export', or 'data'.",
+ timestamp: new Date().toISOString(),
+ },
+ );
} else if (backupFiles.length === 0 && allFiles.length === 0) {
- logger.log("[CapacitorPlatformService] No files found in any accessible location:", {
- guidance: "This could be due to storage permissions being denied or no files being saved yet. Users can create backup files using the export functionality.",
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] No files found in any accessible location:",
+ {
+ guidance:
+ "This could be due to storage permissions being denied or no files being saved yet. Users can create backup files using the export functionality.",
+ timestamp: new Date().toISOString(),
+ },
+ );
}
-
+
return backupFiles;
} catch (error) {
- logger.error("[CapacitorPlatformService] Failed to list backup files:", error);
+ logger.error(
+ "[CapacitorPlatformService] Failed to list backup files:",
+ error,
+ );
return [];
}
}
@@ -1917,25 +2244,35 @@ export class CapacitorPlatformService implements PlatformService {
*/
async openBackupDirectory(): Promise<{ success: boolean; error?: string }> {
try {
- logger.log("[CapacitorPlatformService] User requested to open backup directory:", {
- platform: this.getCapabilities().isIOS ? "iOS" : "Android",
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] User requested to open backup directory:",
+ {
+ platform: this.getCapabilities().isIOS ? "iOS" : "Android",
+ timestamp: new Date().toISOString(),
+ },
+ );
// Instead of creating a .txt file, just notify the user
if (this.getCapabilities().isIOS) {
// iOS: Instruct user to open Files app > Documents > TimeSafari
- alert("To access your backups, open the Files app and navigate to the Documents folder. Look for the TimeSafari folder or files.");
+ alert(
+ "To access your backups, open the Files app and navigate to the Documents folder. Look for the TimeSafari folder or files.",
+ );
} else {
// Android: Instruct user to open Downloads/TimeSafari or TimeSafari in their file manager
- alert("To access your backups, open your file manager and navigate to Downloads/TimeSafari or TimeSafari in external storage.");
+ alert(
+ "To access your backups, open your file manager and navigate to Downloads/TimeSafari or TimeSafari in external storage.",
+ );
}
return { success: true };
} catch (error) {
const err = error as Error;
- logger.error("[CapacitorPlatformService] Failed to open backup directory:", {
- error: err.message,
- timestamp: new Date().toISOString(),
- });
+ logger.error(
+ "[CapacitorPlatformService] Failed to open backup directory:",
+ {
+ error: err.message,
+ timestamp: new Date().toISOString(),
+ },
+ );
return { success: false, error: err.message };
}
}
@@ -1947,7 +2284,10 @@ export class CapacitorPlatformService implements PlatformService {
* @param fileName - Name of the file (for display purposes)
* @returns Promise resolving to success status
*/
- async openFile(fileUri: string, fileName: string): Promise<{ success: boolean; error?: string }> {
+ async openFile(
+ fileUri: string,
+ fileName: string,
+ ): Promise<{ success: boolean; error?: string }> {
try {
logger.log("[CapacitorPlatformService] Opening file:", {
uri: fileUri,
@@ -1963,7 +2303,9 @@ export class CapacitorPlatformService implements PlatformService {
dialogTitle: "Choose how to open your file",
});
- logger.log("[CapacitorPlatformService] File open dialog completed successfully");
+ logger.log(
+ "[CapacitorPlatformService] File open dialog completed successfully",
+ );
return { success: true };
} catch (error) {
const err = error as Error;
@@ -1973,15 +2315,19 @@ export class CapacitorPlatformService implements PlatformService {
fileName,
timestamp: new Date().toISOString(),
});
-
+
// Check if it's a user cancellation
- if (err.message.includes("cancel") ||
- err.message.includes("dismiss") ||
- err.message.includes("timeout")) {
- logger.log("[CapacitorPlatformService] User cancelled file open dialog");
+ if (
+ err.message.includes("cancel") ||
+ err.message.includes("dismiss") ||
+ err.message.includes("timeout")
+ ) {
+ logger.log(
+ "[CapacitorPlatformService] User cancelled file open dialog",
+ );
return { success: true }; // Don't treat cancellation as error
}
-
+
return { success: false, error: err.message };
}
}
@@ -1990,40 +2336,47 @@ export class CapacitorPlatformService implements PlatformService {
* Debug method: List all files without filtering for troubleshooting
* @returns Promise resolving to all files found in user accessible locations
*/
- async debugListAllFiles(): Promise> {
+ async debugListAllFiles(): Promise<
+ Array<{ name: string; uri: string; size?: number; path?: string }>
+ > {
try {
const allFiles = await this.listUserAccessibleFiles();
-
- logger.log("[CapacitorPlatformService] DEBUG: All files found (no filtering):", {
- total: allFiles.length,
- files: allFiles.map(f => ({
- name: f.name,
- size: f.size,
- uri: f.uri
- })),
- platform: this.getCapabilities().isIOS ? "iOS" : "Android",
- timestamp: new Date().toISOString(),
- });
-
+
+ logger.log(
+ "[CapacitorPlatformService] DEBUG: All files found (no filtering):",
+ {
+ total: allFiles.length,
+ files: allFiles.map((f) => ({
+ name: f.name,
+ size: f.size,
+ uri: f.uri,
+ })),
+ platform: this.getCapabilities().isIOS ? "iOS" : "Android",
+ timestamp: new Date().toISOString(),
+ },
+ );
+
// Add path information for Android
- const filesWithPath = allFiles.map(file => {
+ const filesWithPath = allFiles.map((file) => {
let path: string | undefined;
if (!this.getCapabilities().isIOS) {
try {
const uri = file.uri;
- if (uri.startsWith('file://')) {
+ if (uri.startsWith("file://")) {
const decodedPath = decodeURIComponent(uri.substring(7));
- if (decodedPath.includes('Download/TimeSafari')) {
- const timeSafariIndex = decodedPath.indexOf('Download/TimeSafari');
+ if (decodedPath.includes("Download/TimeSafari")) {
+ const timeSafariIndex = decodedPath.indexOf(
+ "Download/TimeSafari",
+ );
path = decodedPath.substring(timeSafariIndex);
- } else if (decodedPath.includes('TimeSafari')) {
- const timeSafariIndex = decodedPath.indexOf('TimeSafari');
+ } else if (decodedPath.includes("TimeSafari")) {
+ const timeSafariIndex = decodedPath.indexOf("TimeSafari");
path = decodedPath.substring(timeSafariIndex);
- } else if (decodedPath.includes('Download')) {
- const downloadIndex = decodedPath.indexOf('Download');
+ } else if (decodedPath.includes("Download")) {
+ const downloadIndex = decodedPath.indexOf("Download");
path = decodedPath.substring(downloadIndex);
- } else if (decodedPath.includes('Android')) {
- const androidIndex = decodedPath.indexOf('Android');
+ } else if (decodedPath.includes("Android")) {
+ const androidIndex = decodedPath.indexOf("Android");
path = decodedPath.substring(androidIndex);
} else {
path = `./${file.name}`;
@@ -2035,16 +2388,19 @@ export class CapacitorPlatformService implements PlatformService {
path = `./${file.name}`;
}
}
-
+
return {
...file,
- path
+ path,
};
});
-
+
return filesWithPath;
} catch (error) {
- logger.error("[CapacitorPlatformService] DEBUG: Failed to list all files:", error);
+ logger.error(
+ "[CapacitorPlatformService] DEBUG: Failed to list all files:",
+ error,
+ );
return [];
}
}
@@ -2055,7 +2411,7 @@ export class CapacitorPlatformService implements PlatformService {
*/
async debugFileDiscoveryStepByStep(): Promise {
try {
- let debugOutput = '';
+ let debugOutput = "";
debugOutput += `=== TimeSafari File Discovery Debug ===\n`;
debugOutput += `Platform: ${this.getCapabilities().isIOS ? "iOS" : "Android"}\n`;
debugOutput += `Timestamp: ${new Date().toISOString()}\n\n`;
@@ -2075,7 +2431,7 @@ export class CapacitorPlatformService implements PlatformService {
const allFiles = await this.listUserAccessibleFiles();
debugOutput += `Found ${allFiles.length} total files:\n`;
allFiles.forEach((file, index) => {
- debugOutput += ` ${index + 1}. ${file.name} (${file.size || 'unknown'} bytes)\n`;
+ debugOutput += ` ${index + 1}. ${file.name} (${file.size || "unknown"} bytes)\n`;
});
// Step 3: Test backup file filtering
@@ -2083,12 +2439,12 @@ export class CapacitorPlatformService implements PlatformService {
const backupFiles = await this.listBackupFiles();
debugOutput += `Found ${backupFiles.length} backup files:\n`;
backupFiles.forEach((file, index) => {
- debugOutput += ` ${index + 1}. ${file.name} (${file.type}) (${file.size || 'unknown'} bytes)\n`;
+ debugOutput += ` ${index + 1}. ${file.name} (${file.type}) (${file.size || "unknown"} bytes)\n`;
});
// Step 4: Test individual directory access
debugOutput += `\n4. Testing individual directory access...\n`;
-
+
if (this.getCapabilities().isIOS) {
try {
const iosResult = await Filesystem.readdir({
@@ -2138,18 +2494,27 @@ export class CapacitorPlatformService implements PlatformService {
debugOutput += `\n5. Testing file filtering criteria...\n`;
allFiles.forEach((file, index) => {
const name = file.name.toLowerCase();
- const isJson = name.endsWith('.json');
- const hasTimeSafari = name.includes('timesafari');
- const hasBackup = name.includes('backup');
- const hasContacts = name.includes('contacts');
- const hasSeed = name.includes('seed');
- const hasExport = name.includes('export');
- const hasData = name.includes('data');
- const isExcluded = name.startsWith('timesafari-directory-access-') && name.endsWith('.txt');
-
- const isBackupFile = isJson || hasTimeSafari || hasBackup || hasContacts || hasSeed || hasExport || hasData;
+ const isJson = name.endsWith(".json");
+ const hasTimeSafari = name.includes("timesafari");
+ const hasBackup = name.includes("backup");
+ const hasContacts = name.includes("contacts");
+ const hasSeed = name.includes("seed");
+ const hasExport = name.includes("export");
+ const hasData = name.includes("data");
+ const isExcluded =
+ name.startsWith("timesafari-directory-access-") &&
+ name.endsWith(".txt");
+
+ const isBackupFile =
+ isJson ||
+ hasTimeSafari ||
+ hasBackup ||
+ hasContacts ||
+ hasSeed ||
+ hasExport ||
+ hasData;
const shouldInclude = isBackupFile && !isExcluded;
-
+
debugOutput += ` ${index + 1}. ${file.name}:\n`;
debugOutput += ` - isJson: ${isJson}\n`;
debugOutput += ` - hasTimeSafari: ${hasTimeSafari}\n`;
@@ -2161,16 +2526,22 @@ export class CapacitorPlatformService implements PlatformService {
debugOutput += ` - isExcluded: ${isExcluded}\n`;
debugOutput += ` - isBackupFile: ${isBackupFile}\n`;
debugOutput += ` - shouldInclude: ${shouldInclude}\n`;
- debugOutput += ` - actuallyIncluded: ${backupFiles.some(bf => bf.name === file.name)}\n`;
+ debugOutput += ` - actuallyIncluded: ${backupFiles.some((bf) => bf.name === file.name)}\n`;
});
debugOutput += `\n=== Debug Complete ===\n`;
-
- logger.log("[CapacitorPlatformService] File discovery debug output:\n" + debugOutput);
+
+ logger.log(
+ "[CapacitorPlatformService] File discovery debug output:\n" +
+ debugOutput,
+ );
return debugOutput;
} catch (error) {
const errorMsg = `❌ Debug failed: ${error}`;
- logger.error("[CapacitorPlatformService] File discovery debug failed:", error);
+ logger.error(
+ "[CapacitorPlatformService] File discovery debug failed:",
+ error,
+ );
return errorMsg;
}
}
@@ -2180,13 +2551,20 @@ export class CapacitorPlatformService implements PlatformService {
* This method tries multiple directories and handles permission denials gracefully.
* @returns Promise resolving to array of file information
*/
- async listUserAccessibleFilesEnhanced(): Promise> {
- const allFiles: Array<{name: string, uri: string, size?: number, path?: string}> = [];
-
+ async listUserAccessibleFilesEnhanced(): Promise<
+ Array<{ name: string; uri: string; size?: number; path?: string }>
+ > {
+ const allFiles: Array<{
+ name: string;
+ uri: string;
+ size?: number;
+ path?: string;
+ }> = [];
+
try {
// Check permissions once at the beginning
const hasPermissions = await this.hasStoragePermissions();
-
+
if (this.getCapabilities().isIOS) {
// iOS: List files in Documents directory (persistent, accessible via Files app)
try {
@@ -2194,7 +2572,7 @@ export class CapacitorPlatformService implements PlatformService {
path: ".",
directory: Directory.Documents,
});
-
+
const files = result.files
.filter((file) => typeof file === "object" && file.type === "file")
.map((file) => {
@@ -2206,19 +2584,23 @@ export class CapacitorPlatformService implements PlatformService {
path: "Documents",
};
});
-
+
allFiles.push(...files);
-
+
logger.log("[CapacitorPlatformService] iOS Documents files found:", {
fileCount: files.length,
timestamp: new Date().toISOString(),
});
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- logger.warn("[CapacitorPlatformService] Could not read iOS Documents:", {
- error: errorMessage,
- timestamp: new Date().toISOString(),
- });
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ logger.warn(
+ "[CapacitorPlatformService] Could not read iOS Documents:",
+ {
+ error: errorMessage,
+ timestamp: new Date().toISOString(),
+ },
+ );
}
} else {
// Android: Try multiple storage locations
@@ -2229,9 +2611,11 @@ export class CapacitorPlatformService implements PlatformService {
path: "TimeSafari",
directory: Directory.ExternalStorage,
});
-
+
const appFiles = appStorageResult.files
- .filter((file) => typeof file === "object" && file.type === "file")
+ .filter(
+ (file) => typeof file === "object" && file.type === "file",
+ )
.map((file) => {
const fileObj = file as any;
return {
@@ -2241,19 +2625,26 @@ export class CapacitorPlatformService implements PlatformService {
path: "TimeSafari",
};
});
-
+
allFiles.push(...appFiles);
-
- logger.log("[CapacitorPlatformService] Android TimeSafari files found:", {
- fileCount: appFiles.length,
- timestamp: new Date().toISOString(),
- });
+
+ logger.log(
+ "[CapacitorPlatformService] Android TimeSafari files found:",
+ {
+ fileCount: appFiles.length,
+ timestamp: new Date().toISOString(),
+ },
+ );
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- logger.warn("[CapacitorPlatformService] Could not read TimeSafari external storage:", {
- error: errorMessage,
- timestamp: new Date().toISOString(),
- });
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ logger.warn(
+ "[CapacitorPlatformService] Could not read TimeSafari external storage:",
+ {
+ error: errorMessage,
+ timestamp: new Date().toISOString(),
+ },
+ );
}
// 2. Downloads/TimeSafari directory
@@ -2262,9 +2653,11 @@ export class CapacitorPlatformService implements PlatformService {
path: "Download/TimeSafari",
directory: Directory.ExternalStorage,
});
-
+
const downloadFiles = downloadsResult.files
- .filter((file) => typeof file === "object" && file.type === "file")
+ .filter(
+ (file) => typeof file === "object" && file.type === "file",
+ )
.map((file) => {
const fileObj = file as any;
return {
@@ -2274,24 +2667,34 @@ export class CapacitorPlatformService implements PlatformService {
path: "Download/TimeSafari",
};
});
-
+
allFiles.push(...downloadFiles);
-
- logger.log("[CapacitorPlatformService] Android Downloads/TimeSafari files found:", {
- fileCount: downloadFiles.length,
- timestamp: new Date().toISOString(),
- });
+
+ logger.log(
+ "[CapacitorPlatformService] Android Downloads/TimeSafari files found:",
+ {
+ fileCount: downloadFiles.length,
+ timestamp: new Date().toISOString(),
+ },
+ );
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- logger.warn("[CapacitorPlatformService] Could not read Downloads/TimeSafari:", {
- error: errorMessage,
- timestamp: new Date().toISOString(),
- });
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ logger.warn(
+ "[CapacitorPlatformService] Could not read Downloads/TimeSafari:",
+ {
+ error: errorMessage,
+ timestamp: new Date().toISOString(),
+ },
+ );
}
} else {
- logger.log("[CapacitorPlatformService] Storage permissions not available, skipping external storage", {
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] Storage permissions not available, skipping external storage",
+ {
+ timestamp: new Date().toISOString(),
+ },
+ );
}
}
@@ -2301,7 +2704,7 @@ export class CapacitorPlatformService implements PlatformService {
path: ".",
directory: Directory.Data,
});
-
+
const dataFiles = dataResult.files
.filter((file) => typeof file === "object" && file.type === "file")
.map((file) => {
@@ -2313,43 +2716,61 @@ export class CapacitorPlatformService implements PlatformService {
path: "Data",
};
});
-
+
allFiles.push(...dataFiles);
-
+
// Only log if files are found or if this is the first discovery
if (dataFiles.length > 0) {
- logger.log("[CapacitorPlatformService] App data directory files found:", {
- fileCount: dataFiles.length,
- files: dataFiles.map(f => ({ name: f.name, size: f.size })),
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] App data directory files found:",
+ {
+ fileCount: dataFiles.length,
+ files: dataFiles.map((f) => ({ name: f.name, size: f.size })),
+ timestamp: new Date().toISOString(),
+ },
+ );
}
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- logger.warn("[CapacitorPlatformService] Could not read app data directory:", {
- error: errorMessage,
- timestamp: new Date().toISOString(),
- });
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ logger.warn(
+ "[CapacitorPlatformService] Could not read app data directory:",
+ {
+ error: errorMessage,
+ timestamp: new Date().toISOString(),
+ },
+ );
}
// Only log summary if files are found or if this is a debug session
if (allFiles.length > 0) {
- logger.log("[CapacitorPlatformService] Enhanced file discovery results:", {
- totalFiles: allFiles.length,
- hasPermissions,
- platform: this.getCapabilities().isIOS ? "iOS" : "Android",
- files: allFiles.map(f => ({ name: f.name, path: f.path, size: f.size })),
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] Enhanced file discovery results:",
+ {
+ totalFiles: allFiles.length,
+ hasPermissions,
+ platform: this.getCapabilities().isIOS ? "iOS" : "Android",
+ files: allFiles.map((f) => ({
+ name: f.name,
+ path: f.path,
+ size: f.size,
+ })),
+ timestamp: new Date().toISOString(),
+ },
+ );
}
return allFiles;
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- logger.error("[CapacitorPlatformService] Error in enhanced file discovery:", {
- error: errorMessage,
- timestamp: new Date().toISOString(),
- });
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ logger.error(
+ "[CapacitorPlatformService] Error in enhanced file discovery:",
+ {
+ error: errorMessage,
+ timestamp: new Date().toISOString(),
+ },
+ );
return [];
}
}
@@ -2362,7 +2783,7 @@ export class CapacitorPlatformService implements PlatformService {
async testDirectoryContexts(): Promise {
try {
let debugOutput = "=== Directory Context Testing ===\n\n";
-
+
if (this.getCapabilities().isIOS) {
debugOutput += "iOS Platform - Testing Documents directory:\n";
try {
@@ -2379,17 +2800,34 @@ export class CapacitorPlatformService implements PlatformService {
debugOutput += `Documents directory error: ${error}\n`;
}
} else {
- debugOutput += "Android Platform - Testing multiple directory contexts:\n\n";
-
+ debugOutput +=
+ "Android Platform - Testing multiple directory contexts:\n\n";
+
const contexts = [
{ name: "Data (App Private)", directory: Directory.Data, path: "." },
{ name: "Documents", directory: Directory.Documents, path: "." },
- { name: "External Storage Root", directory: Directory.ExternalStorage, path: "." },
- { name: "External Storage Downloads", directory: Directory.ExternalStorage, path: "Download" },
- { name: "External Storage TimeSafari", directory: Directory.ExternalStorage, path: "TimeSafari" },
- { name: "External Storage Download/TimeSafari", directory: Directory.ExternalStorage, path: "Download/TimeSafari" },
+ {
+ name: "External Storage Root",
+ directory: Directory.ExternalStorage,
+ path: ".",
+ },
+ {
+ name: "External Storage Downloads",
+ directory: Directory.ExternalStorage,
+ path: "Download",
+ },
+ {
+ name: "External Storage TimeSafari",
+ directory: Directory.ExternalStorage,
+ path: "TimeSafari",
+ },
+ {
+ name: "External Storage Download/TimeSafari",
+ directory: Directory.ExternalStorage,
+ path: "Download/TimeSafari",
+ },
];
-
+
for (const context of contexts) {
debugOutput += `${context.name}:\n`;
try {
@@ -2408,13 +2846,19 @@ export class CapacitorPlatformService implements PlatformService {
debugOutput += "\n";
}
}
-
+
debugOutput += "=== Context Testing Complete ===\n";
- logger.log("[CapacitorPlatformService] Directory context test results:\n" + debugOutput);
+ logger.log(
+ "[CapacitorPlatformService] Directory context test results:\n" +
+ debugOutput,
+ );
return debugOutput;
} catch (error) {
const errorMsg = `❌ Directory context testing failed: ${error}`;
- logger.error("[CapacitorPlatformService] Directory context testing failed:", error);
+ logger.error(
+ "[CapacitorPlatformService] Directory context testing failed:",
+ error,
+ );
return errorMsg;
}
}
@@ -2424,67 +2868,83 @@ export class CapacitorPlatformService implements PlatformService {
* This is useful for debugging file visibility issues.
* @returns Promise resolving to success status and file information
*/
- async createTestBackupFile(): Promise<{ success: boolean; fileName?: string; uri?: string; error?: string }> {
+ async createTestBackupFile(): Promise<{
+ success: boolean;
+ fileName?: string;
+ uri?: string;
+ error?: string;
+ }> {
try {
- logger.log("[CapacitorPlatformService] Creating test backup file for debugging");
-
+ logger.log(
+ "[CapacitorPlatformService] Creating test backup file for debugging",
+ );
+
// Create a simple test backup file
const testData = {
type: "test_backup",
timestamp: new Date().toISOString(),
- message: "This is a test backup file created for debugging file visibility issues",
+ message:
+ "This is a test backup file created for debugging file visibility issues",
contacts: [
{
did: "did:ethr:0x1234567890abcdef",
name: "Test Contact",
- contactMethods: JSON.stringify([{ type: "EMAIL", value: "test@example.com" }]),
- notes: "Test contact for debugging"
- }
- ]
+ contactMethods: JSON.stringify([
+ { type: "EMAIL", value: "test@example.com" },
+ ]),
+ notes: "Test contact for debugging",
+ },
+ ],
};
-
+
const content = JSON.stringify(testData, null, 2);
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const fileName = `TimeSafari-test-backup-${timestamp}.json`;
-
+
logger.log("[CapacitorPlatformService] Test backup file details:", {
fileName,
contentLength: content.length,
timestamp: new Date().toISOString(),
});
-
+
// Save the test file using the same method as regular backups
const result = await this.writeAndShareFile(fileName, content, {
allowLocationSelection: false,
saveToDownloads: true,
- showShareDialog: false
+ showShareDialog: false,
});
-
+
if (result.saved) {
- logger.log("[CapacitorPlatformService] Test backup file created successfully:", {
- fileName,
- uri: result.uri,
- timestamp: new Date().toISOString(),
- });
-
+ logger.log(
+ "[CapacitorPlatformService] Test backup file created successfully:",
+ {
+ fileName,
+ uri: result.uri,
+ timestamp: new Date().toISOString(),
+ },
+ );
+
return {
success: true,
fileName,
- uri: result.uri
+ uri: result.uri,
};
} else {
throw new Error(result.error || "Failed to save test backup file");
}
} catch (error) {
const err = error as Error;
- logger.error("[CapacitorPlatformService] Failed to create test backup file:", {
- error: err.message,
- timestamp: new Date().toISOString(),
- });
-
+ logger.error(
+ "[CapacitorPlatformService] Failed to create test backup file:",
+ {
+ error: err.message,
+ timestamp: new Date().toISOString(),
+ },
+ );
+
return {
success: false,
- error: err.message
+ error: err.message,
};
}
}
@@ -2497,9 +2957,10 @@ export class CapacitorPlatformService implements PlatformService {
async debugTimeSafariDirectory(): Promise {
try {
let debugOutput = "=== TimeSafari Directory Debug ===\n\n";
-
+
if (this.getCapabilities().isIOS) {
- debugOutput += "iOS: Checking Documents directory for TimeSafari files\n";
+ debugOutput +=
+ "iOS: Checking Documents directory for TimeSafari files\n";
try {
const result = await Filesystem.readdir({
path: ".",
@@ -2515,7 +2976,7 @@ export class CapacitorPlatformService implements PlatformService {
}
} else {
debugOutput += "Android: Checking multiple TimeSafari locations\n\n";
-
+
// Test 1: Check if Download/TimeSafari exists and can be read
debugOutput += "1. Testing Download/TimeSafari directory:\n";
try {
@@ -2532,7 +2993,7 @@ export class CapacitorPlatformService implements PlatformService {
} catch (error) {
debugOutput += ` ❌ Error: ${error}\n`;
}
-
+
// Test 2: Check if TimeSafari exists in root external storage
debugOutput += "\n2. Testing TimeSafari in root external storage:\n";
try {
@@ -2549,7 +3010,7 @@ export class CapacitorPlatformService implements PlatformService {
} catch (error) {
debugOutput += ` ❌ Error: ${error}\n`;
}
-
+
// Test 3: Check what's in the Download directory
debugOutput += "\n3. Testing Download directory contents:\n";
try {
@@ -2566,7 +3027,7 @@ export class CapacitorPlatformService implements PlatformService {
} catch (error) {
debugOutput += ` ❌ Error: ${error}\n`;
}
-
+
// Test 4: Try to create a test file in Download/TimeSafari
debugOutput += "\n4. Testing file creation in Download/TimeSafari:\n";
try {
@@ -2581,13 +3042,19 @@ export class CapacitorPlatformService implements PlatformService {
debugOutput += ` ❌ Error creating test file: ${error}\n`;
}
}
-
+
debugOutput += "\n=== TimeSafari Directory Debug Complete ===\n";
- logger.log("[CapacitorPlatformService] TimeSafari directory debug results:\n" + debugOutput);
+ logger.log(
+ "[CapacitorPlatformService] TimeSafari directory debug results:\n" +
+ debugOutput,
+ );
return debugOutput;
} catch (error) {
const errorMsg = `❌ TimeSafari directory debug failed: ${error}`;
- logger.error("[CapacitorPlatformService] TimeSafari directory debug failed:", error);
+ logger.error(
+ "[CapacitorPlatformService] TimeSafari directory debug failed:",
+ error,
+ );
return errorMsg;
}
}
@@ -2598,38 +3065,58 @@ export class CapacitorPlatformService implements PlatformService {
* @param debugShowAll - Debug flag to treat all entries as files
* @returns Promise resolving to array of directory entries
*/
- async listFilesInDirectory(path: string, debugShowAll: boolean = false): Promise> {
+ async listFilesInDirectory(
+ path: string,
+ debugShowAll: boolean = false,
+ ): Promise<
+ Array<{
+ name: string;
+ uri: string;
+ size?: number;
+ path: string;
+ type: "file" | "folder";
+ }>
+ > {
try {
- logger.log("[CapacitorPlatformService] Listing directory:", { path, debugShowAll });
-
+ logger.log("[CapacitorPlatformService] Listing directory:", {
+ path,
+ debugShowAll,
+ });
+
let directory: Directory;
let actualPath: string;
-
+
if (this.getCapabilities().isIOS) {
directory = Directory.Documents;
- actualPath = path === '.' ? '.' : path;
+ actualPath = path === "." ? "." : path;
} else {
directory = Directory.ExternalStorage;
// Handle nested paths properly - use the full path as provided
actualPath = path;
}
-
- logger.log("[CapacitorPlatformService] Attempting to read directory:", {
- actualPath,
+
+ logger.log("[CapacitorPlatformService] Attempting to read directory:", {
+ actualPath,
directory: directory.toString(),
- platform: this.getCapabilities().isIOS ? "iOS" : "Android"
+ platform: this.getCapabilities().isIOS ? "iOS" : "Android",
});
-
+
const result = await Filesystem.readdir({
path: actualPath,
directory: directory,
});
-
- const entries: Array<{name: string, uri: string, size?: number, path: string, type: 'file' | 'folder'}> = [];
-
+
+ const entries: Array<{
+ name: string;
+ uri: string;
+ size?: number;
+ path: string;
+ type: "file" | "folder";
+ }> = [];
+
for (const file of result.files) {
const fileName = typeof file === "string" ? file : file.name;
-
+
// In debug mode, treat everything as a file
if (debugShowAll) {
entries.push({
@@ -2637,48 +3124,54 @@ export class CapacitorPlatformService implements PlatformService {
uri: `file://${file.uri || file}`,
size: typeof file === "string" ? undefined : file.size,
path: path,
- type: 'file'
+ type: "file",
});
continue;
}
-
+
// Check if it's a directory by trying to get file stats
let isDirectory = false;
try {
const stat = await Filesystem.stat({
path: `${actualPath}/${fileName}`,
- directory: directory
+ directory: directory,
});
- isDirectory = stat.type === 'directory';
+ isDirectory = stat.type === "directory";
} catch (statError) {
// If stat fails, assume it's a file
isDirectory = false;
}
-
+
entries.push({
name: fileName,
uri: `file://${file.uri || file}`,
size: typeof file === "string" ? undefined : file.size,
path: path,
- type: isDirectory ? 'folder' : 'file'
+ type: isDirectory ? "folder" : "file",
});
}
-
+
logger.log("[CapacitorPlatformService] Directory listing result:", {
path,
entryCount: entries.length,
- folders: entries.filter(e => e.type === 'folder').length,
- files: entries.filter(e => e.type === 'file').length
+ folders: entries.filter((e) => e.type === "folder").length,
+ files: entries.filter((e) => e.type === "file").length,
});
-
+
return entries;
} catch (error) {
const err = error as Error;
if (err.message.includes("User denied storage permission")) {
- logger.warn("[CapacitorPlatformService] Storage permission denied for directory listing, returning empty array:", { path, error: err.message });
+ logger.warn(
+ "[CapacitorPlatformService] Storage permission denied for directory listing, returning empty array:",
+ { path, error: err.message },
+ );
return [];
}
- logger.error("[CapacitorPlatformService] Failed to list directory:", { path, error });
+ logger.error("[CapacitorPlatformService] Failed to list directory:", {
+ path,
+ error,
+ });
return [];
}
}
@@ -2693,14 +3186,16 @@ export class CapacitorPlatformService implements PlatformService {
return "On iOS, files are saved to the Documents folder and can be accessed via the Files app. No additional permissions are required.";
} else {
// For Android, provide guidance on how to grant permissions
- return "To access your backup files, please grant storage permissions:\n\n" +
- "1. Go to your device Settings\n" +
- "2. Find 'Apps' or 'Application Manager'\n" +
- "3. Find 'TimeSafari' in the list\n" +
- "4. Tap 'Permissions'\n" +
- "5. Enable 'Storage' permission\n" +
- "6. Return to the app and try again\n\n" +
- "Alternatively, you can save files to your app's private storage which doesn't require external storage permissions.";
+ return (
+ "To access your backup files, please grant storage permissions:\n\n" +
+ "1. Go to your device Settings\n" +
+ "2. Find 'Apps' or 'Application Manager'\n" +
+ "3. Find 'TimeSafari' in the list\n" +
+ "4. Tap 'Permissions'\n" +
+ "5. Enable 'Storage' permission\n" +
+ "6. Return to the app and try again\n\n" +
+ "Alternatively, you can save files to your app's private storage which doesn't require external storage permissions."
+ );
}
} catch (error) {
return "Unable to provide permission guidance. Please check your device settings for app permissions.";
@@ -2713,74 +3208,92 @@ export class CapacitorPlatformService implements PlatformService {
*/
async testDirectoryCreation(): Promise {
try {
- logger.log("[CapacitorPlatformService] Testing directory creation functionality");
-
+ logger.log(
+ "[CapacitorPlatformService] Testing directory creation functionality",
+ );
+
if (this.getCapabilities().isIOS) {
return "✅ Directory creation test not needed on iOS - using Documents directory";
}
-
+
const androidVersion = await this.getAndroidVersion();
const hasRestrictions = await this.hasStorageRestrictions();
-
+
let testResults = `=== Directory Creation Test Results ===\n\n`;
testResults += `Android Version: ${androidVersion}\n`;
testResults += `Has Storage Restrictions: ${hasRestrictions}\n\n`;
-
+
// Test 1: Simple directory creation
try {
- await this.ensureDirectoryExists("TimeSafari", Directory.ExternalStorage);
+ await this.ensureDirectoryExists(
+ "TimeSafari",
+ Directory.ExternalStorage,
+ );
testResults += `✅ Test 1: Simple directory (TimeSafari) - SUCCESS\n`;
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
testResults += `❌ Test 1: Simple directory (TimeSafari) - FAILED: ${errorMessage}\n`;
}
-
+
// Test 2: Nested directory creation
try {
- await this.ensureDirectoryExists("Download/TimeSafari", Directory.ExternalStorage);
+ await this.ensureDirectoryExists(
+ "Download/TimeSafari",
+ Directory.ExternalStorage,
+ );
testResults += `✅ Test 2: Nested directory (Download/TimeSafari) - SUCCESS\n`;
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
testResults += `❌ Test 2: Nested directory (Download/TimeSafari) - FAILED: ${errorMessage}\n`;
}
-
+
// Test 3: Deep nested directory creation
try {
- await this.ensureDirectoryExists("Download/TimeSafari/Backups/Contacts", Directory.ExternalStorage);
+ await this.ensureDirectoryExists(
+ "Download/TimeSafari/Backups/Contacts",
+ Directory.ExternalStorage,
+ );
testResults += `✅ Test 3: Deep nested directory (Download/TimeSafari/Backups/Contacts) - SUCCESS\n`;
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
testResults += `❌ Test 3: Deep nested directory (Download/TimeSafari/Backups/Contacts) - FAILED: ${errorMessage}\n`;
}
-
+
// Test 4: App-specific external directory
if (hasRestrictions && androidVersion && androidVersion >= 10) {
try {
- await this.ensureDirectoryExists("TimeSafari", Directory.ExternalStorage);
+ await this.ensureDirectoryExists(
+ "TimeSafari",
+ Directory.ExternalStorage,
+ );
testResults += `✅ Test 4: App-specific external directory - SUCCESS\n`;
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
testResults += `❌ Test 4: App-specific external directory - FAILED: ${errorMessage}\n`;
}
}
-
+
// Test 5: Test file writing to created directories
testResults += `\n=== File Writing Tests ===\n`;
-
+
try {
const testFileName = `test-${Date.now()}.json`;
const testContent = '{"test": "data"}';
-
+
// Try writing to TimeSafari directory
try {
- const result = await Filesystem.writeFile({
+ await Filesystem.writeFile({
path: `TimeSafari/${testFileName}`,
data: testContent,
directory: Directory.ExternalStorage,
encoding: Encoding.UTF8,
});
testResults += `✅ Test 5a: Write to TimeSafari directory - SUCCESS\n`;
-
+
// Clean up
try {
await Filesystem.deleteFile({
@@ -2792,20 +3305,23 @@ export class CapacitorPlatformService implements PlatformService {
testResults += `⚠️ Test 5a: Cleanup - FAILED (non-critical)\n`;
}
} catch (writeError) {
- const errorMessage = writeError instanceof Error ? writeError.message : String(writeError);
+ const errorMessage =
+ writeError instanceof Error
+ ? writeError.message
+ : String(writeError);
testResults += `❌ Test 5a: Write to TimeSafari directory - FAILED: ${errorMessage}\n`;
}
-
+
// Try writing to Downloads/TimeSafari directory
try {
- const result = await Filesystem.writeFile({
+ await Filesystem.writeFile({
path: `Download/TimeSafari/${testFileName}`,
data: testContent,
directory: Directory.ExternalStorage,
encoding: Encoding.UTF8,
});
testResults += `✅ Test 5b: Write to Downloads/TimeSafari directory - SUCCESS\n`;
-
+
// Clean up
try {
await Filesystem.deleteFile({
@@ -2817,28 +3333,36 @@ export class CapacitorPlatformService implements PlatformService {
testResults += `⚠️ Test 5b: Cleanup - FAILED (non-critical)\n`;
}
} catch (writeError) {
- const errorMessage = writeError instanceof Error ? writeError.message : String(writeError);
+ const errorMessage =
+ writeError instanceof Error
+ ? writeError.message
+ : String(writeError);
testResults += `❌ Test 5b: Write to Downloads/TimeSafari directory - FAILED: ${errorMessage}\n`;
}
-
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
testResults += `❌ Test 5: File writing tests - FAILED: ${errorMessage}\n`;
}
-
+
// Summary
testResults += `\n=== Summary ===\n`;
- testResults += `Android ${androidVersion} with ${hasRestrictions ? 'storage restrictions' : 'no restrictions'}\n`;
+ testResults += `Android ${androidVersion} with ${hasRestrictions ? "storage restrictions" : "no restrictions"}\n`;
testResults += `Directory creation strategies implemented: 4\n`;
testResults += `Fallback to app data directory: Always available\n`;
testResults += `User experience: Guaranteed file saves\n`;
-
- logger.log("[CapacitorPlatformService] Directory creation tests completed successfully");
-
+
+ logger.log(
+ "[CapacitorPlatformService] Directory creation tests completed successfully",
+ );
+
return testResults;
} catch (error) {
const err = error as Error;
- logger.error("[CapacitorPlatformService] Directory creation test failed:", error);
+ logger.error(
+ "[CapacitorPlatformService] Directory creation test failed:",
+ error,
+ );
return `❌ Directory creation test failed: ${err.message}`;
}
}
@@ -2856,24 +3380,32 @@ export class CapacitorPlatformService implements PlatformService {
const androidMatch = userAgent.match(/Android\s+(\d+)/);
if (androidMatch) {
const version = parseInt(androidMatch[1], 10);
- logger.log("[CapacitorPlatformService] Android version detected from user agent:", {
- version,
- userAgent: userAgent.substring(0, 100) + "...",
- timestamp: new Date().toISOString(),
- });
+ logger.log(
+ "[CapacitorPlatformService] Android version detected from user agent:",
+ {
+ version,
+ userAgent: userAgent.substring(0, 100) + "...",
+ timestamp: new Date().toISOString(),
+ },
+ );
return version;
}
-
+
// If we can't detect from user agent, assume Android 10+ for safety
- logger.log("[CapacitorPlatformService] Could not detect Android version, assuming 10+ for safety");
+ logger.log(
+ "[CapacitorPlatformService] Could not detect Android version, assuming 10+ for safety",
+ );
return 10;
}
return null;
} catch (error) {
- logger.warn("[CapacitorPlatformService] Could not detect Android version:", {
- error: error instanceof Error ? error.message : String(error),
- timestamp: new Date().toISOString(),
- });
+ logger.warn(
+ "[CapacitorPlatformService] Could not detect Android version:",
+ {
+ error: error instanceof Error ? error.message : String(error),
+ timestamp: new Date().toISOString(),
+ },
+ );
return null;
}
}
@@ -2908,7 +3440,7 @@ export class CapacitorPlatformService implements PlatformService {
if (this.permissionChecked) {
return this.permissionGranted;
}
-
+
// If not checked yet, check without requesting
try {
await Filesystem.readdir({
@@ -2919,9 +3451,12 @@ export class CapacitorPlatformService implements PlatformService {
this.permissionChecked = true;
return true;
} catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- if (errorMessage.includes("user denied permission") ||
- errorMessage.includes("permission request")) {
+ const errorMessage =
+ error instanceof Error ? error.message : String(error);
+ if (
+ errorMessage.includes("user denied permission") ||
+ errorMessage.includes("permission request")
+ ) {
this.permissionGranted = false;
this.permissionChecked = true;
return false;
@@ -2940,26 +3475,28 @@ export class CapacitorPlatformService implements PlatformService {
if (this.getCapabilities().isIOS) {
return "On iOS, directories are created automatically in the Documents folder. No additional setup is required.";
}
-
+
const androidVersion = await this.getAndroidVersion();
- const hasRestrictions = await this.hasStorageRestrictions();
-
+ await this.hasStorageRestrictions();
+
let guidance = "Android Directory Creation Guidance:\n\n";
-
+
if (androidVersion && androidVersion >= 10) {
guidance += "📱 Android 10+ with Scoped Storage:\n";
guidance += "• Directory creation in external storage is restricted\n";
guidance += "• App uses multiple strategies to create directories\n";
- guidance += "• If directory creation fails, files are saved to app data directory\n";
- guidance += "• Files in app data directory persist between app installations\n\n";
-
+ guidance +=
+ "• If directory creation fails, files are saved to app data directory\n";
+ guidance +=
+ "• Files in app data directory persist between app installations\n\n";
+
guidance += "🔧 Directory Creation Strategies:\n";
guidance += "1. Recursive file creation (works on some devices)\n";
guidance += "2. Parent-by-parent directory creation\n";
guidance += "3. Simple file test creation\n";
guidance += "4. App-specific external directory creation\n";
guidance += "5. Fallback to app data directory (always works)\n\n";
-
+
guidance += "💡 User Experience:\n";
guidance += "• Files are always saved successfully\n";
guidance += "• Backup files are immediately visible in the app\n";
@@ -2969,15 +3506,16 @@ export class CapacitorPlatformService implements PlatformService {
guidance += "📱 Android 9 and below:\n";
guidance += "• Full access to external storage\n";
guidance += "• Directories can be created normally\n";
- guidance += "• Files saved to Downloads/TimeSafari or external storage\n\n";
+ guidance +=
+ "• Files saved to Downloads/TimeSafari or external storage\n\n";
}
-
+
guidance += "🛡️ Privacy & Security:\n";
guidance += "• All files are saved securely\n";
guidance += "• App data directory is private to the app\n";
guidance += "• Files survive app reinstalls\n";
guidance += "• No data loss due to storage restrictions\n";
-
+
return guidance;
} catch (error) {
return "Unable to provide directory creation guidance. Please check your device settings for app permissions.";
diff --git a/src/services/platforms/ElectronPlatformService.ts b/src/services/platforms/ElectronPlatformService.ts
index b06bf349..7de5f413 100644
--- a/src/services/platforms/ElectronPlatformService.ts
+++ b/src/services/platforms/ElectronPlatformService.ts
@@ -240,7 +240,7 @@ export class ElectronPlatformService implements PlatformService {
* @todo Implement using Electron's dialog and file system APIs
*/
async writeAndShareFile(
- _fileName: string,
+ _fileName: string,
_content: string,
_options?: {
allowLocationSelection?: boolean;
@@ -249,12 +249,17 @@ export class ElectronPlatformService implements PlatformService {
mimeType?: string;
showShareDialog?: boolean;
showLocationSelectionDialog?: boolean;
- }
- ): Promise<{ saved: boolean; uri?: string; shared: boolean; error?: string }> {
+ },
+ ): Promise<{
+ saved: boolean;
+ uri?: string;
+ shared: boolean;
+ error?: string;
+ }> {
return {
saved: false,
shared: false,
- error: "Not implemented in Electron platform"
+ error: "Not implemented in Electron platform",
};
}
@@ -435,7 +440,9 @@ export class ElectronPlatformService implements PlatformService {
* Not implemented in Electron platform.
* @returns Promise resolving to empty array
*/
- async listUserAccessibleFiles(): Promise> {
+ async listUserAccessibleFiles(): Promise<
+ Array<{ name: string; uri: string; size?: number }>
+ > {
return [];
}
@@ -444,7 +451,15 @@ export class ElectronPlatformService implements PlatformService {
* Not implemented for Electron platform.
* @returns Promise resolving to empty array
*/
- async listBackupFiles(): Promise> {
+ async listBackupFiles(): Promise<
+ Array<{
+ name: string;
+ uri: string;
+ size?: number;
+ type: "contacts" | "seed" | "other";
+ path?: string;
+ }>
+ > {
return [];
}
@@ -455,8 +470,14 @@ export class ElectronPlatformService implements PlatformService {
* @param _fileName - Name of the file (for display purposes)
* @returns Promise resolving to error status
*/
- async openFile(_fileUri: string, _fileName: string): Promise<{ success: boolean; error?: string }> {
- return { success: false, error: "File opening not implemented in Electron platform" };
+ async openFile(
+ _fileUri: string,
+ _fileName: string,
+ ): Promise<{ success: boolean; error?: string }> {
+ return {
+ success: false,
+ error: "File opening not implemented in Electron platform",
+ };
}
/**
@@ -465,7 +486,10 @@ export class ElectronPlatformService implements PlatformService {
* @returns Promise resolving to error status
*/
async openBackupDirectory(): Promise<{ success: boolean; error?: string }> {
- return { success: false, error: "Directory access not implemented in Electron platform" };
+ return {
+ success: false,
+ error: "Directory access not implemented in Electron platform",
+ };
}
/**
@@ -473,7 +497,18 @@ export class ElectronPlatformService implements PlatformService {
* Not implemented for Electron platform.
* @returns Promise resolving to empty array
*/
- async listFilesInDirectory(path: string, debugShowAll?: boolean): Promise> {
+ async listFilesInDirectory(
+ _path: string,
+ _debugShowAll?: boolean,
+ ): Promise<
+ Array<{
+ name: string;
+ uri: string;
+ size?: number;
+ path: string;
+ type: "file" | "folder";
+ }>
+ > {
return [];
}
@@ -491,10 +526,16 @@ export class ElectronPlatformService implements PlatformService {
* Not implemented for Electron platform.
* @returns Promise resolving to error status
*/
- async createTestBackupFile(): Promise<{ success: boolean; fileName?: string; uri?: string; error?: string }> {
+ async createTestBackupFile(): Promise<{
+ success: boolean;
+ fileName?: string;
+ uri?: string;
+ error?: string;
+ }> {
return {
success: false,
- error: "Electron platform does not support file system access for creating test backup files."
+ error:
+ "Electron platform does not support file system access for creating test backup files.",
};
}
diff --git a/src/services/platforms/PyWebViewPlatformService.ts b/src/services/platforms/PyWebViewPlatformService.ts
index 44dde1e6..721b828b 100644
--- a/src/services/platforms/PyWebViewPlatformService.ts
+++ b/src/services/platforms/PyWebViewPlatformService.ts
@@ -131,7 +131,7 @@ export class PyWebViewPlatformService implements PlatformService {
* @returns Promise that resolves to save/share result
*/
async writeAndShareFile(
- _fileName: string,
+ _fileName: string,
_content: string,
_options?: {
allowLocationSelection?: boolean;
@@ -140,9 +140,18 @@ export class PyWebViewPlatformService implements PlatformService {
mimeType?: string;
showShareDialog?: boolean;
showLocationSelectionDialog?: boolean;
- }
- ): Promise<{ saved: boolean; uri?: string; shared: boolean; error?: string }> {
- return { saved: false, shared: false, error: "File sharing not implemented in PyWebView platform" };
+ },
+ ): Promise<{
+ saved: boolean;
+ uri?: string;
+ shared: boolean;
+ error?: string;
+ }> {
+ return {
+ saved: false,
+ shared: false,
+ error: "File sharing not implemented in PyWebView platform",
+ };
}
/**
@@ -150,7 +159,9 @@ export class PyWebViewPlatformService implements PlatformService {
* Not implemented in PyWebView platform.
* @returns Promise resolving to empty array
*/
- async listUserAccessibleFiles(): Promise> {
+ async listUserAccessibleFiles(): Promise<
+ Array<{ name: string; uri: string; size?: number }>
+ > {
return [];
}
@@ -159,7 +170,15 @@ export class PyWebViewPlatformService implements PlatformService {
* Not implemented for PyWebView platform.
* @returns Promise resolving to empty array
*/
- async listBackupFiles(): Promise> {
+ async listBackupFiles(): Promise<
+ Array<{
+ name: string;
+ uri: string;
+ size?: number;
+ type: "contacts" | "seed" | "other";
+ path?: string;
+ }>
+ > {
return [];
}
@@ -170,8 +189,14 @@ export class PyWebViewPlatformService implements PlatformService {
* @param _fileName - Name of the file (for display purposes)
* @returns Promise resolving to error status
*/
- async openFile(_fileUri: string, _fileName: string): Promise<{ success: boolean; error?: string }> {
- return { success: false, error: "File opening not implemented in PyWebView platform" };
+ async openFile(
+ _fileUri: string,
+ _fileName: string,
+ ): Promise<{ success: boolean; error?: string }> {
+ return {
+ success: false,
+ error: "File opening not implemented in PyWebView platform",
+ };
}
/**
@@ -180,7 +205,10 @@ export class PyWebViewPlatformService implements PlatformService {
* @returns Promise resolving to error status
*/
async openBackupDirectory(): Promise<{ success: boolean; error?: string }> {
- return { success: false, error: "Directory access not implemented in PyWebView platform" };
+ return {
+ success: false,
+ error: "Directory access not implemented in PyWebView platform",
+ };
}
/**
@@ -252,7 +280,18 @@ export class PyWebViewPlatformService implements PlatformService {
* Not implemented for PyWebView platform.
* @returns Promise resolving to empty array
*/
- async listFilesInDirectory(path: string, debugShowAll?: boolean): Promise> {
+ async listFilesInDirectory(
+ _path: string,
+ _debugShowAll?: boolean,
+ ): Promise<
+ Array<{
+ name: string;
+ uri: string;
+ size?: number;
+ path: string;
+ type: "file" | "folder";
+ }>
+ > {
return [];
}
@@ -270,10 +309,16 @@ export class PyWebViewPlatformService implements PlatformService {
* Not implemented for PyWebView platform.
* @returns Promise resolving to error status
*/
- async createTestBackupFile(): Promise<{ success: boolean; fileName?: string; uri?: string; error?: string }> {
+ async createTestBackupFile(): Promise<{
+ success: boolean;
+ fileName?: string;
+ uri?: string;
+ error?: string;
+ }> {
return {
success: false,
- error: "PyWebView platform does not support file system access for creating test backup files."
+ error:
+ "PyWebView platform does not support file system access for creating test backup files.",
};
}
diff --git a/src/services/platforms/WebPlatformService.ts b/src/services/platforms/WebPlatformService.ts
index fed63bf0..3aa69c32 100644
--- a/src/services/platforms/WebPlatformService.ts
+++ b/src/services/platforms/WebPlatformService.ts
@@ -29,9 +29,10 @@ export class WebPlatformService implements PlatformService {
return {
hasFileSystem: false,
hasCamera: true, // Through file input with capture
- isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
- navigator.userAgent,
- ),
+ isMobile:
+ /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
+ navigator.userAgent,
+ ),
isIOS: /iPad|iPhone|iPod/.test(navigator.userAgent),
hasFileDownload: true,
needsFileHandlingInstructions: false,
@@ -363,7 +364,7 @@ export class WebPlatformService implements PlatformService {
* @returns Promise that resolves to a failure result
*/
async writeAndShareFile(
- _fileName: string,
+ _fileName: string,
_content: string,
_options?: {
allowLocationSelection?: boolean;
@@ -372,12 +373,17 @@ export class WebPlatformService implements PlatformService {
mimeType?: string;
showShareDialog?: boolean;
showLocationSelectionDialog?: boolean;
- }
- ): Promise<{ saved: boolean; uri?: string; shared: boolean; error?: string }> {
+ },
+ ): Promise<{
+ saved: boolean;
+ uri?: string;
+ shared: boolean;
+ error?: string;
+ }> {
return {
saved: false,
shared: false,
- error: "File system access not available in web platform"
+ error: "File system access not available in web platform",
};
}
@@ -471,7 +477,9 @@ export class WebPlatformService implements PlatformService {
* Not supported in web platform.
* @returns Promise resolving to empty array
*/
- async listUserAccessibleFiles(): Promise> {
+ async listUserAccessibleFiles(): Promise<
+ Array<{ name: string; uri: string; size?: number }>
+ > {
return [];
}
@@ -480,7 +488,15 @@ export class WebPlatformService implements PlatformService {
* Not supported in web platform.
* @returns Promise resolving to empty array
*/
- async listBackupFiles(): Promise> {
+ async listBackupFiles(): Promise<
+ Array<{
+ name: string;
+ uri: string;
+ size?: number;
+ type: "contacts" | "seed" | "other";
+ path?: string;
+ }>
+ > {
return [];
}
@@ -491,8 +507,14 @@ export class WebPlatformService implements PlatformService {
* @param _fileName - Name of the file (for display purposes)
* @returns Promise resolving to error status
*/
- async openFile(_fileUri: string, _fileName: string): Promise<{ success: boolean; error?: string }> {
- return { success: false, error: "File opening not available in web platform" };
+ async openFile(
+ _fileUri: string,
+ _fileName: string,
+ ): Promise<{ success: boolean; error?: string }> {
+ return {
+ success: false,
+ error: "File opening not available in web platform",
+ };
}
/**
@@ -501,7 +523,10 @@ export class WebPlatformService implements PlatformService {
* @returns Promise resolving to error status
*/
async openBackupDirectory(): Promise<{ success: boolean; error?: string }> {
- return { success: false, error: "Directory access not available in web platform" };
+ return {
+ success: false,
+ error: "Directory access not available in web platform",
+ };
}
/**
@@ -519,7 +544,18 @@ export class WebPlatformService implements PlatformService {
* Not supported in web platform.
* @returns Promise resolving to empty array
*/
- async listFilesInDirectory(path: string, debugShowAll?: boolean): Promise> {
+ async listFilesInDirectory(
+ _path: string,
+ _debugShowAll?: boolean,
+ ): Promise<
+ Array<{
+ name: string;
+ uri: string;
+ size?: number;
+ path: string;
+ type: "file" | "folder";
+ }>
+ > {
return [];
}
@@ -537,10 +573,16 @@ export class WebPlatformService implements PlatformService {
* Not supported in web platform.
* @returns Promise resolving to error status
*/
- async createTestBackupFile(): Promise<{ success: boolean; fileName?: string; uri?: string; error?: string }> {
+ async createTestBackupFile(): Promise<{
+ success: boolean;
+ fileName?: string;
+ uri?: string;
+ error?: string;
+ }> {
return {
success: false,
- error: "Web platform does not support file system access for creating test backup files."
+ error:
+ "Web platform does not support file system access for creating test backup files.",
};
}
diff --git a/src/utils/logger.ts b/src/utils/logger.ts
index 0e7014c0..dd0ca3d4 100644
--- a/src/utils/logger.ts
+++ b/src/utils/logger.ts
@@ -87,7 +87,7 @@ export default { logger };
* @returns Formatted timestamp string safe for filenames
*/
export function getTimestampForFilename(): string {
- return new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
+ return new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
}
/**
diff --git a/src/views/TestView.vue b/src/views/TestView.vue
index 2e72295e..d6c61266 100644
--- a/src/views/TestView.vue
+++ b/src/views/TestView.vue
@@ -217,7 +217,8 @@
File Sharing Test
- Test the new file sharing functionality that saves to user-accessible locations.
+ Test the new file sharing functionality that saves to user-accessible
+ locations.