# TimeSafari Contact Backup System

## Overview

The TimeSafari application implements a comprehensive contact backup and listing system that works across multiple platforms (Web, iOS, Android, Desktop). This document breaks down how contacts are saved, exported, and listed as backups.

## Architecture Components

### 1. Database Layer

#### Contact Data Structure
```typescript
interface Contact {
  did: string;                    // Decentralized Identifier (primary key)
  contactMethods?: ContactMethod[]; // Array of contact methods (EMAIL, SMS, etc.)
  name?: string;                  // Display name
  nextPubKeyHashB64?: string;     // Base64 hash of next public key
  notes?: string;                 // User notes
  profileImageUrl?: string;       // Profile image URL
  publicKeyBase64?: string;       // Base64 encoded public key
  seesMe?: boolean;               // Visibility setting
  registered?: boolean;           // Registration status
}

interface ContactMethod {
  label: string;                  // Display label
  type: string;                   // Type (EMAIL, SMS, WHATSAPP, etc.)
  value: string;                  // Contact value
}
```

#### Database Schema
```sql
CREATE TABLE contacts (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  did TEXT NOT NULL,              -- Decentralized Identifier
  name TEXT,                      -- Display name
  contactMethods TEXT,            -- JSON string of contact methods
  nextPubKeyHashB64 TEXT,         -- Next public key hash
  notes TEXT,                     -- User notes
  profileImageUrl TEXT,           -- Profile image URL
  publicKeyBase64 TEXT,           -- Public key
  seesMe BOOLEAN,                 -- Visibility flag
  registered BOOLEAN              -- Registration status
);

CREATE INDEX idx_contacts_did ON contacts(did);
CREATE INDEX idx_contacts_name ON contacts(name);
```

### 2. Contact Saving Operations

#### A. Adding New Contacts

**1. QR Code Scanning (`ContactQRScanFullView.vue`)**
```typescript
async addNewContact(contact: Contact) {
  // Check for existing contact
  const existingContacts = await platformService.dbQuery(
    "SELECT * FROM contacts WHERE did = ?", [contact.did]
  );
  
  if (existingContact) {
    // Handle duplicate
    return;
  }
  
  // Convert contactMethods to JSON string for storage
  contact.contactMethods = JSON.stringify(
    parseJsonField(contact.contactMethods, [])
  );
  
  // Insert into database
  const { sql, params } = databaseUtil.generateInsertStatement(
    contact as unknown as Record<string, unknown>, "contacts"
  );
  await platformService.dbExec(sql, params);
}
```

**2. Manual Contact Addition (`ContactsView.vue`)**
```typescript
private async addContact(newContact: Contact) {
  // Validate DID format
  if (!isDid(newContact.did)) {
    throw new Error("Invalid DID format");
  }
  
  // Generate and execute INSERT statement
  const { sql, params } = databaseUtil.generateInsertStatement(
    newContact as unknown as Record<string, unknown>, "contacts"
  );
  await platformService.dbExec(sql, params);
}
```

**3. Contact Import (`ContactImportView.vue`)**
```typescript
async importContacts() {
  for (const contact of selectedContacts) {
    const contactToStore = contactToDbRecord(contact);
    
    if (existingContact) {
      // Update existing contact
      const { sql, params } = databaseUtil.generateUpdateStatement(
        contactToStore, "contacts", "did = ?", [contact.did]
      );
      await platformService.dbExec(sql, params);
    } else {
      // Add new contact
      const { sql, params } = databaseUtil.generateInsertStatement(
        contactToStore, "contacts"
      );
      await platformService.dbExec(sql, params);
    }
  }
}
```

#### B. Updating Existing Contacts

**Contact Editing (`ContactEditView.vue`)**
```typescript
async saveEdit() {
  // Normalize contact methods
  const contactMethods = this.contactMethods.map(method => ({
    ...method,
    type: method.type.toUpperCase()
  }));
  
  // Update database
  const contactMethodsString = JSON.stringify(contactMethods);
  await platformService.dbExec(
    "UPDATE contacts SET name = ?, notes = ?, contactMethods = ? WHERE did = ?",
    [this.contactName, this.contactNotes, contactMethodsString, this.contact?.did]
  );
}
```

### 3. Contact Export/Backup System

#### A. Export Process (`DataExportSection.vue`)

#### 1. Data Retrieval

```typescript
async exportDatabase() {
  // Query all contacts from database
  const result = await platformService.dbQuery("SELECT * FROM contacts");
  const allContacts = databaseUtil.mapQueryResultToValues(result) as Contact[];
  
  // Convert to export format
  const exportData = contactsToExportJson(allContacts);
  const jsonStr = JSON.stringify(exportData, null, 2);
}
```

#### 2. Export Format Conversion (`libs/util.ts`)

```typescript
export const contactsToExportJson = (contacts: Contact[]): DatabaseExport => {
  const rows = contacts.map((contact) => ({
    did: contact.did,
    name: contact.name || null,
    contactMethods: contact.contactMethods 
      ? JSON.stringify(parseJsonField(contact.contactMethods, []))
      : null,
    nextPubKeyHashB64: contact.nextPubKeyHashB64 || null,
    notes: contact.notes || null,
    profileImageUrl: contact.profileImageUrl || null,
    publicKeyBase64: contact.publicKeyBase64 || null,
    seesMe: contact.seesMe || false,
    registered: contact.registered || false,
  }));

  return {
    data: {
      data: [{ tableName: "contacts", rows }]
    }
  };
};
```

#### 3. File Generation

```typescript
// Create timestamped filename
const timestamp = getTimestampForFilename();
const fileName = `${AppString.APP_NAME_NO_SPACES}-backup-contacts-${timestamp}.json`;

// Create blob and save
const blob = new Blob([jsonStr], { type: "application/json" });
```

#### B. Platform-Specific File Saving

##### 1. Web Platform (`WebPlatformService.ts`)**

```typescript
// Uses browser download API
const downloadUrl = URL.createObjectURL(blob);
const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement;
downloadAnchor.href = downloadUrl;
downloadAnchor.download = fileName;
downloadAnchor.click();
```

##### 2. Mobile Platforms (`CapacitorPlatformService.ts`)

```typescript
async writeAndShareFile(fileName: string, content: string, options = {}) {
  let fileUri: string;
  
  if (options.allowLocationSelection) {
    // User chooses location
    fileUri = await this.saveWithUserChoice(fileName, content, options.mimeType);
  } else if (options.saveToPrivateStorage) {
    // Save to app-private storage
    const result = await Filesystem.writeFile({
      path: fileName,
      data: content,
      directory: Directory.Data,
      encoding: Encoding.UTF8,
      recursive: true,
    });
    fileUri = result.uri;
  } else {
    // Save to user-accessible location (Downloads/Documents)
    fileUri = await this.saveToDownloads(fileName, content);
  }
  
  // Share the file
  return await this.shareFile(fileUri, fileName);
}
```

##### 3. Desktop Platforms (`ElectronPlatformService.ts`, `PyWebViewPlatformService.ts`)

```typescript
// Not implemented - returns empty results
async listBackupFiles(): Promise<Array<{name: string, uri: string, size?: number, type: 'contacts' | 'seed' | 'other', path?: string}>> {
  return [];
}
```

### 4. Backup File Listing System

#### A. File Discovery (`CapacitorPlatformService.ts`)

##### 1. Enhanced File Discovery

```typescript
async listUserAccessibleFilesEnhanced(): Promise<Array<{name: string, uri: string, size?: number, path?: string}>> {
  const allFiles: Array<{name: string, uri: string, size?: number, path?: string}> = [];
  
  if (this.getCapabilities().isIOS) {
    // iOS: Documents directory
    const result = await Filesystem.readdir({
      path: ".",
      directory: Directory.Documents,
    });
    const files = result.files.map((file) => ({
      name: typeof file === "string" ? file : file.name,
      uri: `file://${file.uri || file}`,
      size: typeof file === "string" ? undefined : file.size,
      path: "Documents"
    }));
    allFiles.push(...files);
  } else {
    // Android: Multiple locations
    const commonPaths = ["Download", "Documents", "Backups", "TimeSafari", "Data"];
    
    for (const path of commonPaths) {
      try {
        const result = await Filesystem.readdir({
          path: path,
          directory: Directory.ExternalStorage,
        });
        
        // Filter for TimeSafari-related files
        const relevantFiles = result.files
          .filter(file => {
            const fileName = typeof file === "string" ? file : file.name;
            const name = fileName.toLowerCase();
            return name.includes('timesafari') || 
                   name.includes('backup') || 
                   name.includes('contacts') ||
                   name.endsWith('.json');
          })
          .map((file) => ({
            name: typeof file === "string" ? file : file.name,
            uri: `file://${file.uri || file}`,
            size: typeof file === "string" ? undefined : file.size,
            path: path
          }));
        
        allFiles.push(...relevantFiles);
      } catch (error) {
        // Silently skip inaccessible directories
      }
    }
  }
  
  return allFiles;
}
```

**2. Backup File Filtering**
```typescript
async listBackupFiles(): Promise<Array<{name: string, uri: string, size?: number, type: 'contacts' | 'seed' | 'other', path?: string}>> {
  const allFiles = await this.listUserAccessibleFilesEnhanced();
  
  const backupFiles = allFiles
    .filter(file => {
      const name = file.name.toLowerCase();
      
      // Exclude directory-access notification files
      if (name.startsWith('timesafari-directory-access-') && name.endsWith('.txt')) {
        return false;
      }
      
      // Check 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');
      
      return isJson || hasTimeSafari || hasBackup || hasContacts || hasSeed || hasExport || hasData;
    })
    .map(file => {
      const name = file.name.toLowerCase();
      let type: 'contacts' | 'seed' | 'other' = 'other';
      
      // Categorize files
      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')) {
        type = 'other';
      }
      
      return { ...file, type };
    });
  
  return backupFiles;
}
```

#### B. UI Components (`BackupFilesList.vue`)

**1. File Display**
```typescript
@Component
export default class BackupFilesList extends Vue {
  backupFiles: Array<{name: string, uri: string, size?: number, type: 'contacts' | 'seed' | 'other', path?: string}> = [];
  selectedType: 'all' | 'contacts' | 'seed' | 'other' = 'all';
  isLoading = false;

  async refreshFiles() {
    this.isLoading = true;
    try {
      this.backupFiles = await this.platformService.listBackupFiles();
      
      // 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
      };
    } catch (error) {
      // Handle error
    } finally {
      this.isLoading = false;
    }
  }
}
```

**2. File Operations**
```typescript
async openFile(fileUri: string, fileName: string) {
  const result = await this.platformService.openFile(fileUri, fileName);
  if (!result.success) {
    throw new Error(result.error || "Failed to open file");
  }
}

async openBackupDirectory() {
  const result = await this.platformService.openBackupDirectory();
  if (!result.success) {
    throw new Error(result.error || "Failed to open backup directory");
  }
}
```

### 5. Platform-Specific Storage Locations

#### A. iOS Platform
- **Primary Location**: Documents folder (accessible via Files app)
- **Persistence**: Survives app installations
- **Access**: Through iOS Files app
- **File Format**: JSON with timestamped filenames

#### B. Android Platform
- **Primary Locations**: 
  - `Download/TimeSafari/` (external storage)
  - `TimeSafari/` (external storage)
  - User-chosen locations via file picker
- **Persistence**: Survives app installations
- **Access**: Through file managers
- **File Format**: JSON with timestamped filenames

#### C. Web Platform
- **Primary Location**: Browser downloads folder
- **Persistence**: Depends on browser settings
- **Access**: Through browser download manager
- **File Format**: JSON with timestamped filenames

#### D. Desktop Platforms (Electron/PyWebView)
- **Status**: Not implemented
- **Fallback**: Returns empty arrays for file operations

### 6. File Naming Convention

#### A. Contact Backup Files
```
TimeSafari-backup-contacts-YYYY-MM-DD-HH-MM-SS.json
```

#### B. File Content Structure
```json
{
  "data": {
    "data": [
      {
        "tableName": "contacts",
        "rows": [
          {
            "did": "did:ethr:0x...",
            "name": "Contact Name",
            "contactMethods": "[{\"type\":\"EMAIL\",\"value\":\"email@example.com\"}]",
            "notes": "User notes",
            "profileImageUrl": "https://...",
            "publicKeyBase64": "base64...",
            "seesMe": true,
            "registered": false
          }
        ]
      }
    ]
  }
}
```

### 7. Error Handling and Logging

#### A. Comprehensive Logging
```typescript
logger.log("[CapacitorPlatformService] File write successful:", {
  uri: fileUri,
  saved,
  timestamp: new Date().toISOString(),
});

logger.log("[BackupFilesList] Refreshed backup files:", {
  count: this.backupFiles.length,
  files: this.backupFiles.map(f => ({ 
    name: f.name, 
    type: f.type, 
    path: f.path,
    size: f.size 
  })),
  platform: this.platformCapabilities.isIOS ? "iOS" : "Android",
  timestamp: new Date().toISOString(),
});
```

#### B. Error Recovery
```typescript
try {
  // File operations
} catch (error) {
  logger.error("[CapacitorPlatformService] Failed to list backup files:", error);
  return [];
}
```

### 8. Security Considerations

#### A. Data Privacy
- Contact data is stored locally on device
- No cloud synchronization of contact data
- User controls visibility settings per contact
- Backup files contain only user-authorized data

#### B. File Access
- Platform-specific permission handling
- User choice for file locations
- Secure storage options for sensitive data
- Proper error handling for access failures

### 9. Performance Optimizations

#### A. Database Operations
- Indexed queries on `did` and `name` fields
- Batch operations for multiple contacts
- Efficient JSON serialization/deserialization
- Connection pooling and reuse

#### B. File Operations
- Asynchronous file I/O
- Efficient file discovery algorithms
- Caching of file lists
- Background refresh operations

## Summary

The TimeSafari contact backup system provides:

1. **Robust Data Storage**: SQLite-based contact storage with proper indexing
2. **Cross-Platform Compatibility**: Works on web, iOS, Android, and desktop
3. **Flexible Export Options**: Multiple file formats and storage locations
4. **Intelligent File Discovery**: Finds backup files regardless of user-chosen locations
5. **User-Friendly Interface**: Clear categorization and easy file management
6. **Comprehensive Logging**: Detailed tracking for debugging and monitoring
7. **Security-First Design**: Privacy-preserving with user-controlled data access

The system ensures that users can reliably backup and restore their contact data across different platforms while maintaining data integrity and user privacy.