Compare commits
11 Commits
activedid_
...
capacitor-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1b6add178 | ||
|
|
d3a26a54d4 | ||
|
|
122b5b1a06 | ||
|
|
9e8f08aa49 | ||
|
|
1529cc9689 | ||
|
|
f7ed05d13f | ||
|
|
7c8a6d0666 | ||
|
|
1aa285be55 | ||
|
|
2635c22c33 | ||
|
|
2d516b90b0 | ||
|
|
7a1329e1a4 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -21,6 +21,7 @@ npm-debug.log*
|
|||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
|
android/app/src/main/res/
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.idea
|
.idea
|
||||||
|
|||||||
533
CONTACT_BACKUP_SYSTEM.md
Normal file
533
CONTACT_BACKUP_SYSTEM.md
Normal file
@@ -0,0 +1,533 @@
|
|||||||
|
# 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.
|
||||||
567
package-lock.json
generated
567
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "timesafari",
|
"name": "timesafari",
|
||||||
"version": "0.4.8",
|
"version": "0.5.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "timesafari",
|
"name": "timesafari",
|
||||||
"version": "0.4.8",
|
"version": "0.5.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor-community/sqlite": "6.0.2",
|
"@capacitor-community/sqlite": "6.0.2",
|
||||||
"@capacitor-mlkit/barcode-scanning": "^6.0.0",
|
"@capacitor-mlkit/barcode-scanning": "^6.0.0",
|
||||||
@@ -3835,9 +3835,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@electron/asar/node_modules/brace-expansion": {
|
"node_modules/@electron/asar/node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4524,9 +4524,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
|
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -6686,9 +6686,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
|
"node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -6983,6 +6983,29 @@
|
|||||||
"integrity": "sha512-meL9DERHj+fFVWoOX9fXqfcYcSpUfSYJPcFvDPKrxitICbwAoWR+Ut4j5NO9zAT917HUHLQmqzQbAsGNHlDcxQ==",
|
"integrity": "sha512-meL9DERHj+fFVWoOX9fXqfcYcSpUfSYJPcFvDPKrxitICbwAoWR+Ut4j5NO9zAT917HUHLQmqzQbAsGNHlDcxQ==",
|
||||||
"license": "Apache-2.0 OR MIT"
|
"license": "Apache-2.0 OR MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@isaacs/balanced-match": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@isaacs/brace-expansion": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@isaacs/balanced-match": "^4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
@@ -7839,9 +7862,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@react-native/assets-registry": {
|
"node_modules/@react-native/assets-registry": {
|
||||||
"version": "0.79.3",
|
"version": "0.80.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.3.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.80.0.tgz",
|
||||||
"integrity": "sha512-Vy8DQXCJ21YSAiHxrNBz35VqVlZPpRYm50xRTWRf660JwHuJkFQG8cUkrLzm7AUriqUXxwpkQHcY+b0ibw9ejQ==",
|
"integrity": "sha512-MlScsKAz99zoYghe5Rf5mUqsqz2rMB02640NxtPtBMSHNdGxxRlWu/pp1bFexDa1DYJwyIjnLgt3Z/Y90ikHfw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
"peer": true,
|
||||||
@@ -7947,20 +7970,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@react-native/community-cli-plugin": {
|
"node_modules/@react-native/community-cli-plugin": {
|
||||||
"version": "0.79.3",
|
"version": "0.80.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.79.3.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.80.0.tgz",
|
||||||
"integrity": "sha512-N/+p4HQqN4yK6IRzn7OgMvUIcrmEWkecglk1q5nj+AzNpfIOzB+mqR20SYmnPfeXF+mZzYCzRANb3KiM+WsSDA==",
|
"integrity": "sha512-uadfVvzZfz5tGpqwslL12i+rELK9m6cLhtqICX0JQvS7Bu12PJwrozhKzEzIYwN9i3wl2dWrKDUr08izt7S9Iw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-native/dev-middleware": "0.79.3",
|
"@react-native/dev-middleware": "0.80.0",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"debug": "^2.2.0",
|
"debug": "^4.4.0",
|
||||||
"invariant": "^2.2.4",
|
"invariant": "^2.2.4",
|
||||||
"metro": "^0.82.0",
|
"metro": "^0.82.2",
|
||||||
"metro-config": "^0.82.0",
|
"metro-config": "^0.82.2",
|
||||||
"metro-core": "^0.82.0",
|
"metro-core": "^0.82.2",
|
||||||
"semver": "^7.1.3"
|
"semver": "^7.1.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -7975,25 +7998,97 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@react-native/community-cli-plugin/node_modules/debug": {
|
"node_modules/@react-native/community-cli-plugin/node_modules/@react-native/debugger-frontend": {
|
||||||
"version": "2.6.9",
|
"version": "0.80.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.80.0.tgz",
|
||||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
"integrity": "sha512-lpu9Z3xtKUaKFvEcm5HSgo1KGfkDa/W3oZHn22Zy0WQ9MiOu2/ar1txgd1wjkoNiK/NethKcRdCN7mqnc6y2mA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-native/community-cli-plugin/node_modules/@react-native/dev-middleware": {
|
||||||
|
"version": "0.80.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.80.0.tgz",
|
||||||
|
"integrity": "sha512-lLyTnJ687A5jF3fn8yR/undlCis3FG+N/apQ+Q0Lcl+GV6FsZs0U5H28YmL6lZtjOj4TLek6uGPMPmZasHx7cQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "2.0.0"
|
"@isaacs/ttlcache": "^1.4.1",
|
||||||
|
"@react-native/debugger-frontend": "0.80.0",
|
||||||
|
"chrome-launcher": "^0.15.2",
|
||||||
|
"chromium-edge-launcher": "^0.2.0",
|
||||||
|
"connect": "^3.6.5",
|
||||||
|
"debug": "^4.4.0",
|
||||||
|
"invariant": "^2.2.4",
|
||||||
|
"nullthrows": "^1.1.1",
|
||||||
|
"open": "^7.0.3",
|
||||||
|
"serve-static": "^1.16.2",
|
||||||
|
"ws": "^6.2.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-native/community-cli-plugin/node_modules/debug": {
|
||||||
|
"version": "4.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||||
|
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@react-native/community-cli-plugin/node_modules/ms": {
|
"node_modules/@react-native/community-cli-plugin/node_modules/ms": {
|
||||||
"version": "2.0.0",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-native/community-cli-plugin/node_modules/open": {
|
||||||
|
"version": "7.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
|
||||||
|
"integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"is-docker": "^2.0.0",
|
||||||
|
"is-wsl": "^2.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-native/community-cli-plugin/node_modules/ws": {
|
||||||
|
"version": "6.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
|
||||||
|
"integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"async-limiter": "~1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@react-native/debugger-frontend": {
|
"node_modules/@react-native/debugger-frontend": {
|
||||||
"version": "0.79.3",
|
"version": "0.79.3",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.79.3.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.79.3.tgz",
|
||||||
@@ -8078,9 +8173,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@react-native/gradle-plugin": {
|
"node_modules/@react-native/gradle-plugin": {
|
||||||
"version": "0.79.3",
|
"version": "0.80.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.79.3.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.80.0.tgz",
|
||||||
"integrity": "sha512-imfpZLhNBc9UFSzb/MOy2tNcIBHqVmexh/qdzw83F75BmUtLb/Gs1L2V5gw+WI1r7RqDILbWk7gXB8zUllwd+g==",
|
"integrity": "sha512-drmS68rabSMOuDD+YsAY2luNT8br82ycodSDORDqAg7yWQcieHMp4ZUOcdOi5iW+JCqobablT/b6qxcrBg+RaA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
"peer": true,
|
||||||
@@ -8089,9 +8184,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@react-native/js-polyfills": {
|
"node_modules/@react-native/js-polyfills": {
|
||||||
"version": "0.79.3",
|
"version": "0.80.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.79.3.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.80.0.tgz",
|
||||||
"integrity": "sha512-PEBtg6Kox6KahjCAch0UrqCAmHiNLEbp2SblUEoFAQnov4DSxBN9safh+QSVaCiMAwLjvNfXrJyygZz60Dqz3Q==",
|
"integrity": "sha512-dMX7IcBuwghySTgIeK8q03tYz/epg5ScGmJEfBQAciuhzMDMV1LBR/9wwdgD73EXM/133yC5A+TlHb3KQil4Ew==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
"peer": true,
|
||||||
@@ -8108,9 +8203,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@react-native/virtualized-lists": {
|
"node_modules/@react-native/virtualized-lists": {
|
||||||
"version": "0.79.3",
|
"version": "0.80.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.79.3.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.80.0.tgz",
|
||||||
"integrity": "sha512-/0rRozkn+iIHya2vnnvprDgT7QkfI54FLrACAN3BLP7MRlfOIGOrZsXpRLndnLBVnjNzkcre84i1RecjoXnwIA==",
|
"integrity": "sha512-d9zZdPS/ZRexVAkxo1eRp85U7XnnEpXA1ZpSomRKxBuStYKky1YohfEX5YD5MhphemKK24tT7JR4UhaLlmeX8Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
"peer": true,
|
||||||
@@ -8217,9 +8312,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.43.0.tgz",
|
||||||
"integrity": "sha512-gldmAyS9hpj+H6LpRNlcjQWbuKUtb94lodB9uCz71Jm+7BxK1VIOo7y62tZZwxhA7j1ylv/yQz080L5WkS+LoQ==",
|
"integrity": "sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -8231,9 +8326,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.43.0.tgz",
|
||||||
"integrity": "sha512-bpRipfTgmGFdCZDFLRvIkSNO1/3RGS74aWkJJTFJBH7h3MRV4UijkaEUeOMbi9wxtxYmtAbVcnMtHTPBhLEkaw==",
|
"integrity": "sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -8271,9 +8366,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.43.0.tgz",
|
||||||
"integrity": "sha512-fJcN4uSGPWdpVmvLuMtALUFwCHgb2XiQjuECkHT3lWLZhSQ3MBQ9pq+WoWeJq2PrNxr9rPM1Qx+IjyGj8/c6zQ==",
|
"integrity": "sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -8285,9 +8380,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.43.0.tgz",
|
||||||
"integrity": "sha512-CziHfyzpp8hJpCVE/ZdTizw58gr+m7Y2Xq5VOuCSrZR++th2xWAz4Nqk52MoIIrV3JHtVBhbBsJcAxs6NammOQ==",
|
"integrity": "sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -8299,9 +8394,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.43.0.tgz",
|
||||||
"integrity": "sha512-UsQD5fyLWm2Fe5CDM7VPYAo+UC7+2Px4Y+N3AcPh/LdZu23YcuGPegQly++XEVaC8XUTFVPscl5y5Cl1twEI4A==",
|
"integrity": "sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -8313,9 +8408,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.43.0.tgz",
|
||||||
"integrity": "sha512-/i8NIrlgc/+4n1lnoWl1zgH7Uo0XK5xK3EDqVTf38KvyYgCU/Rm04+o1VvvzJZnVS5/cWSd07owkzcVasgfIkQ==",
|
"integrity": "sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -8353,9 +8448,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.43.0.tgz",
|
||||||
"integrity": "sha512-O8AplvIeavK5ABmZlKBq9/STdZlnQo7Sle0LLhVA7QT+CiGpNVe197/t8Aph9bhJqbDVGCHpY2i7QyfEDDStDg==",
|
"integrity": "sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -8367,9 +8462,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.43.0.tgz",
|
||||||
"integrity": "sha512-6Qb66tbKVN7VyQrekhEzbHRxXXFFD8QKiFAwX5v9Xt6FiJ3BnCVBuyBxa2fkFGqxOCSGGYNejxd8ht+q5SnmtA==",
|
"integrity": "sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -8381,9 +8476,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.43.0.tgz",
|
||||||
"integrity": "sha512-KQETDSEBamQFvg/d8jajtRwLNBlGc3aKpaGiP/LvEbnmVUKlFta1vqJqTrvPtsYsfbE/DLg5CC9zyXRX3fnBiA==",
|
"integrity": "sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -8395,9 +8490,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.43.0.tgz",
|
||||||
"integrity": "sha512-qMvnyjcU37sCo/tuC+JqeDKSuukGAd+pVlRl/oyDbkvPJ3awk6G6ua7tyum02O3lI+fio+eM5wsVd66X0jQtxw==",
|
"integrity": "sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -8409,9 +8504,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.43.0.tgz",
|
||||||
"integrity": "sha512-I2Y1ZUgTgU2RLddUHXTIgyrdOwljjkmcZ/VilvaEumtS3Fkuhbw4p4hgHc39Ypwvo2o7sBFNl2MquNvGCa55Iw==",
|
"integrity": "sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -8462,9 +8557,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.43.0.tgz",
|
||||||
"integrity": "sha512-F+5J9pelstXKwRSDq92J0TEBXn2nfUrQGg+HK1+Tk7VOL09e0gBqUHugZv7SW4MGrYj41oNCUe3IKCDGVlis2g==",
|
"integrity": "sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -8874,9 +8969,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@stencil/core": {
|
"node_modules/@stencil/core": {
|
||||||
"version": "4.33.1",
|
"version": "4.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.33.1.tgz",
|
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.35.0.tgz",
|
||||||
"integrity": "sha512-12k9xhAJBkpg598it+NRmaYIdEe6TSnsL/v6/KRXDcUyTK11VYwZQej2eHnMWtqot+znJ+GNTqb5YbiXi+5Low==",
|
"integrity": "sha512-x0IFtj7IJStK+ZqIkhReWbiC0UMjMJnNXV8OXG+DCLDExZaVaxL3MLuq6BJBBcQ1MHZduTHDv3Iz0Zshoj3zjQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"stencil": "bin/stencil"
|
"stencil": "bin/stencil"
|
||||||
@@ -11184,13 +11279,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/app-builder-lib/node_modules/minimatch": {
|
"node_modules/app-builder-lib/node_modules/minimatch": {
|
||||||
"version": "10.0.1",
|
"version": "10.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
|
||||||
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
|
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^2.0.1"
|
"@isaacs/brace-expansion": "^5.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "20 || >=22"
|
"node": "20 || >=22"
|
||||||
@@ -12199,9 +12294,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0"
|
"balanced-match": "^1.0.0"
|
||||||
@@ -12648,9 +12743,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cacache/node_modules/rimraf/node_modules/brace-expansion": {
|
"node_modules/cacache/node_modules/rimraf/node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -12868,9 +12963,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001721",
|
"version": "1.0.30001723",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz",
|
||||||
"integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==",
|
"integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -14931,9 +15026,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dir-compare/node_modules/brace-expansion": {
|
"node_modules/dir-compare/node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -15347,9 +15442,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.166",
|
"version": "1.5.167",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.167.tgz",
|
||||||
"integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==",
|
"integrity": "sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
@@ -15865,9 +15960,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint/node_modules/brace-expansion": {
|
"node_modules/eslint/node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -16079,9 +16174,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ethers": {
|
"node_modules/ethers": {
|
||||||
"version": "6.14.3",
|
"version": "6.14.4",
|
||||||
"resolved": "https://registry.npmjs.org/ethers/-/ethers-6.14.3.tgz",
|
"resolved": "https://registry.npmjs.org/ethers/-/ethers-6.14.4.tgz",
|
||||||
"integrity": "sha512-qq7ft/oCJohoTcsNPFaXSQUm457MA5iWqkf1Mb11ujONdg7jBI6sAOrHaTi3j0CBqIGFSCeR/RMc+qwRRub7IA==",
|
"integrity": "sha512-Jm/dzRs2Z9iBrT6e9TvGxyb5YVKAPLlpna7hjxH7KH/++DSh2T/JVmQUv7iHI5E55hDbp/gEVvstWYXVxXFzsA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
@@ -16178,21 +16273,21 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/ethr-did": {
|
"node_modules/ethr-did": {
|
||||||
"version": "3.0.37",
|
"version": "3.0.38",
|
||||||
"resolved": "https://registry.npmjs.org/ethr-did/-/ethr-did-3.0.37.tgz",
|
"resolved": "https://registry.npmjs.org/ethr-did/-/ethr-did-3.0.38.tgz",
|
||||||
"integrity": "sha512-L9UUhAS8B1T7jTRdKLwAt514lx2UrJebJK7uc6UU4AJ9RhY8Vcfwc93Ux82jREE7yvvqDPXsVNH+lS3aw18a9A==",
|
"integrity": "sha512-gUxtErXVOQUJf+bmnxRdSJdlU9aFbQSBNaJCYGt+PLqw6l4qqInTfMRiWpwe/brhRtdjE+64tnayOVk8ataeQA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"did-jwt": "^8.0.0",
|
"did-jwt": "^8.0.0",
|
||||||
"did-resolver": "^4.1.0",
|
"did-resolver": "^4.1.0",
|
||||||
"ethers": "^6.8.1",
|
"ethers": "^6.8.1",
|
||||||
"ethr-did-resolver": "11.0.3"
|
"ethr-did-resolver": "11.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ethr-did-resolver": {
|
"node_modules/ethr-did-resolver": {
|
||||||
"version": "11.0.3",
|
"version": "11.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/ethr-did-resolver/-/ethr-did-resolver-11.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/ethr-did-resolver/-/ethr-did-resolver-11.0.4.tgz",
|
||||||
"integrity": "sha512-lQ1T/SZfgR6Kp05/GSIXnMELxQ5H6M6OCTH4wBTVSAgHzbJiDNVIYWzg/c+NniIM88B0ViAi4CaiCHaiUlvPQg==",
|
"integrity": "sha512-EJ/dL2QsFzvhBJd0nlPFjma3bxpQOWyp2TytQZyAeqi6SfZ4ALCB0VaA4dSeT4T8ZtI2pzs/sD7t/7A0584J6Q==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"did-resolver": "^4.1.0",
|
"did-resolver": "^4.1.0",
|
||||||
@@ -17617,9 +17712,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/glob/node_modules/brace-expansion": {
|
"node_modules/glob/node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -19127,9 +19222,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jake/node_modules/brace-expansion": {
|
"node_modules/jake/node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -24054,9 +24149,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.4",
|
"version": "8.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz",
|
||||||
"integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==",
|
"integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -24954,44 +25049,43 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/react-native": {
|
"node_modules/react-native": {
|
||||||
"version": "0.79.3",
|
"version": "0.80.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.79.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.80.0.tgz",
|
||||||
"integrity": "sha512-EzH1+9gzdyEo9zdP6u7Sh3Jtf5EOMwzy+TK65JysdlgAzfEVfq4mNeXcAZ6SmD+CW6M7ARJbvXLyTD0l2S5rpg==",
|
"integrity": "sha512-b9K1ygb2MWCBtKAodKmE3UsbUuC29Pt4CrJMR0ocTA8k+8HJQTPleBPDNKL4/p0P01QO9aL/gZUddoxHempLow==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jest/create-cache-key-function": "^29.7.0",
|
"@jest/create-cache-key-function": "^29.7.0",
|
||||||
"@react-native/assets-registry": "0.79.3",
|
"@react-native/assets-registry": "0.80.0",
|
||||||
"@react-native/codegen": "0.79.3",
|
"@react-native/codegen": "0.80.0",
|
||||||
"@react-native/community-cli-plugin": "0.79.3",
|
"@react-native/community-cli-plugin": "0.80.0",
|
||||||
"@react-native/gradle-plugin": "0.79.3",
|
"@react-native/gradle-plugin": "0.80.0",
|
||||||
"@react-native/js-polyfills": "0.79.3",
|
"@react-native/js-polyfills": "0.80.0",
|
||||||
"@react-native/normalize-colors": "0.79.3",
|
"@react-native/normalize-colors": "0.80.0",
|
||||||
"@react-native/virtualized-lists": "0.79.3",
|
"@react-native/virtualized-lists": "0.80.0",
|
||||||
"abort-controller": "^3.0.0",
|
"abort-controller": "^3.0.0",
|
||||||
"anser": "^1.4.9",
|
"anser": "^1.4.9",
|
||||||
"ansi-regex": "^5.0.0",
|
"ansi-regex": "^5.0.0",
|
||||||
"babel-jest": "^29.7.0",
|
"babel-jest": "^29.7.0",
|
||||||
"babel-plugin-syntax-hermes-parser": "0.25.1",
|
"babel-plugin-syntax-hermes-parser": "0.28.1",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"commander": "^12.0.0",
|
"commander": "^12.0.0",
|
||||||
"event-target-shim": "^5.0.1",
|
|
||||||
"flow-enums-runtime": "^0.0.6",
|
"flow-enums-runtime": "^0.0.6",
|
||||||
"glob": "^7.1.1",
|
"glob": "^7.1.1",
|
||||||
"invariant": "^2.2.4",
|
"invariant": "^2.2.4",
|
||||||
"jest-environment-node": "^29.7.0",
|
"jest-environment-node": "^29.7.0",
|
||||||
"memoize-one": "^5.0.0",
|
"memoize-one": "^5.0.0",
|
||||||
"metro-runtime": "^0.82.0",
|
"metro-runtime": "^0.82.2",
|
||||||
"metro-source-map": "^0.82.0",
|
"metro-source-map": "^0.82.2",
|
||||||
"nullthrows": "^1.1.1",
|
"nullthrows": "^1.1.1",
|
||||||
"pretty-format": "^29.7.0",
|
"pretty-format": "^29.7.0",
|
||||||
"promise": "^8.3.0",
|
"promise": "^8.3.0",
|
||||||
"react-devtools-core": "^6.1.1",
|
"react-devtools-core": "^6.1.1",
|
||||||
"react-refresh": "^0.14.0",
|
"react-refresh": "^0.14.0",
|
||||||
"regenerator-runtime": "^0.13.2",
|
"regenerator-runtime": "^0.13.2",
|
||||||
"scheduler": "0.25.0",
|
"scheduler": "0.26.0",
|
||||||
"semver": "^7.1.3",
|
"semver": "^7.1.3",
|
||||||
"stacktrace-parser": "^0.1.10",
|
"stacktrace-parser": "^0.1.10",
|
||||||
"whatwg-fetch": "^3.0.0",
|
"whatwg-fetch": "^3.0.0",
|
||||||
@@ -25005,8 +25099,8 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.1.0",
|
||||||
"react": "^19.0.0"
|
"react": "^19.1.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@types/react": {
|
"@types/react": {
|
||||||
@@ -25039,14 +25133,46 @@
|
|||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native/node_modules/@react-native/codegen": {
|
||||||
|
"version": "0.80.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.80.0.tgz",
|
||||||
|
"integrity": "sha512-X9TsPgytoUkNrQjzAZh4dXa4AuouvYT0NzYyvnjw1ry4LESCZtKba+eY4x3+M30WPR52zjgu+UFL//14BSdCCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"glob": "^7.1.1",
|
||||||
|
"hermes-parser": "0.28.1",
|
||||||
|
"invariant": "^2.2.4",
|
||||||
|
"nullthrows": "^1.1.1",
|
||||||
|
"yargs": "^17.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/core": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-native/node_modules/@react-native/normalize-colors": {
|
"node_modules/react-native/node_modules/@react-native/normalize-colors": {
|
||||||
"version": "0.79.3",
|
"version": "0.80.0",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.79.3.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.80.0.tgz",
|
||||||
"integrity": "sha512-T75NIQPRFCj6DFMxtcVMJTZR+3vHXaUMSd15t+CkJpc5LnyX91GVaPxpRSAdjFh7m3Yppl5MpdjV/fntImheYQ==",
|
"integrity": "sha512-bJZDSopadjJxMDvysc634eTfLL4w7cAx5diPe14Ez5l+xcKjvpfofS/1Ja14DlgdMJhxGd03MTXlrxoWust3zg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native/node_modules/babel-plugin-syntax-hermes-parser": {
|
||||||
|
"version": "0.28.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.28.1.tgz",
|
||||||
|
"integrity": "sha512-meT17DOuUElMNsL5LZN56d+KBp22hb0EfxWfuPUeoSi54e40v1W4C2V36P75FpsH9fVEfDKpw5Nnkahc8haSsQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"hermes-parser": "0.28.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-native/node_modules/commander": {
|
"node_modules/react-native/node_modules/commander": {
|
||||||
"version": "12.1.0",
|
"version": "12.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||||
@@ -25058,6 +25184,25 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native/node_modules/hermes-estree": {
|
||||||
|
"version": "0.28.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.28.1.tgz",
|
||||||
|
"integrity": "sha512-w3nxl/RGM7LBae0v8LH2o36+8VqwOZGv9rX1wyoWT6YaKZLqpJZ0YQ5P0LVr3tuRpf7vCx0iIG4i/VmBJejxTQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/react-native/node_modules/hermes-parser": {
|
||||||
|
"version": "0.28.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.28.1.tgz",
|
||||||
|
"integrity": "sha512-nf8o+hE8g7UJWParnccljHumE9Vlq8F7MqIdeahl+4x0tvCUJYRrT0L7h0MMg/X9YJmkNwsfbaNNrzPtFXOscg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"hermes-estree": "0.28.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-native/node_modules/ws": {
|
"node_modules/react-native/node_modules/ws": {
|
||||||
"version": "6.2.3",
|
"version": "6.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
|
||||||
@@ -25514,9 +25659,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/replace/node_modules/brace-expansion": {
|
"node_modules/replace/node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -25960,15 +26105,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rimraf/node_modules/glob": {
|
"node_modules/rimraf/node_modules/glob": {
|
||||||
"version": "11.0.2",
|
"version": "11.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
|
||||||
"integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==",
|
"integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"foreground-child": "^3.1.0",
|
"foreground-child": "^3.3.1",
|
||||||
"jackspeak": "^4.0.1",
|
"jackspeak": "^4.1.1",
|
||||||
"minimatch": "^10.0.0",
|
"minimatch": "^10.0.3",
|
||||||
"minipass": "^7.1.2",
|
"minipass": "^7.1.2",
|
||||||
"package-json-from-dist": "^1.0.0",
|
"package-json-from-dist": "^1.0.0",
|
||||||
"path-scurry": "^2.0.0"
|
"path-scurry": "^2.0.0"
|
||||||
@@ -26010,13 +26155,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rimraf/node_modules/minimatch": {
|
"node_modules/rimraf/node_modules/minimatch": {
|
||||||
"version": "10.0.1",
|
"version": "10.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
|
||||||
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
|
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^2.0.1"
|
"@isaacs/brace-expansion": "^5.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "20 || >=22"
|
"node": "20 || >=22"
|
||||||
@@ -26094,9 +26239,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.43.0.tgz",
|
||||||
"integrity": "sha512-LW+Vse3BJPyGJGAJt1j8pWDKPd73QM8cRXYK1IxOBgL2AGLu7Xd2YOW0M2sLUBCkF5MshXXtMApyEAEzMVMsnw==",
|
"integrity": "sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -26110,33 +26255,33 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.42.0",
|
"@rollup/rollup-android-arm-eabi": "4.43.0",
|
||||||
"@rollup/rollup-android-arm64": "4.42.0",
|
"@rollup/rollup-android-arm64": "4.43.0",
|
||||||
"@rollup/rollup-darwin-arm64": "4.42.0",
|
"@rollup/rollup-darwin-arm64": "4.43.0",
|
||||||
"@rollup/rollup-darwin-x64": "4.42.0",
|
"@rollup/rollup-darwin-x64": "4.43.0",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.42.0",
|
"@rollup/rollup-freebsd-arm64": "4.43.0",
|
||||||
"@rollup/rollup-freebsd-x64": "4.42.0",
|
"@rollup/rollup-freebsd-x64": "4.43.0",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.42.0",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.43.0",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.42.0",
|
"@rollup/rollup-linux-arm-musleabihf": "4.43.0",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.42.0",
|
"@rollup/rollup-linux-arm64-gnu": "4.43.0",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.42.0",
|
"@rollup/rollup-linux-arm64-musl": "4.43.0",
|
||||||
"@rollup/rollup-linux-loongarch64-gnu": "4.42.0",
|
"@rollup/rollup-linux-loongarch64-gnu": "4.43.0",
|
||||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.42.0",
|
"@rollup/rollup-linux-powerpc64le-gnu": "4.43.0",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.42.0",
|
"@rollup/rollup-linux-riscv64-gnu": "4.43.0",
|
||||||
"@rollup/rollup-linux-riscv64-musl": "4.42.0",
|
"@rollup/rollup-linux-riscv64-musl": "4.43.0",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.42.0",
|
"@rollup/rollup-linux-s390x-gnu": "4.43.0",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.42.0",
|
"@rollup/rollup-linux-x64-gnu": "4.43.0",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.42.0",
|
"@rollup/rollup-linux-x64-musl": "4.43.0",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.42.0",
|
"@rollup/rollup-win32-arm64-msvc": "4.43.0",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.42.0",
|
"@rollup/rollup-win32-ia32-msvc": "4.43.0",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.42.0",
|
"@rollup/rollup-win32-x64-msvc": "4.43.0",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup/node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/rollup/node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.43.0.tgz",
|
||||||
"integrity": "sha512-JxHtA081izPBVCHLKnl6GEA0w3920mlJPLh89NojpU2GsBSB6ypu4erFg/Wx1qbpUbepn0jY4dVWMGZM8gplgA==",
|
"integrity": "sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -26148,9 +26293,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/rollup/node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/rollup/node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.43.0.tgz",
|
||||||
"integrity": "sha512-rv5UZaWVIJTDMyQ3dCEK+m0SAn6G7H3PRc2AZmExvbDvtaDc+qXkei0knQWcI3+c9tEs7iL/4I4pTQoPbNL2SA==",
|
"integrity": "sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -26162,9 +26307,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.43.0.tgz",
|
||||||
"integrity": "sha512-eoujJFOvoIBjZEi9hJnXAbWg+Vo1Ov8n/0IKZZcPZ7JhBzxh2A+2NFyeMZIRkY9iwBvSjloKgcvnjTbGKHE44Q==",
|
"integrity": "sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -26176,9 +26321,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.43.0.tgz",
|
||||||
"integrity": "sha512-/3NrcOWFSR7RQUQIuZQChLND36aTU9IYE4j+TB40VU78S+RA0IiqHR30oSh6P1S9f9/wVOenHQnacs/Byb824g==",
|
"integrity": "sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -26190,9 +26335,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.43.0.tgz",
|
||||||
"integrity": "sha512-Gfm6cV6mj3hCUY8TqWa63DB8Mx3NADoFwiJrMpoZ1uESbK8FQV3LXkhfry+8bOniq9pqY1OdsjFWNsSbfjPugw==",
|
"integrity": "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -26204,9 +26349,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.43.0.tgz",
|
||||||
"integrity": "sha512-g86PF8YZ9GRqkdi0VoGlcDUb4rYtQKyTD1IVtxxN4Hpe7YqLBShA7oHMKU6oKTCi3uxwW4VkIGnOaH/El8de3w==",
|
"integrity": "sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -26218,9 +26363,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/rollup/node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/rollup/node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.43.0.tgz",
|
||||||
"integrity": "sha512-+axkdyDGSp6hjyzQ5m1pgcvQScfHnMCcsXkx8pTgy/6qBmWVhtRVlgxjWwDp67wEXXUr0x+vD6tp5W4x6V7u1A==",
|
"integrity": "sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -26232,9 +26377,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/rollup/node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/rollup/node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.42.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.42.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.43.0.tgz",
|
||||||
"integrity": "sha512-LpHiJRwkaVz/LqjHjK8LCi8osq7elmpwujwbXKNW88bM8eeGxavJIKKjkjpMHAh/2xfnrt1ZSnhTv41WYUHYmA==",
|
"integrity": "sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -26410,9 +26555,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/scheduler": {
|
"node_modules/scheduler": {
|
||||||
"version": "0.25.0",
|
"version": "0.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
|
||||||
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
|
"integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true
|
"peer": true
|
||||||
@@ -26424,9 +26569,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/sdp": {
|
"node_modules/sdp": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.1.tgz",
|
||||||
"integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==",
|
"integrity": "sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/secp256k1": {
|
"node_modules/secp256k1": {
|
||||||
@@ -28495,9 +28640,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/test-exclude/node_modules/brace-expansion": {
|
"node_modules/test-exclude/node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
"peer": true,
|
||||||
@@ -31045,9 +31190,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "3.25.58",
|
"version": "3.25.64",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.58.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.64.tgz",
|
||||||
"integrity": "sha512-DVLmMQzSZwNYzQoMaM3MQWnxr2eq+AtM9Hx3w1/Yl0pH8sLTSjN4jGP7w6f7uand6Hw44tsnSu1hz1AOA6qI2Q==",
|
"integrity": "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
|||||||
894
src/components/BackupFilesList.vue
Normal file
894
src/components/BackupFilesList.vue
Normal file
@@ -0,0 +1,894 @@
|
|||||||
|
/** * 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 *
|
||||||
|
<BackupFilesList />
|
||||||
|
* ``` */
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="backup-files-list">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h3 class="text-lg font-semibold">Backup Files</h3>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button
|
||||||
|
v-if="platformCapabilities.hasFileSystem"
|
||||||
|
class="text-sm bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded"
|
||||||
|
:disabled="isLoading"
|
||||||
|
@click="refreshFiles()"
|
||||||
|
>
|
||||||
|
<font-awesome
|
||||||
|
icon="refresh"
|
||||||
|
class="fa-fw"
|
||||||
|
:class="{ 'animate-spin': isLoading }"
|
||||||
|
/>
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="platformCapabilities.hasFileSystem"
|
||||||
|
class="text-sm bg-green-500 hover:bg-green-600 text-white px-3 py-1 rounded"
|
||||||
|
:disabled="isLoading"
|
||||||
|
@click="openBackupDirectory()"
|
||||||
|
>
|
||||||
|
<font-awesome icon="folder-open" class="fa-fw" />
|
||||||
|
Open Directory
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="platformCapabilities.hasFileSystem && isDevelopment"
|
||||||
|
class="text-sm bg-yellow-500 hover:bg-yellow-600 text-white px-3 py-1 rounded"
|
||||||
|
:disabled="isLoading"
|
||||||
|
title="Debug file discovery (development only)"
|
||||||
|
@click="debugFileDiscovery()"
|
||||||
|
>
|
||||||
|
<font-awesome icon="bug" class="fa-fw" />
|
||||||
|
Debug
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:disabled="isLoading"
|
||||||
|
class="px-3 py-1 bg-green-500 text-white rounded text-sm hover:bg-green-600 disabled:opacity-50"
|
||||||
|
@click="createTestBackup"
|
||||||
|
>
|
||||||
|
Create Test Backup
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:disabled="isLoading"
|
||||||
|
class="px-3 py-1 bg-purple-500 text-white rounded text-sm hover:bg-purple-600 disabled:opacity-50"
|
||||||
|
@click="testDirectoryContexts"
|
||||||
|
>
|
||||||
|
Test Contexts
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isLoading" class="text-center py-4">
|
||||||
|
<font-awesome icon="spinner" class="animate-spin fa-2x" />
|
||||||
|
<p class="mt-2">Loading backup files...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else-if="backupFiles.length === 0"
|
||||||
|
class="text-center py-4 text-gray-500"
|
||||||
|
>
|
||||||
|
<font-awesome icon="folder-open" class="fa-2x mb-2" />
|
||||||
|
<p>No backup files found</p>
|
||||||
|
<p class="text-sm mt-1">
|
||||||
|
Create backups using the export functions above
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="mt-3 p-3 bg-blue-50 border border-blue-200 rounded-lg text-left"
|
||||||
|
>
|
||||||
|
<p class="text-sm font-medium text-blue-800 mb-2">
|
||||||
|
💡 How to create backup files:
|
||||||
|
</p>
|
||||||
|
<ul class="text-xs text-blue-700 space-y-1">
|
||||||
|
<li>
|
||||||
|
• Use the "Export Contacts" button above to create contact backups
|
||||||
|
</li>
|
||||||
|
<li>• Use the "Export Seed" button to backup your recovery phrase</li>
|
||||||
|
<li>
|
||||||
|
• Backup files are saved to persistent storage that survives app
|
||||||
|
installations
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="platformCapabilities.isMobile && !platformCapabilities.isIOS"
|
||||||
|
class="text-orange-700"
|
||||||
|
>
|
||||||
|
• On Android: Files are saved to Downloads/TimeSafari or app data
|
||||||
|
directory
|
||||||
|
</li>
|
||||||
|
<li v-if="platformCapabilities.isIOS" class="text-orange-700">
|
||||||
|
• On iOS: Files are saved to Documents folder (accessible via Files
|
||||||
|
app)
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="space-y-2">
|
||||||
|
<!-- File Type Filter -->
|
||||||
|
<div class="flex gap-2 mb-3">
|
||||||
|
<button
|
||||||
|
v-for="type in ['all', 'contacts', 'seed', 'other'] as const"
|
||||||
|
:key="type"
|
||||||
|
:class="[
|
||||||
|
'text-sm px-3 py-1 rounded border',
|
||||||
|
selectedType === type
|
||||||
|
? 'bg-blue-500 text-white border-blue-500'
|
||||||
|
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50',
|
||||||
|
]"
|
||||||
|
@click="selectedType = type"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
type === "all"
|
||||||
|
? "All"
|
||||||
|
: type.charAt(0).toUpperCase() + type.slice(1)
|
||||||
|
}}
|
||||||
|
<span class="ml-1 text-xs"> ({{ getFileCountByType(type) }}) </span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Files List -->
|
||||||
|
<div class="flex items-center gap-2 mb-2">
|
||||||
|
<span v-for="(crumb, idx) in breadcrumbs" :key="idx">
|
||||||
|
<span
|
||||||
|
v-if="idx < breadcrumbs.length - 1"
|
||||||
|
class="text-blue-600 cursor-pointer underline"
|
||||||
|
@click="goToBreadcrumb(idx)"
|
||||||
|
>
|
||||||
|
{{ crumb }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="font-bold">{{ crumb }}</span>
|
||||||
|
<span v-if="idx < breadcrumbs.length - 1"> / </span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="currentPath.length > 1" class="mb-2">
|
||||||
|
<button class="text-xs text-blue-500 underline" @click="goUp">
|
||||||
|
⬅ Up
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="inline-flex items-center">
|
||||||
|
<input
|
||||||
|
v-model="debugShowAll"
|
||||||
|
type="checkbox"
|
||||||
|
class="mr-2"
|
||||||
|
@change="loadDirectory"
|
||||||
|
/>
|
||||||
|
<span class="text-xs">Debug: Show all entries as files</span>
|
||||||
|
</label>
|
||||||
|
<span v-if="debugShowAll" class="text-xs text-red-600 ml-2"
|
||||||
|
>[Debug mode: forcibly treating all entries as files]</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2 max-h-64 overflow-y-auto">
|
||||||
|
<div
|
||||||
|
v-for="entry in folders"
|
||||||
|
:key="'folder-' + entry.path"
|
||||||
|
class="flex items-center justify-between p-3 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer"
|
||||||
|
@click="openFolder(entry)"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<font-awesome icon="folder" class="fa-fw text-yellow-500" />
|
||||||
|
<span class="font-medium">{{ entry.name }}</span>
|
||||||
|
<span
|
||||||
|
class="text-xs bg-gray-200 text-gray-700 px-2 py-0.5 rounded-full ml-2"
|
||||||
|
>Folder</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="entry in files"
|
||||||
|
:key="'file-' + entry.path"
|
||||||
|
class="flex items-center justify-between p-3 bg-white border border-gray-200 rounded-lg hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<font-awesome icon="file-alt" class="fa-fw text-gray-500" />
|
||||||
|
<span class="font-medium truncate">{{ entry.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-500 mt-1">
|
||||||
|
<span v-if="entry.size">{{ formatFileSize(entry.size) }}</span>
|
||||||
|
<span v-else>Size unknown</span>
|
||||||
|
<span
|
||||||
|
v-if="entry.path && !platformCapabilities.isIOS"
|
||||||
|
class="ml-2 text-xs text-blue-600"
|
||||||
|
>📁 {{ entry.path }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 ml-3">
|
||||||
|
<button
|
||||||
|
class="text-blue-500 hover:text-blue-700 p-1"
|
||||||
|
title="Open file"
|
||||||
|
@click="openFile(entry.uri, entry.name)"
|
||||||
|
>
|
||||||
|
<font-awesome icon="external-link-alt" class="fa-fw" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Summary -->
|
||||||
|
<div class="text-sm text-gray-500 mt-3 pt-3 border-t">
|
||||||
|
Showing {{ filteredFiles.length }} of {{ backupFiles.length }} backup
|
||||||
|
files
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-sm text-gray-600 mb-2">
|
||||||
|
<p>
|
||||||
|
📁 Backup files are saved to persistent storage that survives app
|
||||||
|
installations:
|
||||||
|
</p>
|
||||||
|
<ul class="list-disc list-inside ml-2 mt-1 text-xs">
|
||||||
|
<li v-if="platformCapabilities.isIOS">
|
||||||
|
iOS: Documents folder (accessible via Files app)
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="platformCapabilities.isMobile && !platformCapabilities.isIOS"
|
||||||
|
>
|
||||||
|
Android: Downloads/TimeSafari or external storage (accessible via
|
||||||
|
file managers)
|
||||||
|
</li>
|
||||||
|
<li v-if="!platformCapabilities.isMobile">
|
||||||
|
Desktop: User's download directory
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Vue, Watch } from "vue-facing-decorator";
|
||||||
|
import { NotificationIface } from "../constants/app";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
|
||||||
|
import {
|
||||||
|
PlatformService,
|
||||||
|
PlatformCapabilities,
|
||||||
|
} from "../services/PlatformService";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @vue-component
|
||||||
|
* Backup Files List Component
|
||||||
|
* Displays and manages backup files with platform-specific functionality
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
export default class BackupFilesList extends Vue {
|
||||||
|
/**
|
||||||
|
* Notification function injected by Vue
|
||||||
|
* Used to show success/error messages to the user
|
||||||
|
*/
|
||||||
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform service instance for platform-specific operations
|
||||||
|
*/
|
||||||
|
private platformService: PlatformService =
|
||||||
|
PlatformServiceFactory.getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform capabilities for the current platform
|
||||||
|
*/
|
||||||
|
private get platformCapabilities(): PlatformCapabilities {
|
||||||
|
return this.platformService.getCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of backup files found on the device
|
||||||
|
*/
|
||||||
|
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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loading state for file operations
|
||||||
|
*/
|
||||||
|
isLoading = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interval for periodic refresh (5 minutes)
|
||||||
|
*/
|
||||||
|
private refreshInterval: number | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current path for folder navigation (array for breadcrumbs)
|
||||||
|
*/
|
||||||
|
currentPath: string[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of files/folders in the current directory
|
||||||
|
*/
|
||||||
|
directoryEntries: Array<{
|
||||||
|
name: string;
|
||||||
|
uri: string;
|
||||||
|
size?: number;
|
||||||
|
path: string;
|
||||||
|
type: "file" | "folder";
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary debug mode to show all entries as files
|
||||||
|
*/
|
||||||
|
debugShowAll = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks and requests storage permissions if needed.
|
||||||
|
* Returns true if permission is granted, false otherwise.
|
||||||
|
*/
|
||||||
|
private async ensureStoragePermission(): Promise<boolean> {
|
||||||
|
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") {
|
||||||
|
try {
|
||||||
|
await platformService.checkStoragePermissions();
|
||||||
|
logger.log("[BackupFilesList] Storage permission granted.");
|
||||||
|
return true;
|
||||||
|
} catch (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"
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
guidance = await platformService.getStoragePermissionGuidance();
|
||||||
|
} catch (guidanceError) {
|
||||||
|
logger.warn(
|
||||||
|
"[BackupFilesList] Could not get permission guidance:",
|
||||||
|
guidanceError,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "warning",
|
||||||
|
title: "Storage Permission Required",
|
||||||
|
text: guidance,
|
||||||
|
},
|
||||||
|
10000, // Show for 10 seconds to give user time to read
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle hook to load backup files when component is mounted
|
||||||
|
*/
|
||||||
|
async mounted() {
|
||||||
|
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 = ["."];
|
||||||
|
} else {
|
||||||
|
this.currentPath = ["Download", "TimeSafari"];
|
||||||
|
}
|
||||||
|
await this.loadDirectory();
|
||||||
|
this.refreshInterval = window.setInterval(
|
||||||
|
() => {
|
||||||
|
this.loadDirectory();
|
||||||
|
},
|
||||||
|
5 * 60 * 1000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle hook to clean up resources when component is unmounted
|
||||||
|
*/
|
||||||
|
beforeUnmount() {
|
||||||
|
if (this.refreshInterval) {
|
||||||
|
clearInterval(this.refreshInterval);
|
||||||
|
this.refreshInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed property for filtered files based on selected type
|
||||||
|
* 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);
|
||||||
|
return this.backupFiles;
|
||||||
|
}
|
||||||
|
const filtered = this.backupFiles.filter(
|
||||||
|
(file) => file.type === this.selectedType,
|
||||||
|
);
|
||||||
|
logger.log(
|
||||||
|
`[BackupFilesList] filteredFiles (${this.selectedType}):`,
|
||||||
|
filtered,
|
||||||
|
);
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed property to check if we're in development mode
|
||||||
|
*/
|
||||||
|
get isDevelopment(): boolean {
|
||||||
|
return import.meta.env.DEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the current directory entries
|
||||||
|
*/
|
||||||
|
async loadDirectory() {
|
||||||
|
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 PlatformService
|
||||||
|
).listFilesInDirectory(path, this.debugShowAll);
|
||||||
|
logger.log("[BackupFilesList] Loaded directory:", {
|
||||||
|
path,
|
||||||
|
entries: this.directoryEntries,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[BackupFilesList] Failed to load directory:", error);
|
||||||
|
this.directoryEntries = [];
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate into a folder
|
||||||
|
*/
|
||||||
|
async openFolder(entry: { name: string; path: string }) {
|
||||||
|
this.currentPath.push(entry.name);
|
||||||
|
await this.loadDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to a breadcrumb
|
||||||
|
*/
|
||||||
|
async goToBreadcrumb(index: number) {
|
||||||
|
this.currentPath = this.currentPath.slice(0, index + 1);
|
||||||
|
await this.loadDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go up one directory
|
||||||
|
*/
|
||||||
|
async goUp() {
|
||||||
|
if (this.currentPath.length > 1) {
|
||||||
|
this.currentPath.pop();
|
||||||
|
await this.loadDirectory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed property for breadcrumbs
|
||||||
|
*/
|
||||||
|
get breadcrumbs() {
|
||||||
|
return this.currentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed property for showing files and folders
|
||||||
|
*/
|
||||||
|
get folders() {
|
||||||
|
return this.directoryEntries.filter((e) => e.type === "folder");
|
||||||
|
}
|
||||||
|
get files() {
|
||||||
|
return this.directoryEntries.filter((e) => e.type === "file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the list of backup files from the device
|
||||||
|
*/
|
||||||
|
async refreshFiles() {
|
||||||
|
logger.log("[BackupFilesList] refreshFiles called.");
|
||||||
|
if (!this.platformCapabilities.hasFileSystem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check/request permission before refreshing
|
||||||
|
const hasPermission = await this.ensureStoragePermission();
|
||||||
|
if (!hasPermission) {
|
||||||
|
this.backupFiles = [];
|
||||||
|
this.isLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isLoading = true;
|
||||||
|
try {
|
||||||
|
this.backupFiles = await this.platformService.listBackupFiles();
|
||||||
|
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(),
|
||||||
|
});
|
||||||
|
// 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,
|
||||||
|
};
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[BackupFilesList] Failed to refresh backup files:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error Loading Files",
|
||||||
|
text: "Failed to load backup files from your device.",
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a test backup file for debugging purposes
|
||||||
|
*/
|
||||||
|
async createTestBackup() {
|
||||||
|
try {
|
||||||
|
this.isLoading = true;
|
||||||
|
logger.log("[BackupFilesList] Creating test backup file");
|
||||||
|
|
||||||
|
const result = await this.platformService.createTestBackupFile();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
logger.log("[BackupFilesList] Test backup file created successfully:", {
|
||||||
|
fileName: result.fileName,
|
||||||
|
uri: result.uri,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Test Backup Created",
|
||||||
|
text: `Test backup file "${result.fileName}" created successfully. Refresh the list to see it.`,
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Test Backup Failed",
|
||||||
|
text: "Failed to create test backup file. Check the console for details.",
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests different directory contexts to debug file visibility issues
|
||||||
|
*/
|
||||||
|
async testDirectoryContexts() {
|
||||||
|
try {
|
||||||
|
this.isLoading = true;
|
||||||
|
logger.log("[BackupFilesList] Testing directory contexts");
|
||||||
|
|
||||||
|
const debugOutput = await this.platformService.testDirectoryContexts();
|
||||||
|
|
||||||
|
logger.log(
|
||||||
|
"[BackupFilesList] Directory context test results:",
|
||||||
|
debugOutput,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show the debug output in a notification or alert
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "info",
|
||||||
|
title: "Directory Context Test",
|
||||||
|
text: "Directory context test completed. Check the console for detailed results.",
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Also log the full output to console for easy access
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Context Test Failed",
|
||||||
|
text: "Failed to test directory contexts. Check the console for details.",
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the file list after a backup is created
|
||||||
|
* This method can be called from parent components
|
||||||
|
*/
|
||||||
|
async refreshAfterSave() {
|
||||||
|
logger.log("[BackupFilesList] refreshAfterSave called");
|
||||||
|
await this.refreshFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a specific file in the device's file viewer
|
||||||
|
* @param fileUri - URI of the file to open
|
||||||
|
* @param fileName - Name of the file for display
|
||||||
|
*/
|
||||||
|
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,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(result.error || "Failed to open file");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[BackupFilesList] Failed to open file:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error Opening File",
|
||||||
|
text: `Failed to open ${fileName}. ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the backup directory in the device's file explorer
|
||||||
|
*/
|
||||||
|
async openBackupDirectory() {
|
||||||
|
try {
|
||||||
|
const result = await this.platformService.openBackupDirectory();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
logger.log("[BackupFilesList] Backup directory opened successfully:", {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(result.error || "Failed to open backup directory");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[BackupFilesList] Failed to open backup directory:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error Opening Directory",
|
||||||
|
text: `Failed to open backup directory. ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
let count;
|
||||||
|
if (type === "all") {
|
||||||
|
count = this.backupFiles.length;
|
||||||
|
logger.log(
|
||||||
|
"[BackupFilesList] getFileCountByType (All):",
|
||||||
|
count,
|
||||||
|
this.backupFiles,
|
||||||
|
);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
count = this.backupFiles.filter((file) => file.type === type).length;
|
||||||
|
logger.log(`[BackupFilesList] getFileCountByType (${type}):`, count);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the appropriate icon for a file type
|
||||||
|
* @param type - File type
|
||||||
|
* @returns FontAwesome icon name
|
||||||
|
*/
|
||||||
|
getFileIcon(type: "contacts" | "seed" | "other"): string {
|
||||||
|
switch (type) {
|
||||||
|
case "contacts":
|
||||||
|
return "address-book";
|
||||||
|
case "seed":
|
||||||
|
return "key";
|
||||||
|
default:
|
||||||
|
return "file-alt";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the appropriate icon color for a file type
|
||||||
|
* @param type - File type
|
||||||
|
* @returns CSS color class
|
||||||
|
*/
|
||||||
|
getFileIconColor(type: "contacts" | "seed" | "other"): string {
|
||||||
|
switch (type) {
|
||||||
|
case "contacts":
|
||||||
|
return "text-blue-500";
|
||||||
|
case "seed":
|
||||||
|
return "text-orange-500";
|
||||||
|
default:
|
||||||
|
return "text-gray-500";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the appropriate badge color for a file type
|
||||||
|
* @param type - File type
|
||||||
|
* @returns CSS color class
|
||||||
|
*/
|
||||||
|
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";
|
||||||
|
default:
|
||||||
|
return "bg-gray-100 text-gray-800";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats file size in human-readable format
|
||||||
|
* @param bytes - File size in bytes
|
||||||
|
* @returns Formatted file size string
|
||||||
|
*/
|
||||||
|
formatFileSize(bytes: number): string {
|
||||||
|
if (bytes === 0) return "0 Bytes";
|
||||||
|
|
||||||
|
const k = 1024;
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug method to test file discovery
|
||||||
|
* Can be called from browser console for troubleshooting
|
||||||
|
*/
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test listing backup files specifically
|
||||||
|
const backupFilesResult = await platformService.testBackupFiles();
|
||||||
|
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();
|
||||||
|
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,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
allFiles: allFilesResult,
|
||||||
|
backupFiles: backupFilesResult,
|
||||||
|
currentBackupFiles: this.backupFiles,
|
||||||
|
debugAllFiles:
|
||||||
|
"debugListAllFiles" in platformService
|
||||||
|
? await (platformService as any).debugListAllFiles()
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[BackupFilesList] Debug file discovery failed:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Watch("platformCapabilities.hasFileSystem", { immediate: true })
|
||||||
|
async onFileSystemCapabilityChanged(newVal: boolean) {
|
||||||
|
if (newVal) {
|
||||||
|
await this.refreshFiles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
/** * Data Export Section Component * * Provides UI and functionality for
|
/** * Data Export Section Component * * Provides UI and functionality for
|
||||||
exporting user data and backing up identifier seeds. * Includes buttons for seed
|
exporting user data and backing up identifier seeds. * Includes buttons for seed
|
||||||
backup and database export, with platform-specific download instructions. * *
|
backup and database export, with platform-specific download instructions. * Also
|
||||||
@component * @displayName DataExportSection * @example * ```vue *
|
displays a list of backup files with options to open them in the device's file
|
||||||
|
explorer. * * @component * @displayName DataExportSection * @example * ```vue *
|
||||||
<DataExportSection :active-did="currentDid" />
|
<DataExportSection :active-did="currentDid" />
|
||||||
* ``` */
|
* ``` */
|
||||||
|
|
||||||
@@ -43,18 +44,27 @@ backup and database export, with platform-specific download instructions. * *
|
|||||||
v-if="platformCapabilities.isIOS"
|
v-if="platformCapabilities.isIOS"
|
||||||
class="list-disc list-outside ml-4"
|
class="list-disc list-outside ml-4"
|
||||||
>
|
>
|
||||||
On iOS: You will be prompted to choose a location to save your backup
|
On iOS: Files are saved to Documents folder (accessible via Files app)
|
||||||
file.
|
and persist between app installations.
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="platformCapabilities.isMobile && !platformCapabilities.isIOS"
|
v-if="platformCapabilities.isMobile && !platformCapabilities.isIOS"
|
||||||
class="list-disc list-outside ml-4"
|
class="list-disc list-outside ml-4"
|
||||||
>
|
>
|
||||||
On Android: You will be prompted to choose a location to save your
|
On Android: Files are saved to Downloads/TimeSafari or external
|
||||||
backup file.
|
storage (accessible via file managers) and persist between app
|
||||||
|
installations.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Backup Files List -->
|
||||||
|
<div
|
||||||
|
v-if="platformCapabilities.hasFileSystem"
|
||||||
|
class="mt-6 pt-6 border-t border-gray-300"
|
||||||
|
>
|
||||||
|
<BackupFilesList ref="backupFilesList" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -65,20 +75,21 @@ import { AppString, NotificationIface } from "../constants/app";
|
|||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import * as databaseUtil from "../db/databaseUtil";
|
import * as databaseUtil from "../db/databaseUtil";
|
||||||
|
|
||||||
import { logger } from "../utils/logger";
|
import { logger, getTimestampForFilename } from "../utils/logger";
|
||||||
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
|
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
|
||||||
import {
|
import {
|
||||||
PlatformService,
|
PlatformService,
|
||||||
PlatformCapabilities,
|
PlatformCapabilities,
|
||||||
} from "../services/PlatformService";
|
} from "../services/PlatformService";
|
||||||
import { contactsToExportJson } from "../libs/util";
|
import { contactsToExportJson } from "../libs/util";
|
||||||
|
import BackupFilesList from "./BackupFilesList.vue";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @vue-component
|
* @vue-component
|
||||||
* Data Export Section Component
|
* Data Export Section Component
|
||||||
* Handles database export and seed backup functionality with platform-specific behavior
|
* Handles database export and seed backup functionality with platform-specific behavior
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component({ components: { BackupFilesList } })
|
||||||
export default class DataExportSection extends Vue {
|
export default class DataExportSection extends Vue {
|
||||||
/**
|
/**
|
||||||
* Notification function injected by Vue
|
* Notification function injected by Vue
|
||||||
@@ -151,7 +162,9 @@ export default class DataExportSection extends Vue {
|
|||||||
const jsonStr = JSON.stringify(exportData, null, 2);
|
const jsonStr = JSON.stringify(exportData, null, 2);
|
||||||
const blob = new Blob([jsonStr], { type: "application/json" });
|
const blob = new Blob([jsonStr], { type: "application/json" });
|
||||||
|
|
||||||
const fileName = `${AppString.APP_NAME_NO_SPACES}-backup-contacts.json`;
|
// Create timestamped filename
|
||||||
|
const timestamp = getTimestampForFilename();
|
||||||
|
const fileName = `${AppString.APP_NAME_NO_SPACES}-backup-contacts-${timestamp}.json`;
|
||||||
|
|
||||||
if (this.platformCapabilities.hasFileDownload) {
|
if (this.platformCapabilities.hasFileDownload) {
|
||||||
// Web platform: Use download link
|
// Web platform: Use download link
|
||||||
@@ -162,8 +175,21 @@ export default class DataExportSection extends Vue {
|
|||||||
downloadAnchor.click();
|
downloadAnchor.click();
|
||||||
setTimeout(() => URL.revokeObjectURL(this.downloadUrl), 1000);
|
setTimeout(() => URL.revokeObjectURL(this.downloadUrl), 1000);
|
||||||
} else if (this.platformCapabilities.hasFileSystem) {
|
} else if (this.platformCapabilities.hasFileSystem) {
|
||||||
// Native platform: Write to app directory
|
// Native platform: Write to user-accessible location and share
|
||||||
await this.platformService.writeAndShareFile(fileName, jsonStr);
|
const result = await this.platformService.writeAndShareFile(
|
||||||
|
fileName,
|
||||||
|
jsonStr,
|
||||||
|
{
|
||||||
|
allowLocationSelection: true,
|
||||||
|
showLocationSelectionDialog: true,
|
||||||
|
mimeType: "application/json",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle the result
|
||||||
|
if (!result.saved) {
|
||||||
|
throw new Error(result.error || "Failed to save file");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error("This platform does not support file downloads.");
|
throw new Error("This platform does not support file downloads.");
|
||||||
}
|
}
|
||||||
@@ -175,10 +201,19 @@ export default class DataExportSection extends Vue {
|
|||||||
title: "Export Successful",
|
title: "Export Successful",
|
||||||
text: this.platformCapabilities.hasFileDownload
|
text: this.platformCapabilities.hasFileDownload
|
||||||
? "See your downloads directory for the backup."
|
? "See your downloads directory for the backup."
|
||||||
: "The backup file has been saved.",
|
: "Backup saved to persistent storage that survives app installations. Use the share dialog to access your file and choose where to save it permanently.",
|
||||||
},
|
},
|
||||||
3000,
|
5000,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Refresh the backup files list
|
||||||
|
const backupFilesList = this.$refs.backupFilesList as any;
|
||||||
|
if (
|
||||||
|
backupFilesList &&
|
||||||
|
typeof backupFilesList.refreshAfterSave === "function"
|
||||||
|
) {
|
||||||
|
await backupFilesList.refreshAfterSave();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Export Error:", error);
|
logger.error("Export Error:", error);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -212,5 +247,18 @@ export default class DataExportSection extends Vue {
|
|||||||
hidden: !this.downloadUrl || !this.platformCapabilities.hasFileDownload,
|
hidden: !this.downloadUrl || !this.platformCapabilities.hasFileDownload,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async mounted() {
|
||||||
|
// 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"
|
||||||
|
) {
|
||||||
|
await backupFilesList.refreshFiles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ import {
|
|||||||
} from "../constants/app";
|
} from "../constants/app";
|
||||||
import { retrieveSettingsForActiveAccount } from "../db/index";
|
import { retrieveSettingsForActiveAccount } from "../db/index";
|
||||||
import { accessToken } from "../libs/crypto";
|
import { accessToken } from "../libs/crypto";
|
||||||
import { logger } from "../utils/logger";
|
import { logger, getTimestampForFilename } from "../utils/logger";
|
||||||
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
|
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
|
||||||
import * as databaseUtil from "../db/databaseUtil";
|
import * as databaseUtil from "../db/databaseUtil";
|
||||||
|
|
||||||
@@ -576,7 +576,7 @@ export default class ImageMethodDialog extends Vue {
|
|||||||
(blob) => {
|
(blob) => {
|
||||||
if (blob) {
|
if (blob) {
|
||||||
this.blob = blob;
|
this.blob = blob;
|
||||||
this.fileName = `photo_${Date.now()}.jpg`;
|
this.fileName = `photo-${getTimestampForFilename()}.jpg`;
|
||||||
this.showRetry = true;
|
this.showRetry = true;
|
||||||
this.stopCameraPreview();
|
this.stopCameraPreview();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ import {
|
|||||||
import * as databaseUtil from "../db/databaseUtil";
|
import * as databaseUtil from "../db/databaseUtil";
|
||||||
import { retrieveSettingsForActiveAccount } from "../db/index";
|
import { retrieveSettingsForActiveAccount } from "../db/index";
|
||||||
import { accessToken } from "../libs/crypto";
|
import { accessToken } from "../libs/crypto";
|
||||||
import { logger } from "../utils/logger";
|
import { logger, getTimestampForFilename } from "../utils/logger";
|
||||||
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
|
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
|
||||||
|
|
||||||
@Component({ components: { VuePictureCropper } })
|
@Component({ components: { VuePictureCropper } })
|
||||||
@@ -393,7 +393,7 @@ export default class PhotoDialog extends Vue {
|
|||||||
(blob) => {
|
(blob) => {
|
||||||
if (blob) {
|
if (blob) {
|
||||||
this.blob = blob;
|
this.blob = blob;
|
||||||
this.fileName = `photo_${Date.now()}.jpg`;
|
this.fileName = `photo-${getTimestampForFilename()}.jpg`;
|
||||||
this.stopCameraPreview();
|
this.stopCameraPreview();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,6 +12,14 @@ import type { PluginListenerHandle } from "@capacitor/core";
|
|||||||
* Supports 'backButton' and 'appUrlOpen' events from Capacitor
|
* Supports 'backButton' and 'appUrlOpen' events from Capacitor
|
||||||
*/
|
*/
|
||||||
interface AppInterface {
|
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<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add listener for back button events
|
* Add listener for back button events
|
||||||
* @param eventName - Must be 'backButton'
|
* @param eventName - Must be 'backButton'
|
||||||
@@ -38,8 +46,19 @@ interface AppInterface {
|
|||||||
/**
|
/**
|
||||||
* App wrapper for Capacitor functionality
|
* App wrapper for Capacitor functionality
|
||||||
* Provides type-safe event listeners for back button and URL open events
|
* Provides type-safe event listeners for back button and URL open events
|
||||||
|
* and app exit functionality
|
||||||
*/
|
*/
|
||||||
export const App: AppInterface = {
|
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<void> {
|
||||||
|
return CapacitorApp.exitApp();
|
||||||
|
},
|
||||||
|
|
||||||
addListener(
|
addListener(
|
||||||
eventName: "backButton" | "appUrlOpen",
|
eventName: "backButton" | "appUrlOpen",
|
||||||
listenerFunc: BackButtonListener | ((data: AppLaunchUrl) => void),
|
listenerFunc: BackButtonListener | ((data: AppLaunchUrl) => void),
|
||||||
|
|||||||
@@ -65,9 +65,21 @@ export interface PlatformService {
|
|||||||
* Writes content to a file at the specified path and shares it.
|
* Writes content to a file at the specified path and shares it.
|
||||||
* @param fileName - The filename of the file to write
|
* @param fileName - The filename of the file to write
|
||||||
* @param content - The content to write to the file
|
* @param content - The content to write to the file
|
||||||
* @returns Promise that resolves when the write is complete
|
* @param options - Optional parameters for file saving behavior
|
||||||
|
* @returns Promise that resolves to save/share result
|
||||||
*/
|
*/
|
||||||
writeAndShareFile(fileName: string, content: string): Promise<void>;
|
writeAndShareFile(
|
||||||
|
fileName: string,
|
||||||
|
content: string,
|
||||||
|
options?: {
|
||||||
|
allowLocationSelection?: boolean;
|
||||||
|
saveToDownloads?: boolean;
|
||||||
|
saveToPrivateStorage?: boolean;
|
||||||
|
mimeType?: string;
|
||||||
|
showShareDialog?: boolean;
|
||||||
|
showLocationSelectionDialog?: boolean;
|
||||||
|
},
|
||||||
|
): Promise<{ saved: boolean; uri?: string; shared: boolean; error?: string }>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a file at the specified path.
|
* Deletes a file at the specified path.
|
||||||
@@ -83,6 +95,48 @@ export interface PlatformService {
|
|||||||
*/
|
*/
|
||||||
listFiles(directory: string): Promise<string[]>;
|
listFiles(directory: string): Promise<string[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the file sharing functionality by creating and sharing a test file.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
testFileSharing(): Promise<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests saving a file without showing the share dialog.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
testFileSaveOnly(): Promise<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the location selection functionality using the file picker.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
testLocationSelection(): Promise<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests location selection without showing the dialog (restores original behavior).
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
testLocationSelectionSilent(): Promise<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests listing user-accessible files saved by the app.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
testListUserFiles(): Promise<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests listing backup files specifically saved by the app.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
testBackupFiles(): Promise<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests opening the backup directory in the device's file explorer.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
testOpenBackupDirectory(): Promise<string>;
|
||||||
|
|
||||||
// Camera operations
|
// Camera operations
|
||||||
/**
|
/**
|
||||||
* Activates the device camera to take a picture.
|
* Activates the device camera to take a picture.
|
||||||
@@ -130,4 +184,92 @@ export interface PlatformService {
|
|||||||
sql: string,
|
sql: string,
|
||||||
params?: unknown[],
|
params?: unknown[],
|
||||||
): Promise<{ changes: number; lastId?: number }>;
|
): Promise<{ changes: number; lastId?: number }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists user-accessible files saved by the app.
|
||||||
|
* Returns files from Downloads (Android) or Documents (iOS) directories.
|
||||||
|
* @returns Promise resolving to array of file information
|
||||||
|
*/
|
||||||
|
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<
|
||||||
|
Array<{
|
||||||
|
name: string;
|
||||||
|
uri: string;
|
||||||
|
size?: number;
|
||||||
|
type: "contacts" | "seed" | "other";
|
||||||
|
path?: string;
|
||||||
|
}>
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a file in the device's default file viewer/app.
|
||||||
|
* Uses the native share dialog to provide options for opening the file.
|
||||||
|
* @param fileUri - URI of the file to open
|
||||||
|
* @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 }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the directory containing backup files in the device's file explorer.
|
||||||
|
* Uses the native share dialog to provide options for accessing the directory.
|
||||||
|
* @returns Promise resolving to success status
|
||||||
|
*/
|
||||||
|
openBackupDirectory(): Promise<{ success: boolean; error?: string }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a test backup file to verify file writing and reading functionality.
|
||||||
|
* 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;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests different directory contexts to see what files are available.
|
||||||
|
* This helps debug file visibility issues across different storage contexts.
|
||||||
|
* @returns Promise resolving to debug information about file discovery across contexts
|
||||||
|
*/
|
||||||
|
testDirectoryContexts(): Promise<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists files and folders in a specific directory for directory browsing
|
||||||
|
* @param path - The directory path to list
|
||||||
|
* @param debugShowAll - Debug flag to treat all entries as files
|
||||||
|
* @returns Promise resolving to array of directory entries
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
* This helps identify if the directory exists but is empty or has permission issues
|
||||||
|
* @returns Promise resolving to debug information about the TimeSafari directory
|
||||||
|
*/
|
||||||
|
debugTimeSafariDirectory(): Promise<string>;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -205,6 +205,7 @@ export class ElectronPlatformService implements PlatformService {
|
|||||||
isIOS: false,
|
isIOS: false,
|
||||||
hasFileDownload: false, // Not implemented yet
|
hasFileDownload: false, // Not implemented yet
|
||||||
needsFileHandlingInstructions: false,
|
needsFileHandlingInstructions: false,
|
||||||
|
isNativeApp: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,11 +235,32 @@ export class ElectronPlatformService implements PlatformService {
|
|||||||
* Writes content to a file and opens the system share dialog.
|
* Writes content to a file and opens the system share dialog.
|
||||||
* @param _fileName - Name of the file to create
|
* @param _fileName - Name of the file to create
|
||||||
* @param _content - Content to write to the file
|
* @param _content - Content to write to the file
|
||||||
|
* @param _options - Options for file saving behavior
|
||||||
* @throws Error with "Not implemented" message
|
* @throws Error with "Not implemented" message
|
||||||
* @todo Implement using Electron's dialog and file system APIs
|
* @todo Implement using Electron's dialog and file system APIs
|
||||||
*/
|
*/
|
||||||
async writeAndShareFile(_fileName: string, _content: string): Promise<void> {
|
async writeAndShareFile(
|
||||||
throw new Error("Not implemented");
|
_fileName: string,
|
||||||
|
_content: string,
|
||||||
|
_options?: {
|
||||||
|
allowLocationSelection?: boolean;
|
||||||
|
saveToDownloads?: boolean;
|
||||||
|
saveToPrivateStorage?: boolean;
|
||||||
|
mimeType?: string;
|
||||||
|
showShareDialog?: boolean;
|
||||||
|
showLocationSelectionDialog?: boolean;
|
||||||
|
},
|
||||||
|
): Promise<{
|
||||||
|
saved: boolean;
|
||||||
|
uri?: string;
|
||||||
|
shared: boolean;
|
||||||
|
error?: string;
|
||||||
|
}> {
|
||||||
|
return {
|
||||||
|
saved: false,
|
||||||
|
shared: false,
|
||||||
|
error: "Not implemented in Electron platform",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -284,6 +306,17 @@ export class ElectronPlatformService implements PlatformService {
|
|||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should rotate the camera between front and back cameras.
|
||||||
|
* @returns Promise that resolves when the camera is rotated
|
||||||
|
* @throws Error with "Not implemented" message
|
||||||
|
* @todo Implement camera rotation using Electron's media APIs
|
||||||
|
*/
|
||||||
|
async rotateCamera(): Promise<void> {
|
||||||
|
logger.error("rotateCamera not implemented in Electron platform");
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should handle deep link URLs for the desktop application.
|
* Should handle deep link URLs for the desktop application.
|
||||||
* @param _url - The deep link URL to handle
|
* @param _url - The deep link URL to handle
|
||||||
@@ -345,4 +378,173 @@ export class ElectronPlatformService implements PlatformService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the file sharing functionality.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testFileSharing(): Promise<string> {
|
||||||
|
return "File sharing not available in Electron platform - not implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests saving a file without showing the share dialog.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testFileSaveOnly(): Promise<string> {
|
||||||
|
return "File save only not available in Electron platform - not implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the location selection functionality using the file picker.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testLocationSelection(): Promise<string> {
|
||||||
|
return "Location selection not available in Electron platform - not implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests location selection without showing the dialog (restores original behavior).
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testLocationSelectionSilent(): Promise<string> {
|
||||||
|
return "Location selection not available in Electron platform - not implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests listing user-accessible files saved by the app.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testListUserFiles(): Promise<string> {
|
||||||
|
return "File listing not available in Electron platform - not implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests listing backup files specifically saved by the app.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testBackupFiles(): Promise<string> {
|
||||||
|
return "Backup file listing not available in Electron platform - not implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests opening the backup directory in the device's file explorer.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testOpenBackupDirectory(): Promise<string> {
|
||||||
|
return "Directory access not available in Electron platform - not implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists user-accessible files saved by the app.
|
||||||
|
* Not implemented in Electron platform.
|
||||||
|
* @returns Promise resolving to empty array
|
||||||
|
*/
|
||||||
|
async listUserAccessibleFiles(): Promise<
|
||||||
|
Array<{ name: string; uri: string; size?: number }>
|
||||||
|
> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists backup files specifically saved by the app.
|
||||||
|
* Not implemented for Electron platform.
|
||||||
|
* @returns Promise resolving to empty array
|
||||||
|
*/
|
||||||
|
async listBackupFiles(): Promise<
|
||||||
|
Array<{
|
||||||
|
name: string;
|
||||||
|
uri: string;
|
||||||
|
size?: number;
|
||||||
|
type: "contacts" | "seed" | "other";
|
||||||
|
path?: string;
|
||||||
|
}>
|
||||||
|
> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a file in the device's default file viewer/app.
|
||||||
|
* Not implemented in Electron platform.
|
||||||
|
* @param _fileUri - URI of the file to open
|
||||||
|
* @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",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the directory containing backup files in the device's file explorer.
|
||||||
|
* Not implemented in Electron platform.
|
||||||
|
* @returns Promise resolving to error status
|
||||||
|
*/
|
||||||
|
async openBackupDirectory(): Promise<{ success: boolean; error?: string }> {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "Directory access not implemented in Electron platform",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists files and folders in a specific directory for directory browsing.
|
||||||
|
* Not implemented for Electron platform.
|
||||||
|
* @returns Promise resolving to empty array
|
||||||
|
*/
|
||||||
|
async listFilesInDirectory(
|
||||||
|
_path: string,
|
||||||
|
_debugShowAll?: boolean,
|
||||||
|
): Promise<
|
||||||
|
Array<{
|
||||||
|
name: string;
|
||||||
|
uri: string;
|
||||||
|
size?: number;
|
||||||
|
path: string;
|
||||||
|
type: "file" | "folder";
|
||||||
|
}>
|
||||||
|
> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug method to check what's actually in the TimeSafari directory.
|
||||||
|
* Not implemented for Electron platform.
|
||||||
|
* @returns Promise resolving to debug information
|
||||||
|
*/
|
||||||
|
async debugTimeSafariDirectory(): Promise<string> {
|
||||||
|
return "Electron platform does not support file system access for debugging TimeSafari directory.";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a test backup file to verify file writing and reading functionality.
|
||||||
|
* Not implemented for Electron platform.
|
||||||
|
* @returns Promise resolving to error status
|
||||||
|
*/
|
||||||
|
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.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test method to try different directory contexts and see what files are available.
|
||||||
|
* Not implemented for Electron platform.
|
||||||
|
* @returns Promise resolving to debug information
|
||||||
|
*/
|
||||||
|
async testDirectoryContexts(): Promise<string> {
|
||||||
|
return "Electron platform does not support file system access for testing directory contexts.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export class PyWebViewPlatformService implements PlatformService {
|
|||||||
isIOS: false,
|
isIOS: false,
|
||||||
hasFileDownload: false, // Not implemented yet
|
hasFileDownload: false, // Not implemented yet
|
||||||
needsFileHandlingInstructions: false,
|
needsFileHandlingInstructions: false,
|
||||||
|
isNativeApp: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,14 +123,211 @@ export class PyWebViewPlatformService implements PlatformService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should write and share a file using the Python backend.
|
* Writes content to a file at the specified path and shares it.
|
||||||
* @param _fileName - Name of the file to write and share
|
* Not implemented in PyWebView platform.
|
||||||
* @param _content - Content to write to the file
|
* @param _fileName - The filename of the file to write
|
||||||
* @throws Error with "Not implemented" message
|
* @param _content - The content to write to the file
|
||||||
* @todo Implement file writing and sharing through pywebview's Python-JavaScript bridge
|
* @param _options - Optional parameters for file saving behavior
|
||||||
|
* @returns Promise that resolves to save/share result
|
||||||
*/
|
*/
|
||||||
async writeAndShareFile(_fileName: string, _content: string): Promise<void> {
|
async writeAndShareFile(
|
||||||
logger.error("writeAndShareFile not implemented in PyWebView platform");
|
_fileName: string,
|
||||||
throw new Error("Not implemented");
|
_content: string,
|
||||||
|
_options?: {
|
||||||
|
allowLocationSelection?: boolean;
|
||||||
|
saveToDownloads?: boolean;
|
||||||
|
saveToPrivateStorage?: boolean;
|
||||||
|
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",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists user-accessible files saved by the app.
|
||||||
|
* Not implemented in PyWebView platform.
|
||||||
|
* @returns Promise resolving to empty array
|
||||||
|
*/
|
||||||
|
async listUserAccessibleFiles(): Promise<
|
||||||
|
Array<{ name: string; uri: string; size?: number }>
|
||||||
|
> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists backup files specifically saved by the app.
|
||||||
|
* Not implemented for PyWebView platform.
|
||||||
|
* @returns Promise resolving to empty array
|
||||||
|
*/
|
||||||
|
async listBackupFiles(): Promise<
|
||||||
|
Array<{
|
||||||
|
name: string;
|
||||||
|
uri: string;
|
||||||
|
size?: number;
|
||||||
|
type: "contacts" | "seed" | "other";
|
||||||
|
path?: string;
|
||||||
|
}>
|
||||||
|
> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a file in the device's default file viewer/app.
|
||||||
|
* Not implemented in PyWebView platform.
|
||||||
|
* @param _fileUri - URI of the file to open
|
||||||
|
* @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",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the directory containing backup files in the device's file explorer.
|
||||||
|
* Not implemented in PyWebView platform.
|
||||||
|
* @returns Promise resolving to error status
|
||||||
|
*/
|
||||||
|
async openBackupDirectory(): Promise<{ success: boolean; error?: string }> {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "Directory access not implemented in PyWebView platform",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests listing user-accessible files saved by the app.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testListUserFiles(): Promise<string> {
|
||||||
|
return "File listing not available in PyWebView platform - not implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests listing backup files specifically saved by the app.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testBackupFiles(): Promise<string> {
|
||||||
|
return "Backup file listing not available in PyWebView platform - not implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests opening the backup directory in the device's file explorer.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testOpenBackupDirectory(): Promise<string> {
|
||||||
|
return "Directory access not available in PyWebView platform - not implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the file sharing functionality.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testFileSharing(): Promise<string> {
|
||||||
|
return "File sharing not available in PyWebView platform - not implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests saving a file without showing the share dialog.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testFileSaveOnly(): Promise<string> {
|
||||||
|
return "File saving not available in PyWebView platform - not implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the location selection functionality.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testLocationSelection(): Promise<string> {
|
||||||
|
return "Location selection not available in PyWebView platform - not implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests location selection without showing the dialog.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testLocationSelectionSilent(): Promise<string> {
|
||||||
|
return "Silent location selection not available in PyWebView platform - not implemented";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates the camera between front and back.
|
||||||
|
* Not implemented in PyWebView platform.
|
||||||
|
*/
|
||||||
|
async rotateCamera(): Promise<void> {
|
||||||
|
// Not implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists files and folders in a specific directory for directory browsing.
|
||||||
|
* Not implemented for PyWebView platform.
|
||||||
|
* @returns Promise resolving to empty array
|
||||||
|
*/
|
||||||
|
async listFilesInDirectory(
|
||||||
|
_path: string,
|
||||||
|
_debugShowAll?: boolean,
|
||||||
|
): Promise<
|
||||||
|
Array<{
|
||||||
|
name: string;
|
||||||
|
uri: string;
|
||||||
|
size?: number;
|
||||||
|
path: string;
|
||||||
|
type: "file" | "folder";
|
||||||
|
}>
|
||||||
|
> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug method to check what's actually in the TimeSafari directory.
|
||||||
|
* Not implemented for PyWebView platform.
|
||||||
|
* @returns Promise resolving to debug information
|
||||||
|
*/
|
||||||
|
async debugTimeSafariDirectory(): Promise<string> {
|
||||||
|
return "PyWebView platform does not support file system access for debugging TimeSafari directory.";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a test backup file to verify file writing and reading functionality.
|
||||||
|
* Not implemented for PyWebView platform.
|
||||||
|
* @returns Promise resolving to error status
|
||||||
|
*/
|
||||||
|
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.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test method to try different directory contexts and see what files are available.
|
||||||
|
* Not implemented for PyWebView platform.
|
||||||
|
* @returns Promise resolving to debug information
|
||||||
|
*/
|
||||||
|
async testDirectoryContexts(): Promise<string> {
|
||||||
|
return "PyWebView platform does not support file system access for testing directory contexts.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
PlatformService,
|
PlatformService,
|
||||||
PlatformCapabilities,
|
PlatformCapabilities,
|
||||||
} from "../PlatformService";
|
} from "../PlatformService";
|
||||||
import { logger } from "../../utils/logger";
|
import { logger, getTimestampForFilename } from "../../utils/logger";
|
||||||
import { QueryExecResult } from "@/interfaces/database";
|
import { QueryExecResult } from "@/interfaces/database";
|
||||||
import databaseService from "../AbsurdSqlDatabaseService";
|
import databaseService from "../AbsurdSqlDatabaseService";
|
||||||
|
|
||||||
@@ -29,10 +29,14 @@ export class WebPlatformService implements PlatformService {
|
|||||||
return {
|
return {
|
||||||
hasFileSystem: false,
|
hasFileSystem: false,
|
||||||
hasCamera: true, // Through file input with capture
|
hasCamera: true, // Through file input with capture
|
||||||
isMobile: /iPhone|iPad|iPod|Android/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),
|
isIOS: /iPad|iPhone|iPod/.test(navigator.userAgent),
|
||||||
hasFileDownload: true,
|
hasFileDownload: true,
|
||||||
needsFileHandlingInstructions: false,
|
needsFileHandlingInstructions: false,
|
||||||
|
isNativeApp: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +194,7 @@ export class WebPlatformService implements PlatformService {
|
|||||||
if (blob) {
|
if (blob) {
|
||||||
resolve({
|
resolve({
|
||||||
blob,
|
blob,
|
||||||
fileName: `photo_${Date.now()}.jpg`,
|
fileName: `photo-${getTimestampForFilename()}.jpg`,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
reject(new Error("Failed to capture image from webcam"));
|
reject(new Error("Failed to capture image from webcam"));
|
||||||
@@ -356,10 +360,31 @@ export class WebPlatformService implements PlatformService {
|
|||||||
* Not supported in web platform.
|
* Not supported in web platform.
|
||||||
* @param _fileName - Unused fileName parameter
|
* @param _fileName - Unused fileName parameter
|
||||||
* @param _content - Unused content parameter
|
* @param _content - Unused content parameter
|
||||||
* @throws Error indicating file system access is not available
|
* @param _options - Unused options parameter
|
||||||
|
* @returns Promise that resolves to a failure result
|
||||||
*/
|
*/
|
||||||
async writeAndShareFile(_fileName: string, _content: string): Promise<void> {
|
async writeAndShareFile(
|
||||||
throw new Error("File system access not available in web platform");
|
_fileName: string,
|
||||||
|
_content: string,
|
||||||
|
_options?: {
|
||||||
|
allowLocationSelection?: boolean;
|
||||||
|
saveToDownloads?: boolean;
|
||||||
|
saveToPrivateStorage?: boolean;
|
||||||
|
mimeType?: string;
|
||||||
|
showShareDialog?: boolean;
|
||||||
|
showLocationSelectionDialog?: boolean;
|
||||||
|
},
|
||||||
|
): Promise<{
|
||||||
|
saved: boolean;
|
||||||
|
uri?: string;
|
||||||
|
shared: boolean;
|
||||||
|
error?: string;
|
||||||
|
}> {
|
||||||
|
return {
|
||||||
|
saved: false,
|
||||||
|
shared: false,
|
||||||
|
error: "File system access not available in web platform",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -390,4 +415,183 @@ export class WebPlatformService implements PlatformService {
|
|||||||
.query(sql, params)
|
.query(sql, params)
|
||||||
.then((result: QueryExecResult[]) => result[0]?.values[0]);
|
.then((result: QueryExecResult[]) => result[0]?.values[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the file sharing functionality.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testFileSharing(): Promise<string> {
|
||||||
|
return "File sharing not available in web platform - use download instead";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests saving a file without showing the share dialog.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testFileSaveOnly(): Promise<string> {
|
||||||
|
return "File saving not available in web platform - use download instead";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the location selection functionality using the file picker.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testLocationSelection(): Promise<string> {
|
||||||
|
return "Location selection not available in web platform - use download instead";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests location selection without showing the dialog (restores original behavior).
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testLocationSelectionSilent(): Promise<string> {
|
||||||
|
return "Location selection not available in web platform - use download instead";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests listing user-accessible files saved by the app.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testListUserFiles(): Promise<string> {
|
||||||
|
return "File listing not available in web platform - files are downloaded directly";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests listing backup files specifically saved by the app.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testBackupFiles(): Promise<string> {
|
||||||
|
return "Backup file listing not available in web platform - files are downloaded directly";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests opening the backup directory in the device's file explorer.
|
||||||
|
* @returns Promise resolving to a test result message
|
||||||
|
*/
|
||||||
|
async testOpenBackupDirectory(): Promise<string> {
|
||||||
|
return "Directory access not available in web platform - files are downloaded directly";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists user-accessible files saved by the app.
|
||||||
|
* Not supported in web platform.
|
||||||
|
* @returns Promise resolving to empty array
|
||||||
|
*/
|
||||||
|
async listUserAccessibleFiles(): Promise<
|
||||||
|
Array<{ name: string; uri: string; size?: number }>
|
||||||
|
> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists backup files specifically saved by the app.
|
||||||
|
* Not supported in web platform.
|
||||||
|
* @returns Promise resolving to empty array
|
||||||
|
*/
|
||||||
|
async listBackupFiles(): Promise<
|
||||||
|
Array<{
|
||||||
|
name: string;
|
||||||
|
uri: string;
|
||||||
|
size?: number;
|
||||||
|
type: "contacts" | "seed" | "other";
|
||||||
|
path?: string;
|
||||||
|
}>
|
||||||
|
> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a file in the device's default file viewer/app.
|
||||||
|
* Not supported in web platform.
|
||||||
|
* @param _fileUri - URI of the file to open
|
||||||
|
* @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",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the directory containing backup files in the device's file explorer.
|
||||||
|
* Not supported in web platform.
|
||||||
|
* @returns Promise resolving to error status
|
||||||
|
*/
|
||||||
|
async openBackupDirectory(): Promise<{ success: boolean; error?: string }> {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "Directory access not available in web platform",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates the camera between front and back cameras.
|
||||||
|
* Not supported in web platform.
|
||||||
|
* @returns Promise that resolves immediately
|
||||||
|
*/
|
||||||
|
async rotateCamera(): Promise<void> {
|
||||||
|
// Not supported in web platform
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists files and folders in a specific directory for directory browsing.
|
||||||
|
* Not supported in web platform.
|
||||||
|
* @returns Promise resolving to empty array
|
||||||
|
*/
|
||||||
|
async listFilesInDirectory(
|
||||||
|
_path: string,
|
||||||
|
_debugShowAll?: boolean,
|
||||||
|
): Promise<
|
||||||
|
Array<{
|
||||||
|
name: string;
|
||||||
|
uri: string;
|
||||||
|
size?: number;
|
||||||
|
path: string;
|
||||||
|
type: "file" | "folder";
|
||||||
|
}>
|
||||||
|
> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug method to check what's actually in the TimeSafari directory.
|
||||||
|
* Not supported in web platform.
|
||||||
|
* @returns Promise resolving to debug information
|
||||||
|
*/
|
||||||
|
async debugTimeSafariDirectory(): Promise<string> {
|
||||||
|
return "Web platform does not support file system access for debugging TimeSafari directory.";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a test backup file to verify file writing and reading functionality.
|
||||||
|
* Not supported in web platform.
|
||||||
|
* @returns Promise resolving to error status
|
||||||
|
*/
|
||||||
|
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.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test method to try different directory contexts and see what files are available.
|
||||||
|
* Not supported in web platform.
|
||||||
|
* @returns Promise resolving to debug information
|
||||||
|
*/
|
||||||
|
async testDirectoryContexts(): Promise<string> {
|
||||||
|
return "Web platform does not support file system access for testing directory contexts.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,3 +79,22 @@ if (typeof module !== "undefined" && module.exports) {
|
|||||||
|
|
||||||
// Add default export for ESM
|
// Add default export for ESM
|
||||||
export default { logger };
|
export default { logger };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats current timestamp for use in filenames.
|
||||||
|
* Returns ISO string with colons and periods replaced with hyphens, truncated to seconds.
|
||||||
|
* Format: 2024-01-15T14-30-45
|
||||||
|
* @returns Formatted timestamp string safe for filenames
|
||||||
|
*/
|
||||||
|
export function getTimestampForFilename(): string {
|
||||||
|
return new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats current timestamp for use in filenames with date only.
|
||||||
|
* Format: 2024-01-15
|
||||||
|
* @returns Date-only timestamp string safe for filenames
|
||||||
|
*/
|
||||||
|
export function getDateForFilename(): string {
|
||||||
|
return new Date().toISOString().slice(0, 10);
|
||||||
|
}
|
||||||
|
|||||||
@@ -215,6 +215,65 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
|
<h2 class="text-xl font-bold mb-4">File Sharing Test</h2>
|
||||||
|
Test the new file sharing functionality that saves to user-accessible
|
||||||
|
locations.
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="font-bold capitalize bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
|
||||||
|
@click="testFileSharing()"
|
||||||
|
>
|
||||||
|
Test File Sharing
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="font-bold capitalize bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
|
||||||
|
@click="testFileSaveOnly()"
|
||||||
|
>
|
||||||
|
Test Save Only (No Share Dialog)
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="font-bold capitalize bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
|
||||||
|
@click="testLocationSelection()"
|
||||||
|
>
|
||||||
|
Test Location Selection
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="font-bold capitalize bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
|
||||||
|
@click="testLocationSelectionSilent()"
|
||||||
|
>
|
||||||
|
Test Silent Location Selection
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="font-bold capitalize bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
|
||||||
|
@click="testListUserFiles()"
|
||||||
|
>
|
||||||
|
List User Files
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="font-bold capitalize bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
|
||||||
|
@click="testBackupFiles()"
|
||||||
|
>
|
||||||
|
Test Backup Files
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="font-bold capitalize bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
|
||||||
|
@click="testOpenBackupDirectory()"
|
||||||
|
>
|
||||||
|
Test Open Directory
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="font-bold capitalize bg-slate-500 text-white px-3 py-2 rounded-md mr-2"
|
||||||
|
@click="testFileDiscoveryDebug()"
|
||||||
|
>
|
||||||
|
Debug File Discovery
|
||||||
|
</button>
|
||||||
|
<div v-if="fileSharingResult" class="mt-2 p-2 bg-gray-100 rounded">
|
||||||
|
<strong>Result:</strong> {{ fileSharingResult }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<h2 class="text-xl font-bold mb-4">Image Sharing</h2>
|
<h2 class="text-xl font-bold mb-4">Image Sharing</h2>
|
||||||
Populates the "shared-photo" view as if they used "share_target".
|
Populates the "shared-photo" view as if they used "share_target".
|
||||||
@@ -387,6 +446,9 @@ export default class Help extends Vue {
|
|||||||
sqlQuery = "";
|
sqlQuery = "";
|
||||||
sqlResult: unknown = null;
|
sqlResult: unknown = null;
|
||||||
|
|
||||||
|
// for file sharing test
|
||||||
|
fileSharingResult = "";
|
||||||
|
|
||||||
cryptoLib = cryptoLib;
|
cryptoLib = cryptoLib;
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
@@ -620,5 +682,174 @@ export default class Help extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async testFileSharing() {
|
||||||
|
const platformService = PlatformServiceFactory.getInstance();
|
||||||
|
try {
|
||||||
|
const result = await platformService.testFileSharing();
|
||||||
|
this.fileSharingResult = result;
|
||||||
|
logger.log("File Sharing Test Result:", this.fileSharingResult);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("File Sharing Test Error:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "File Sharing Error",
|
||||||
|
text: error instanceof Error ? error.message : String(error),
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async testFileSaveOnly() {
|
||||||
|
const platformService = PlatformServiceFactory.getInstance();
|
||||||
|
try {
|
||||||
|
const result = await platformService.testFileSaveOnly();
|
||||||
|
this.fileSharingResult = result;
|
||||||
|
logger.log("File Save Only Test Result:", this.fileSharingResult);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("File Save Only Test Error:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "File Save Only Error",
|
||||||
|
text: error instanceof Error ? error.message : String(error),
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async testLocationSelection() {
|
||||||
|
const platformService = PlatformServiceFactory.getInstance();
|
||||||
|
try {
|
||||||
|
const result = await platformService.testLocationSelection();
|
||||||
|
this.fileSharingResult = result;
|
||||||
|
logger.log("Location Selection Test Result:", this.fileSharingResult);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Location Selection Test Error:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Location Selection Error",
|
||||||
|
text: error instanceof Error ? error.message : String(error),
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async testLocationSelectionSilent() {
|
||||||
|
const platformService = PlatformServiceFactory.getInstance();
|
||||||
|
try {
|
||||||
|
const result = await platformService.testLocationSelectionSilent();
|
||||||
|
this.fileSharingResult = result;
|
||||||
|
logger.log(
|
||||||
|
"Silent Location Selection Test Result:",
|
||||||
|
this.fileSharingResult,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Silent Location Selection Test Error:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Silent Location Selection Error",
|
||||||
|
text: error instanceof Error ? error.message : String(error),
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async testListUserFiles() {
|
||||||
|
const platformService = PlatformServiceFactory.getInstance();
|
||||||
|
try {
|
||||||
|
const result = await platformService.testListUserFiles();
|
||||||
|
this.fileSharingResult = result;
|
||||||
|
logger.log("List User Files Test Result:", this.fileSharingResult);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("List User Files Test Error:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "List User Files Error",
|
||||||
|
text: error instanceof Error ? error.message : String(error),
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async testBackupFiles() {
|
||||||
|
const platformService = PlatformServiceFactory.getInstance();
|
||||||
|
try {
|
||||||
|
const result = await platformService.testBackupFiles();
|
||||||
|
this.fileSharingResult = result;
|
||||||
|
logger.log("Backup Files Test Result:", this.fileSharingResult);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Backup Files Test Error:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Backup Files Error",
|
||||||
|
text: error instanceof Error ? error.message : String(error),
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async testOpenBackupDirectory() {
|
||||||
|
const platformService = PlatformServiceFactory.getInstance();
|
||||||
|
try {
|
||||||
|
const result = await platformService.testOpenBackupDirectory();
|
||||||
|
this.fileSharingResult = result;
|
||||||
|
logger.log("Open Backup Directory Test Result:", this.fileSharingResult);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Open Backup Directory Test Error:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Open Backup Directory Error",
|
||||||
|
text: error instanceof Error ? error.message : String(error),
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async testFileDiscoveryDebug() {
|
||||||
|
const platformService = PlatformServiceFactory.getInstance();
|
||||||
|
try {
|
||||||
|
if ("debugFileDiscoveryStepByStep" in platformService) {
|
||||||
|
const result = await (
|
||||||
|
platformService as any
|
||||||
|
).debugFileDiscoveryStepByStep();
|
||||||
|
this.fileSharingResult = result;
|
||||||
|
logger.log("File Discovery Debug Test Result:", this.fileSharingResult);
|
||||||
|
} else {
|
||||||
|
this.fileSharingResult = "Debug method not available on this platform";
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("File Discovery Debug Test Error:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "File Discovery Debug Error",
|
||||||
|
text: error instanceof Error ? error.message : String(error),
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user