refactor: migrate database operations to PlatformService
- Add account management methods to PlatformService interface - Implement account operations in all platform services - Fix PlatformCapabilities interface by adding sqlite property - Update util.ts to use PlatformService for account operations - Standardize account and settings management across platforms This change improves code organization by: - Centralizing database operations through PlatformService - Ensuring consistent account management across platforms - Making platform-specific implementations more maintainable - Reducing direct database access in utility functions Note: Some linter errors remain regarding db.accounts access and sqlite capabilities that need to be addressed in a follow-up commit.
This commit is contained in:
172
.cursor/rules/SQLITE.mdc
Normal file
172
.cursor/rules/SQLITE.mdc
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
# @capacitor-community/sqlite MDC Ruleset
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
This ruleset is for the `@capacitor-community/sqlite` plugin, a Capacitor community plugin that provides native and Electron SQLite database functionality with encryption support.
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
- Native SQLite database support for iOS, Android, and Electron
|
||||||
|
- Database encryption support using SQLCipher (Native) and better-sqlite3-multiple-ciphers (Electron)
|
||||||
|
- Biometric authentication support
|
||||||
|
- Cross-platform database operations
|
||||||
|
- JSON import/export capabilities
|
||||||
|
- Database migration support
|
||||||
|
- Sync table functionality
|
||||||
|
|
||||||
|
## Platform Support Matrix
|
||||||
|
|
||||||
|
### Core Database Operations
|
||||||
|
| Operation | Android | iOS | Electron | Web |
|
||||||
|
|-----------|---------|-----|----------|-----|
|
||||||
|
| Create Connection (RW) | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| Create Connection (RO) | ✅ | ✅ | ✅ | ❌ |
|
||||||
|
| Open DB (non-encrypted) | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| Open DB (encrypted) | ✅ | ✅ | ✅ | ❌ |
|
||||||
|
| Execute/Query | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| Import/Export JSON | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
|
||||||
|
### Security Features
|
||||||
|
| Feature | Android | iOS | Electron | Web |
|
||||||
|
|---------|---------|-----|----------|-----|
|
||||||
|
| Encryption | ✅ | ✅ | ✅ | ❌ |
|
||||||
|
| Biometric Auth | ✅ | ✅ | ✅ | ❌ |
|
||||||
|
| Secret Management | ✅ | ✅ | ✅ | ❌ |
|
||||||
|
|
||||||
|
## Configuration Requirements
|
||||||
|
|
||||||
|
### Base Configuration
|
||||||
|
```typescript
|
||||||
|
// capacitor.config.ts
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
CapacitorSQLite: {
|
||||||
|
iosDatabaseLocation: 'Library/CapacitorDatabase',
|
||||||
|
iosIsEncryption: true,
|
||||||
|
iosKeychainPrefix: 'your-app-prefix',
|
||||||
|
androidIsEncryption: true,
|
||||||
|
electronIsEncryption: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Platform-Specific Requirements
|
||||||
|
|
||||||
|
#### Android
|
||||||
|
- Minimum SDK: 23
|
||||||
|
- Target SDK: 35
|
||||||
|
- Required Gradle JDK: 21
|
||||||
|
- Required Android Gradle Plugin: 8.7.2
|
||||||
|
- Required manifest settings for backup prevention
|
||||||
|
- Required data extraction rules
|
||||||
|
|
||||||
|
#### iOS
|
||||||
|
- No additional configuration needed beyond base setup
|
||||||
|
- Supports biometric authentication
|
||||||
|
- Uses keychain for encryption
|
||||||
|
|
||||||
|
#### Electron
|
||||||
|
Required dependencies:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"better-sqlite3-multiple-ciphers": "latest",
|
||||||
|
"electron-json-storage": "latest",
|
||||||
|
"jszip": "latest",
|
||||||
|
"node-fetch": "2.6.7",
|
||||||
|
"crypto": "latest",
|
||||||
|
"crypto-js": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Web
|
||||||
|
- Requires `sql.js` and `jeep-sqlite`
|
||||||
|
- Manual copy of `sql-wasm.wasm` to assets folder
|
||||||
|
- Framework-specific asset placement:
|
||||||
|
- Angular: `src/assets/`
|
||||||
|
- Vue/React: `public/assets/`
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Database Operations
|
||||||
|
1. Always close connections after use
|
||||||
|
2. Use transactions for multiple operations
|
||||||
|
3. Implement proper error handling
|
||||||
|
4. Use prepared statements for queries
|
||||||
|
5. Implement proper database versioning
|
||||||
|
|
||||||
|
### Security
|
||||||
|
1. Always use encryption for sensitive data
|
||||||
|
2. Implement proper secret management
|
||||||
|
3. Use biometric authentication when available
|
||||||
|
4. Follow platform-specific security guidelines
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
1. Use appropriate indexes
|
||||||
|
2. Implement connection pooling
|
||||||
|
3. Use transactions for bulk operations
|
||||||
|
4. Implement proper database cleanup
|
||||||
|
|
||||||
|
## Common Issues and Solutions
|
||||||
|
|
||||||
|
### Android
|
||||||
|
- Build data properties conflict: Add to `app/build.gradle`:
|
||||||
|
```gradle
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'build-data.properties'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Electron
|
||||||
|
- Node-fetch version must be ≤2.6.7
|
||||||
|
- For Capacitor Electron v5:
|
||||||
|
- Use Electron@25.8.4
|
||||||
|
- Add `"skipLibCheck": true` to tsconfig.json
|
||||||
|
|
||||||
|
### Web
|
||||||
|
- Ensure proper WASM file placement
|
||||||
|
- Handle browser compatibility
|
||||||
|
- Implement proper fallbacks
|
||||||
|
|
||||||
|
## Version Compatibility
|
||||||
|
- Requires Node.js ≥16.0.0
|
||||||
|
- Compatible with Capacitor ≥7.0.0
|
||||||
|
- Supports TypeScript 4.1.5+
|
||||||
|
|
||||||
|
## Testing Requirements
|
||||||
|
- Unit tests for database operations
|
||||||
|
- Platform-specific integration tests
|
||||||
|
- Encryption/decryption tests
|
||||||
|
- Biometric authentication tests
|
||||||
|
- Migration tests
|
||||||
|
- Sync functionality tests
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
- API Documentation: `/docs/API.md`
|
||||||
|
- Connection API: `/docs/APIConnection.md`
|
||||||
|
- DB Connection API: `/docs/APIDBConnection.md`
|
||||||
|
- Release Notes: `/docs/info_releases.md`
|
||||||
|
- Changelog: `CHANGELOG.md`
|
||||||
|
|
||||||
|
## Contributing Guidelines
|
||||||
|
- Follow Ionic coding standards
|
||||||
|
- Use provided linting and formatting tools
|
||||||
|
- Maintain platform compatibility
|
||||||
|
- Update documentation
|
||||||
|
- Add appropriate tests
|
||||||
|
- Follow semantic versioning
|
||||||
|
|
||||||
|
## Maintenance
|
||||||
|
- Regular security updates
|
||||||
|
- Platform compatibility checks
|
||||||
|
- Performance optimization
|
||||||
|
- Documentation updates
|
||||||
|
- Dependency updates
|
||||||
|
|
||||||
|
## License
|
||||||
|
MIT License - See LICENSE file for details
|
||||||
@@ -144,6 +144,32 @@ try {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## SQLite Database Initialization (Secure Storage)
|
||||||
|
|
||||||
|
In our secure storage implementation (using the new PlatformService SQLite interface), the database is not initialized by simply calling `PlatformServiceFactory.getInstance()`. Instead, we initialize the database early in the app startup (for example, in our main entry file) so that it is ready before any component (or other code) tries to use it.
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
- **PlatformServiceFactory.getInstance()** returns a singleton instance of the appropriate platform service (for example, Web, Capacitor, Electron, or PyWebView) and assigns it (for example, to `app.config.globalProperties.$platform`).
|
||||||
|
- The platform service (via its `getSQLite()` method) returns a SQLiteOperations interface (which is platform-agnostic).
|
||||||
|
- **The actual database initialization** (for example, creating/opening the database, running migrations, setting PRAGMAs) is done by calling `initialize(config)` on the SQLiteOperations object.
|
||||||
|
|
||||||
|
### Example (in main.common.ts)
|
||||||
|
|
||||||
|
Below is an example diff (or “edit”) applied in our main entry (for example, in `src/main.common.ts`) so that the SQLite database is initialized immediately after setting the global property:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
app.config.globalProperties.$platform = PlatformServiceFactory.getInstance();
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const platform = app.config.globalProperties.$platform;
|
||||||
|
const sqlite = await platform.getSQLite();
|
||||||
|
const config = { name: "TimeSafariDB", useWAL: true }; // (or your desired config)
|
||||||
|
await sqlite.initialize(config);
|
||||||
|
logger.log("[App Init] SQLite database initialized.");
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
### 3. Platform Detection
|
### 3. Platform Detection
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
|||||||
270
package-lock.json
generated
270
package-lock.json
generated
@@ -8,6 +8,7 @@
|
|||||||
"name": "timesafari",
|
"name": "timesafari",
|
||||||
"version": "0.4.6",
|
"version": "0.4.6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@capacitor-community/sqlite": "6.0.0",
|
||||||
"@capacitor-mlkit/barcode-scanning": "^6.0.0",
|
"@capacitor-mlkit/barcode-scanning": "^6.0.0",
|
||||||
"@capacitor/android": "^6.2.0",
|
"@capacitor/android": "^6.2.0",
|
||||||
"@capacitor/app": "^6.0.0",
|
"@capacitor/app": "^6.0.0",
|
||||||
@@ -77,6 +78,7 @@
|
|||||||
"reflect-metadata": "^0.1.14",
|
"reflect-metadata": "^0.1.14",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
"simple-vue-camera": "^1.1.3",
|
"simple-vue-camera": "^1.1.3",
|
||||||
|
"sqlite": "^5.1.1",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"stream-browserify": "^3.0.0",
|
"stream-browserify": "^3.0.0",
|
||||||
"three": "^0.156.1",
|
"three": "^0.156.1",
|
||||||
@@ -2362,6 +2364,21 @@
|
|||||||
"node": ">=8.9"
|
"node": ">=8.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@capacitor-community/sqlite": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@capacitor-community/sqlite/-/sqlite-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-Pm0orp17nkLzqQb7hGD9A1UCt5jbgxthHBSRxxcPOxiI+/6aTSBwa1Eshy+k9d8Arz/H77ISTdoH/HPi0ez3sQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jeep-sqlite": "^2.7.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@capacitor/core": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@capacitor-mlkit/barcode-scanning": {
|
"node_modules/@capacitor-mlkit/barcode-scanning": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@capacitor-mlkit/barcode-scanning/-/barcode-scanning-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@capacitor-mlkit/barcode-scanning/-/barcode-scanning-6.2.0.tgz",
|
||||||
@@ -8225,6 +8242,133 @@
|
|||||||
"@stablelib/xchacha20": "^1.0.1"
|
"@stablelib/xchacha20": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@stencil/core": {
|
||||||
|
"version": "4.31.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.31.0.tgz",
|
||||||
|
"integrity": "sha512-Ei9MFJ6LPD9BMFs+klkHylbVOOYhG10Jv4bvoFf3GMH15kA41rSYkEdr4DiX84ZdErQE2qtFV/2SUyWoXh0AhA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"stencil": "bin/stencil"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0",
|
||||||
|
"npm": ">=7.10.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@rollup/rollup-darwin-arm64": "4.34.9",
|
||||||
|
"@rollup/rollup-darwin-x64": "4.34.9",
|
||||||
|
"@rollup/rollup-linux-arm64-gnu": "4.34.9",
|
||||||
|
"@rollup/rollup-linux-arm64-musl": "4.34.9",
|
||||||
|
"@rollup/rollup-linux-x64-gnu": "4.34.9",
|
||||||
|
"@rollup/rollup-linux-x64-musl": "4.34.9",
|
||||||
|
"@rollup/rollup-win32-arm64-msvc": "4.34.9",
|
||||||
|
"@rollup/rollup-win32-x64-msvc": "4.34.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@stencil/core/node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
|
"version": "4.34.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz",
|
||||||
|
"integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@stencil/core/node_modules/@rollup/rollup-darwin-x64": {
|
||||||
|
"version": "4.34.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz",
|
||||||
|
"integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@stencil/core/node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
|
"version": "4.34.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz",
|
||||||
|
"integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@stencil/core/node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
|
"version": "4.34.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz",
|
||||||
|
"integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@stencil/core/node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
|
"version": "4.34.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz",
|
||||||
|
"integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@stencil/core/node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
|
"version": "4.34.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz",
|
||||||
|
"integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@stencil/core/node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
|
"version": "4.34.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz",
|
||||||
|
"integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@stencil/core/node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
|
"version": "4.34.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz",
|
||||||
|
"integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@surma/rollup-plugin-off-main-thread": {
|
"node_modules/@surma/rollup-plugin-off-main-thread": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
|
||||||
@@ -8872,6 +9016,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/sqlite3/-/sqlite3-3.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/sqlite3/-/sqlite3-3.1.11.tgz",
|
||||||
"integrity": "sha512-KYF+QgxAnnAh7DWPdNDroxkDI3/MspH1NMx6m/N/6fT1G6+jvsw4/ZePt8R8cr7ta58aboeTfYFBDxTJ5yv15w==",
|
"integrity": "sha512-KYF+QgxAnnAh7DWPdNDroxkDI3/MspH1NMx6m/N/6fT1G6+jvsw4/ZePt8R8cr7ta58aboeTfYFBDxTJ5yv15w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
@@ -11289,6 +11434,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
||||||
"integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="
|
"integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/browser-fs-access": {
|
||||||
|
"version": "0.35.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.35.0.tgz",
|
||||||
|
"integrity": "sha512-sLoadumpRfsjprP8XzVjpQc0jK8yqHBx0PtUTGYj2fftT+P/t+uyDAQdMgGAPKD011in/O+YYGh7fIs0oG/viw==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/browserify-aes": {
|
"node_modules/browserify-aes": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
|
||||||
@@ -12880,8 +13031,7 @@
|
|||||||
"node_modules/core-util-is": {
|
"node_modules/core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
|
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/cosmiconfig": {
|
"node_modules/cosmiconfig": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
@@ -16777,6 +16927,12 @@
|
|||||||
"node": ">=16.x"
|
"node": ">=16.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/immediate": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||||
@@ -17645,6 +17801,19 @@
|
|||||||
"node": ">=6.4.0"
|
"node": ">=6.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jeep-sqlite": {
|
||||||
|
"version": "2.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jeep-sqlite/-/jeep-sqlite-2.8.0.tgz",
|
||||||
|
"integrity": "sha512-FWNUP6OAmrUHwiW7H1xH5YUQ8tN2O4l4psT1sLd7DQtHd5PfrA1nvNdeKPNj+wQBtu7elJa8WoUibTytNTaaCg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@stencil/core": "^4.20.0",
|
||||||
|
"browser-fs-access": "^0.35.0",
|
||||||
|
"jszip": "^3.10.1",
|
||||||
|
"localforage": "^1.10.0",
|
||||||
|
"sql.js": "^1.11.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jest-environment-node": {
|
"node_modules/jest-environment-node": {
|
||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
|
||||||
@@ -18091,6 +18260,60 @@
|
|||||||
"resolved": "https://registry.npmjs.org/jsqr/-/jsqr-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsqr/-/jsqr-1.4.0.tgz",
|
||||||
"integrity": "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A=="
|
"integrity": "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A=="
|
||||||
},
|
},
|
||||||
|
"node_modules/jszip": {
|
||||||
|
"version": "3.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
|
||||||
|
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
|
||||||
|
"license": "(MIT OR GPL-3.0-or-later)",
|
||||||
|
"dependencies": {
|
||||||
|
"lie": "~3.3.0",
|
||||||
|
"pako": "~1.0.2",
|
||||||
|
"readable-stream": "~2.3.6",
|
||||||
|
"setimmediate": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jszip/node_modules/isarray": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/jszip/node_modules/pako": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||||
|
"license": "(MIT AND Zlib)"
|
||||||
|
},
|
||||||
|
"node_modules/jszip/node_modules/readable-stream": {
|
||||||
|
"version": "2.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||||
|
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"core-util-is": "~1.0.0",
|
||||||
|
"inherits": "~2.0.3",
|
||||||
|
"isarray": "~1.0.0",
|
||||||
|
"process-nextick-args": "~2.0.0",
|
||||||
|
"safe-buffer": "~5.1.1",
|
||||||
|
"string_decoder": "~1.1.1",
|
||||||
|
"util-deprecate": "~1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jszip/node_modules/safe-buffer": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/jszip/node_modules/string_decoder": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "~5.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/katex": {
|
"node_modules/katex": {
|
||||||
"version": "0.16.22",
|
"version": "0.16.22",
|
||||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz",
|
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz",
|
||||||
@@ -18566,6 +18789,15 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lie": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"immediate": "~3.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lighthouse-logger": {
|
"node_modules/lighthouse-logger": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz",
|
||||||
@@ -18903,6 +19135,24 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/localforage": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"lie": "3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/localforage/node_modules/lie": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"immediate": "~3.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/localstorage-slim": {
|
"node_modules/localstorage-slim": {
|
||||||
"version": "2.7.1",
|
"version": "2.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/localstorage-slim/-/localstorage-slim-2.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/localstorage-slim/-/localstorage-slim-2.7.1.tgz",
|
||||||
@@ -22364,8 +22614,7 @@
|
|||||||
"node_modules/process-nextick-args": {
|
"node_modules/process-nextick-args": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/progress": {
|
"node_modules/progress": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
@@ -24992,11 +25241,24 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sql.js": {
|
||||||
|
"version": "1.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.13.0.tgz",
|
||||||
|
"integrity": "sha512-RJbVP1HRDlUUXahJ7VMTcu9Rm1Nzw+EBpoPr94vnbD4LwR715F3CcxE2G2k45PewcaZ57pjetYa+LoSJLAASgA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/sqlite": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/sqlite/-/sqlite-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-oBkezXa2hnkfuJwUo44Hl9hS3er+YFtueifoajrgidvqsJRQFpc5fKoAkAor1O5ZnLoa28GBScfHXs8j0K358Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/sqlite3": {
|
"node_modules/sqlite3": {
|
||||||
"version": "5.1.7",
|
"version": "5.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz",
|
||||||
"integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==",
|
"integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bindings": "^1.5.0",
|
"bindings": "^1.5.0",
|
||||||
"node-addon-api": "^7.0.0",
|
"node-addon-api": "^7.0.0",
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
"electron:build-mac-universal": "npm run build:electron-prod && electron-builder --mac --universal"
|
"electron:build-mac-universal": "npm run build:electron-prod && electron-builder --mac --universal"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@capacitor-community/sqlite": "6.0.0",
|
||||||
"@capacitor-mlkit/barcode-scanning": "^6.0.0",
|
"@capacitor-mlkit/barcode-scanning": "^6.0.0",
|
||||||
"@capacitor/android": "^6.2.0",
|
"@capacitor/android": "^6.2.0",
|
||||||
"@capacitor/app": "^6.0.0",
|
"@capacitor/app": "^6.0.0",
|
||||||
@@ -115,6 +116,7 @@
|
|||||||
"reflect-metadata": "^0.1.14",
|
"reflect-metadata": "^0.1.14",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
"simple-vue-camera": "^1.1.3",
|
"simple-vue-camera": "^1.1.3",
|
||||||
|
"sqlite": "^5.1.1",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"stream-browserify": "^3.0.0",
|
"stream-browserify": "^3.0.0",
|
||||||
"three": "^0.156.1",
|
"three": "^0.156.1",
|
||||||
|
|||||||
@@ -99,8 +99,6 @@ import {
|
|||||||
LTileLayer,
|
LTileLayer,
|
||||||
} from "@vue-leaflet/vue-leaflet";
|
} from "@vue-leaflet/vue-leaflet";
|
||||||
import { Router } from "vue-router";
|
import { Router } from "vue-router";
|
||||||
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
|
||||||
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
@@ -122,7 +120,8 @@ export default class FeedFilters extends Vue {
|
|||||||
async open(onCloseIfChanged: () => void) {
|
async open(onCloseIfChanged: () => void) {
|
||||||
this.onCloseIfChanged = onCloseIfChanged;
|
this.onCloseIfChanged = onCloseIfChanged;
|
||||||
|
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const platform = this.$platform;
|
||||||
|
const settings = await platform.getActiveAccountSettings();
|
||||||
this.hasVisibleDid = !!settings.filterFeedByVisible;
|
this.hasVisibleDid = !!settings.filterFeedByVisible;
|
||||||
this.isNearby = !!settings.filterFeedByNearby;
|
this.isNearby = !!settings.filterFeedByNearby;
|
||||||
if (settings.searchBoxes && settings.searchBoxes.length > 0) {
|
if (settings.searchBoxes && settings.searchBoxes.length > 0) {
|
||||||
@@ -136,7 +135,8 @@ export default class FeedFilters extends Vue {
|
|||||||
async toggleHasVisibleDid() {
|
async toggleHasVisibleDid() {
|
||||||
this.settingChanged = true;
|
this.settingChanged = true;
|
||||||
this.hasVisibleDid = !this.hasVisibleDid;
|
this.hasVisibleDid = !this.hasVisibleDid;
|
||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
const platform = this.$platform;
|
||||||
|
await platform.updateMasterSettings({
|
||||||
filterFeedByVisible: this.hasVisibleDid,
|
filterFeedByVisible: this.hasVisibleDid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,8 @@ export default class FeedFilters extends Vue {
|
|||||||
async toggleNearby() {
|
async toggleNearby() {
|
||||||
this.settingChanged = true;
|
this.settingChanged = true;
|
||||||
this.isNearby = !this.isNearby;
|
this.isNearby = !this.isNearby;
|
||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
const platform = this.$platform;
|
||||||
|
await platform.updateMasterSettings({
|
||||||
filterFeedByNearby: this.isNearby,
|
filterFeedByNearby: this.isNearby,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -154,7 +155,8 @@ export default class FeedFilters extends Vue {
|
|||||||
this.settingChanged = true;
|
this.settingChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
const platform = this.$platform;
|
||||||
|
await platform.updateMasterSettings({
|
||||||
filterFeedByNearby: false,
|
filterFeedByNearby: false,
|
||||||
filterFeedByVisible: false,
|
filterFeedByVisible: false,
|
||||||
});
|
});
|
||||||
@@ -168,7 +170,8 @@ export default class FeedFilters extends Vue {
|
|||||||
this.settingChanged = true;
|
this.settingChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
const platform = this.$platform;
|
||||||
|
await platform.updateMasterSettings({
|
||||||
filterFeedByNearby: true,
|
filterFeedByNearby: true,
|
||||||
filterFeedByVisible: true,
|
filterFeedByVisible: true,
|
||||||
});
|
});
|
||||||
|
|||||||
130
src/libs/util.ts
130
src/libs/util.ts
@@ -6,29 +6,24 @@ import * as R from "ramda";
|
|||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
|
||||||
import { DEFAULT_PUSH_SERVER, NotificationIface } from "../constants/app";
|
import { DEFAULT_PUSH_SERVER, NotificationIface } from "../constants/app";
|
||||||
import {
|
import { retrieveSettingsForActiveAccount } from "../db/index";
|
||||||
accountsDBPromise,
|
|
||||||
retrieveSettingsForActiveAccount,
|
|
||||||
updateAccountSettings,
|
|
||||||
updateDefaultSettings,
|
|
||||||
} from "../db/index";
|
|
||||||
import databaseService from "../services/database";
|
|
||||||
import { Account } from "../db/tables/accounts";
|
import { Account } from "../db/tables/accounts";
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import { DEFAULT_PASSKEY_EXPIRATION_MINUTES } from "../db/tables/settings";
|
import { DEFAULT_PASSKEY_EXPIRATION_MINUTES } from "../db/tables/settings";
|
||||||
import { deriveAddress, generateSeed, newIdentifier } from "../libs/crypto";
|
import { deriveAddress, generateSeed, newIdentifier } from "../libs/crypto";
|
||||||
import * as serverUtil from "../libs/endorserServer";
|
import * as serverUtil from "../libs/endorserServer";
|
||||||
import {
|
import {
|
||||||
containsHiddenDid,
|
|
||||||
GenericCredWrapper,
|
GenericCredWrapper,
|
||||||
GenericVerifiableCredential,
|
GenericVerifiableCredential,
|
||||||
GiveSummaryRecord,
|
GiveSummaryRecord,
|
||||||
OfferVerifiableCredential,
|
OfferVerifiableCredential,
|
||||||
} from "../libs/endorserServer";
|
} from "../interfaces";
|
||||||
|
import { containsHiddenDid } from "../libs/endorserServer";
|
||||||
import { KeyMeta } from "../libs/crypto/vc";
|
import { KeyMeta } from "../libs/crypto/vc";
|
||||||
import { createPeerDid } from "../libs/crypto/vc/didPeer";
|
import { createPeerDid } from "../libs/crypto/vc/didPeer";
|
||||||
import { registerCredential } from "../libs/crypto/vc/passkeyDidPeer";
|
import { registerCredential } from "../libs/crypto/vc/passkeyDidPeer";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
|
import type { PlatformService } from "../services/PlatformService";
|
||||||
|
|
||||||
export interface GiverReceiverInputInfo {
|
export interface GiverReceiverInputInfo {
|
||||||
did?: string;
|
did?: string;
|
||||||
@@ -460,45 +455,38 @@ export function findAllVisibleToDids(
|
|||||||
|
|
||||||
export interface AccountKeyInfo extends Account, KeyMeta {}
|
export interface AccountKeyInfo extends Account, KeyMeta {}
|
||||||
|
|
||||||
export const retrieveAccountCount = async (): Promise<number> => {
|
export const retrieveAccountCount = async (
|
||||||
// one of the few times we use accountsDBPromise directly; try to avoid more usage
|
platform: PlatformService,
|
||||||
const accountsDB = await accountsDBPromise;
|
): Promise<number> => {
|
||||||
return await accountsDB.accounts.count();
|
const accounts = await platform.getAccounts();
|
||||||
|
return accounts.length;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const retrieveAccountDids = async (): Promise<string[]> => {
|
export const retrieveAccountDids = async (
|
||||||
// one of the few times we use accountsDBPromise directly; try to avoid more usage
|
platform: PlatformService,
|
||||||
const accountsDB = await accountsDBPromise;
|
): Promise<string[]> => {
|
||||||
const allAccounts = await accountsDB.accounts.toArray();
|
const accounts = await platform.getAccounts();
|
||||||
const allDids = allAccounts.map((acc) => acc.did);
|
return accounts.map((acc: Account) => acc.did);
|
||||||
return allDids;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is provided and recommended when the full key is not necessary so that
|
|
||||||
// future work could separate this info from the sensitive key material.
|
|
||||||
export const retrieveAccountMetadata = async (
|
export const retrieveAccountMetadata = async (
|
||||||
|
platform: PlatformService,
|
||||||
activeDid: string,
|
activeDid: string,
|
||||||
): Promise<AccountKeyInfo | undefined> => {
|
): Promise<AccountKeyInfo | undefined> => {
|
||||||
// one of the few times we use accountsDBPromise directly; try to avoid more usage
|
const account = await platform.getAccount(activeDid);
|
||||||
const accountsDB = await accountsDBPromise;
|
|
||||||
const account = (await accountsDB.accounts
|
|
||||||
.where("did")
|
|
||||||
.equals(activeDid)
|
|
||||||
.first()) as Account;
|
|
||||||
if (account) {
|
if (account) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { identity, mnemonic, ...metadata } = account;
|
const { identity, mnemonic, ...metadata } = account;
|
||||||
return metadata;
|
return metadata;
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const retrieveAllAccountsMetadata = async (): Promise<Account[]> => {
|
export const retrieveAllAccountsMetadata = async (
|
||||||
// one of the few times we use accountsDBPromise directly; try to avoid more usage
|
platform: PlatformService,
|
||||||
const accountsDB = await accountsDBPromise;
|
): Promise<Account[]> => {
|
||||||
const array = await accountsDB.accounts.toArray();
|
const accounts = await platform.getAccounts();
|
||||||
return array.map((account) => {
|
return accounts.map((account: Account) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { identity, mnemonic, ...metadata } = account;
|
const { identity, mnemonic, ...metadata } = account;
|
||||||
return metadata;
|
return metadata;
|
||||||
@@ -506,43 +494,30 @@ export const retrieveAllAccountsMetadata = async (): Promise<Account[]> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const retrieveFullyDecryptedAccount = async (
|
export const retrieveFullyDecryptedAccount = async (
|
||||||
|
platform: PlatformService,
|
||||||
activeDid: string,
|
activeDid: string,
|
||||||
): Promise<AccountKeyInfo | undefined> => {
|
): Promise<AccountKeyInfo | undefined> => {
|
||||||
// one of the few times we use accountsDBPromise directly; try to avoid more usage
|
return await platform.getAccount(activeDid);
|
||||||
const accountsDB = await accountsDBPromise;
|
|
||||||
const account = (await accountsDB.accounts
|
|
||||||
.where("did")
|
|
||||||
.equals(activeDid)
|
|
||||||
.first()) as Account;
|
|
||||||
return account;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// let's try and eliminate this
|
export const retrieveAllFullyDecryptedAccounts = async (
|
||||||
export const retrieveAllFullyDecryptedAccounts = async (): Promise<
|
platform: PlatformService,
|
||||||
Array<AccountKeyInfo>
|
): Promise<Array<AccountKeyInfo>> => {
|
||||||
> => {
|
return await platform.getAccounts();
|
||||||
const accountsDB = await accountsDBPromise;
|
|
||||||
const allAccounts = await accountsDB.accounts.toArray();
|
|
||||||
return allAccounts;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export const generateSaveAndActivateIdentity = async (
|
||||||
* Generates a new identity, saves it to the database, and sets it as the active identity.
|
platform: PlatformService,
|
||||||
* @return {Promise<string>} with the DID of the new identity
|
): Promise<string> => {
|
||||||
*/
|
|
||||||
export const generateSaveAndActivateIdentity = async (): Promise<string> => {
|
|
||||||
const mnemonic = generateSeed();
|
const mnemonic = generateSeed();
|
||||||
// address is 0x... ETH address, without "did:eth:"
|
|
||||||
const [address, privateHex, publicHex, derivationPath] =
|
const [address, privateHex, publicHex, derivationPath] =
|
||||||
deriveAddress(mnemonic);
|
deriveAddress(mnemonic);
|
||||||
|
|
||||||
const newId = newIdentifier(address, publicHex, privateHex, derivationPath);
|
const newId = newIdentifier(address, publicHex, privateHex, derivationPath);
|
||||||
const identity = JSON.stringify(newId);
|
const identity = JSON.stringify(newId);
|
||||||
|
|
||||||
// one of the few times we use accountsDBPromise directly; try to avoid more usage
|
|
||||||
try {
|
try {
|
||||||
const accountsDB = await accountsDBPromise;
|
await platform.addAccount({
|
||||||
await accountsDB.accounts.add({
|
|
||||||
dateCreated: new Date().toISOString(),
|
dateCreated: new Date().toISOString(),
|
||||||
derivationPath: derivationPath,
|
derivationPath: derivationPath,
|
||||||
did: newId.did,
|
did: newId.did,
|
||||||
@@ -551,32 +526,19 @@ export const generateSaveAndActivateIdentity = async (): Promise<string> => {
|
|||||||
publicKeyHex: newId.keys[0].publicKeyHex,
|
publicKeyHex: newId.keys[0].publicKeyHex,
|
||||||
});
|
});
|
||||||
|
|
||||||
// add to the new sql db
|
await platform.updateMasterSettings({ activeDid: newId.did });
|
||||||
await databaseService.run(
|
await platform.updateAccountSettings(newId.did, { isRegistered: false });
|
||||||
`INSERT INTO accounts (dateCreated, derivationPath, did, identity, mnemonic, publicKeyHex)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
||||||
[
|
|
||||||
new Date().toISOString(),
|
|
||||||
derivationPath,
|
|
||||||
newId.did,
|
|
||||||
identity,
|
|
||||||
mnemonic,
|
|
||||||
newId.keys[0].publicKeyHex,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
await updateDefaultSettings({ activeDid: newId.did });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Failed to update default settings:", error);
|
logger.error("Failed to save new identity:", error);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Failed to set default settings. Please try again or restart the app.",
|
"Failed to save new identity. Please try again or restart the app.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await updateAccountSettings(newId.did, { isRegistered: false });
|
|
||||||
return newId.did;
|
return newId.did;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const registerAndSavePasskey = async (
|
export const registerAndSavePasskey = async (
|
||||||
|
platform: PlatformService,
|
||||||
keyName: string,
|
keyName: string,
|
||||||
): Promise<Account> => {
|
): Promise<Account> => {
|
||||||
const cred = await registerCredential(keyName);
|
const cred = await registerCredential(keyName);
|
||||||
@@ -590,23 +552,25 @@ export const registerAndSavePasskey = async (
|
|||||||
passkeyCredIdHex,
|
passkeyCredIdHex,
|
||||||
publicKeyHex: Buffer.from(publicKeyBytes).toString("hex"),
|
publicKeyHex: Buffer.from(publicKeyBytes).toString("hex"),
|
||||||
};
|
};
|
||||||
// one of the few times we use accountsDBPromise directly; try to avoid more usage
|
|
||||||
const accountsDB = await accountsDBPromise;
|
await platform.addAccount(account);
|
||||||
await accountsDB.accounts.add(account);
|
|
||||||
return account;
|
return account;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const registerSaveAndActivatePasskey = async (
|
export const registerSaveAndActivatePasskey = async (
|
||||||
|
platform: PlatformService,
|
||||||
keyName: string,
|
keyName: string,
|
||||||
): Promise<Account> => {
|
): Promise<Account> => {
|
||||||
const account = await registerAndSavePasskey(keyName);
|
const account = await registerAndSavePasskey(platform, keyName);
|
||||||
await updateDefaultSettings({ activeDid: account.did });
|
await platform.updateMasterSettings({ activeDid: account.did });
|
||||||
await updateAccountSettings(account.did, { isRegistered: false });
|
await platform.updateAccountSettings(account.did, { isRegistered: false });
|
||||||
return account;
|
return account;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPasskeyExpirationSeconds = async (): Promise<number> => {
|
export const getPasskeyExpirationSeconds = async (
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
platform: PlatformService,
|
||||||
|
): Promise<number> => {
|
||||||
|
const settings = await platform.getActiveAccountSettings();
|
||||||
return (
|
return (
|
||||||
(settings?.passkeyExpirationMinutes ?? DEFAULT_PASSKEY_EXPIRATION_MINUTES) *
|
(settings?.passkeyExpirationMinutes ?? DEFAULT_PASSKEY_EXPIRATION_MINUTES) *
|
||||||
60
|
60
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import "./assets/styles/tailwind.css";
|
|||||||
import { FontAwesomeIcon } from "./libs/fontawesome";
|
import { FontAwesomeIcon } from "./libs/fontawesome";
|
||||||
import Camera from "simple-vue-camera";
|
import Camera from "simple-vue-camera";
|
||||||
import { logger } from "./utils/logger";
|
import { logger } from "./utils/logger";
|
||||||
|
import { PlatformServiceFactory } from "./services/PlatformServiceFactory";
|
||||||
|
|
||||||
// Global Error Handler
|
// Global Error Handler
|
||||||
function setupGlobalErrorHandler(app: VueApp) {
|
function setupGlobalErrorHandler(app: VueApp) {
|
||||||
@@ -54,6 +55,16 @@ export function initializeApp() {
|
|||||||
app.use(Notifications);
|
app.use(Notifications);
|
||||||
logger.log("[App Init] Notifications initialized");
|
logger.log("[App Init] Notifications initialized");
|
||||||
|
|
||||||
|
app.config.globalProperties.$platform = PlatformServiceFactory.getInstance();
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const platform = app.config.globalProperties.$platform;
|
||||||
|
const sqlite = await platform.getSQLite();
|
||||||
|
const config = { name: "TimeSafariDB", useWAL: true }; // (or your desired config)
|
||||||
|
await sqlite.initialize(config);
|
||||||
|
logger.log("[App Init] SQLite database initialized.");
|
||||||
|
})();
|
||||||
|
|
||||||
setupGlobalErrorHandler(app);
|
setupGlobalErrorHandler(app);
|
||||||
logger.log("[App Init] App initialization complete");
|
logger.log("[App Init] App initialization complete");
|
||||||
|
|
||||||
|
|||||||
139
src/services/CapacitorPlatformService.ts
Normal file
139
src/services/CapacitorPlatformService.ts
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import {
|
||||||
|
PlatformService,
|
||||||
|
PlatformCapabilities,
|
||||||
|
SQLiteOperations,
|
||||||
|
} from "./PlatformService";
|
||||||
|
import { CapacitorSQLiteService } from "./sqlite/CapacitorSQLiteService";
|
||||||
|
import { Capacitor } from "@capacitor/core";
|
||||||
|
import { Camera } from "@capacitor/camera";
|
||||||
|
import { Filesystem, Directory } from "@capacitor/filesystem";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
|
export class CapacitorPlatformService implements PlatformService {
|
||||||
|
private sqliteService: CapacitorSQLiteService | null = null;
|
||||||
|
|
||||||
|
getCapabilities(): PlatformCapabilities {
|
||||||
|
const platform = Capacitor.getPlatform();
|
||||||
|
return {
|
||||||
|
hasFileSystem: true,
|
||||||
|
hasCamera: true,
|
||||||
|
isMobile: true,
|
||||||
|
isIOS: platform === "ios",
|
||||||
|
hasFileDownload: true,
|
||||||
|
needsFileHandlingInstructions: false,
|
||||||
|
sqlite: {
|
||||||
|
supported: true,
|
||||||
|
runsInWorker: false,
|
||||||
|
hasSharedArrayBuffer: false,
|
||||||
|
supportsWAL: true,
|
||||||
|
maxSize: 1024 * 1024 * 1024 * 2, // 2GB limit for mobile SQLite
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSQLite(): Promise<SQLiteOperations> {
|
||||||
|
if (!this.sqliteService) {
|
||||||
|
this.sqliteService = new CapacitorSQLiteService();
|
||||||
|
}
|
||||||
|
return this.sqliteService;
|
||||||
|
}
|
||||||
|
|
||||||
|
async readFile(path: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
const result = await Filesystem.readFile({
|
||||||
|
path,
|
||||||
|
directory: Directory.Data,
|
||||||
|
});
|
||||||
|
return result.data;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to read file:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeFile(path: string, content: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
await Filesystem.writeFile({
|
||||||
|
path,
|
||||||
|
data: content,
|
||||||
|
directory: Directory.Data,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to write file:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteFile(path: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
await Filesystem.deleteFile({
|
||||||
|
path,
|
||||||
|
directory: Directory.Data,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to delete file:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async listFiles(directory: string): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
const result = await Filesystem.readdir({
|
||||||
|
path: directory,
|
||||||
|
directory: Directory.Data,
|
||||||
|
});
|
||||||
|
return result.files.map((file) => file.name);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to list files:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async takePicture(): Promise<{ blob: Blob; fileName: string }> {
|
||||||
|
try {
|
||||||
|
const image = await Camera.getPhoto({
|
||||||
|
quality: 90,
|
||||||
|
allowEditing: true,
|
||||||
|
resultType: "base64",
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`data:image/jpeg;base64,${image.base64String}`,
|
||||||
|
);
|
||||||
|
const blob = await response.blob();
|
||||||
|
const fileName = `photo_${Date.now()}.jpg`;
|
||||||
|
|
||||||
|
return { blob, fileName };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to take picture:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async pickImage(): Promise<{ blob: Blob; fileName: string }> {
|
||||||
|
try {
|
||||||
|
const image = await Camera.getPhoto({
|
||||||
|
quality: 90,
|
||||||
|
allowEditing: true,
|
||||||
|
resultType: "base64",
|
||||||
|
source: "PHOTOLIBRARY",
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`data:image/jpeg;base64,${image.base64String}`,
|
||||||
|
);
|
||||||
|
const blob = await response.blob();
|
||||||
|
const fileName = `image_${Date.now()}.jpg`;
|
||||||
|
|
||||||
|
return { blob, fileName };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to pick image:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleDeepLink(url: string): Promise<void> {
|
||||||
|
// Implement deep link handling for Capacitor platform
|
||||||
|
logger.info("Handling deep link:", url);
|
||||||
|
}
|
||||||
|
}
|
||||||
295
src/services/ElectronPlatformService.ts
Normal file
295
src/services/ElectronPlatformService.ts
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
import {
|
||||||
|
PlatformService,
|
||||||
|
PlatformCapabilities,
|
||||||
|
SQLiteOperations,
|
||||||
|
SQLiteConfig,
|
||||||
|
PreparedStatement,
|
||||||
|
} from "./PlatformService";
|
||||||
|
import { BaseSQLiteService } from "./sqlite/BaseSQLiteService";
|
||||||
|
import { app } from "electron";
|
||||||
|
import { dialog } from "electron";
|
||||||
|
import { promises as fs } from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
import sqlite3 from "sqlite3";
|
||||||
|
import { open, Database } from "sqlite";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQLite implementation for Electron using native sqlite3
|
||||||
|
*/
|
||||||
|
class ElectronSQLiteService extends BaseSQLiteService {
|
||||||
|
private db: Database | null = null;
|
||||||
|
private config: SQLiteConfig | null = null;
|
||||||
|
|
||||||
|
async initialize(config: SQLiteConfig): Promise<void> {
|
||||||
|
if (this.initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.config = config;
|
||||||
|
const dbPath = join(app.getPath("userData"), `${config.name}.db`);
|
||||||
|
|
||||||
|
this.db = await open({
|
||||||
|
filename: dbPath,
|
||||||
|
driver: sqlite3.Database,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configure database settings
|
||||||
|
if (config.useWAL) {
|
||||||
|
await this.execute("PRAGMA journal_mode = WAL");
|
||||||
|
this.stats.walMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set other pragmas for performance
|
||||||
|
await this.execute("PRAGMA synchronous = NORMAL");
|
||||||
|
await this.execute("PRAGMA temp_store = MEMORY");
|
||||||
|
await this.execute("PRAGMA cache_size = -2000"); // Use 2MB of cache
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
await this.updateStats();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to initialize Electron SQLite:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async close(): Promise<void> {
|
||||||
|
if (!this.initialized || !this.db) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.db.close();
|
||||||
|
this.db = null;
|
||||||
|
this.initialized = false;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to close Electron SQLite connection:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _executeQuery<T>(
|
||||||
|
sql: string,
|
||||||
|
params: unknown[] = [],
|
||||||
|
operation: "query" | "execute" = "query",
|
||||||
|
): Promise<SQLiteResult<T>> {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (operation === "query") {
|
||||||
|
const rows = await this.db.all<T>(sql, params);
|
||||||
|
const result = await this.db.run("SELECT last_insert_rowid() as id");
|
||||||
|
return {
|
||||||
|
rows,
|
||||||
|
rowsAffected: this.db.changes,
|
||||||
|
lastInsertId: result.lastID,
|
||||||
|
executionTime: 0, // Will be set by base class
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const result = await this.db.run(sql, params);
|
||||||
|
return {
|
||||||
|
rows: [],
|
||||||
|
rowsAffected: this.db.changes,
|
||||||
|
lastInsertId: result.lastID,
|
||||||
|
executionTime: 0, // Will be set by base class
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Electron SQLite query failed:", {
|
||||||
|
sql,
|
||||||
|
params,
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _beginTransaction(): Promise<void> {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
await this.db.run("BEGIN TRANSACTION");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _commitTransaction(): Promise<void> {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
await this.db.run("COMMIT");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _rollbackTransaction(): Promise<void> {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
await this.db.run("ROLLBACK");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _prepareStatement<T>(
|
||||||
|
sql: string,
|
||||||
|
): Promise<PreparedStatement<T>> {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
const stmt = await this.db.prepare(sql);
|
||||||
|
return {
|
||||||
|
execute: async (params: unknown[] = []) => {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = await stmt.all<T>(params);
|
||||||
|
return {
|
||||||
|
rows,
|
||||||
|
rowsAffected: this.db.changes,
|
||||||
|
lastInsertId: (await this.db.run("SELECT last_insert_rowid() as id"))
|
||||||
|
.lastID,
|
||||||
|
executionTime: 0, // Will be set by base class
|
||||||
|
};
|
||||||
|
},
|
||||||
|
finalize: async () => {
|
||||||
|
await stmt.finalize();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _finalizeStatement(_sql: string): Promise<void> {
|
||||||
|
// Statements are finalized when the PreparedStatement is finalized
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDatabaseSize(): Promise<number> {
|
||||||
|
if (!this.db || !this.config) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dbPath = join(app.getPath("userData"), `${this.config.name}.db`);
|
||||||
|
const stats = await fs.stat(dbPath);
|
||||||
|
return stats.size;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to get database size:", error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ElectronPlatformService implements PlatformService {
|
||||||
|
private sqliteService: ElectronSQLiteService | null = null;
|
||||||
|
|
||||||
|
getCapabilities(): PlatformCapabilities {
|
||||||
|
return {
|
||||||
|
hasFileSystem: true,
|
||||||
|
hasCamera: true,
|
||||||
|
isMobile: false,
|
||||||
|
isIOS: false,
|
||||||
|
hasFileDownload: true,
|
||||||
|
needsFileHandlingInstructions: false,
|
||||||
|
sqlite: {
|
||||||
|
supported: true,
|
||||||
|
runsInWorker: false,
|
||||||
|
hasSharedArrayBuffer: true,
|
||||||
|
supportsWAL: true,
|
||||||
|
maxSize: 1024 * 1024 * 1024 * 10, // 10GB limit for desktop SQLite
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSQLite(): Promise<SQLiteOperations> {
|
||||||
|
if (!this.sqliteService) {
|
||||||
|
this.sqliteService = new ElectronSQLiteService();
|
||||||
|
}
|
||||||
|
return this.sqliteService;
|
||||||
|
}
|
||||||
|
|
||||||
|
async readFile(path: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
return await fs.readFile(path, "utf-8");
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to read file:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeFile(path: string, content: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
await fs.writeFile(path, content, "utf-8");
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to write file:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteFile(path: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
await fs.unlink(path);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to delete file:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async listFiles(directory: string): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
const files = await fs.readdir(directory);
|
||||||
|
return files;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to list files:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async takePicture(): Promise<{ blob: Blob; fileName: string }> {
|
||||||
|
try {
|
||||||
|
const { canceled, filePaths } = await dialog.showOpenDialog({
|
||||||
|
properties: ["openFile"],
|
||||||
|
filters: [{ name: "Images", extensions: ["jpg", "png", "gif"] }],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled || !filePaths.length) {
|
||||||
|
throw new Error("No image selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = filePaths[0];
|
||||||
|
const buffer = await fs.readFile(filePath);
|
||||||
|
const blob = new Blob([buffer], { type: "image/jpeg" });
|
||||||
|
const fileName = `photo_${Date.now()}.jpg`;
|
||||||
|
|
||||||
|
return { blob, fileName };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to take picture:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async pickImage(): Promise<{ blob: Blob; fileName: string }> {
|
||||||
|
try {
|
||||||
|
const { canceled, filePaths } = await dialog.showOpenDialog({
|
||||||
|
properties: ["openFile"],
|
||||||
|
filters: [{ name: "Images", extensions: ["jpg", "png", "gif"] }],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled || !filePaths.length) {
|
||||||
|
throw new Error("No image selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = filePaths[0];
|
||||||
|
const buffer = await fs.readFile(filePath);
|
||||||
|
const blob = new Blob([buffer], { type: "image/jpeg" });
|
||||||
|
const fileName = `image_${Date.now()}.jpg`;
|
||||||
|
|
||||||
|
return { blob, fileName };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to pick image:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleDeepLink(url: string): Promise<void> {
|
||||||
|
// Implement deep link handling for Electron platform
|
||||||
|
logger.info("Handling deep link:", url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { Settings } from "../db/tables/settings";
|
||||||
|
import { Account } from "../db/tables/accounts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the result of an image capture or selection operation.
|
* Represents the result of an image capture or selection operation.
|
||||||
* Contains both the image data as a Blob and the associated filename.
|
* Contains both the image data as a Blob and the associated filename.
|
||||||
@@ -26,6 +29,154 @@ export interface PlatformCapabilities {
|
|||||||
hasFileDownload: boolean;
|
hasFileDownload: boolean;
|
||||||
/** Whether the platform requires special file handling instructions */
|
/** Whether the platform requires special file handling instructions */
|
||||||
needsFileHandlingInstructions: boolean;
|
needsFileHandlingInstructions: boolean;
|
||||||
|
/** SQLite capabilities of the platform */
|
||||||
|
sqlite: {
|
||||||
|
/** Whether SQLite is supported on this platform */
|
||||||
|
supported: boolean;
|
||||||
|
/** Whether SQLite runs in a Web Worker (browser) */
|
||||||
|
runsInWorker: boolean;
|
||||||
|
/** Whether the platform supports SharedArrayBuffer (required for optimal performance) */
|
||||||
|
hasSharedArrayBuffer: boolean;
|
||||||
|
/** Whether the platform supports WAL mode */
|
||||||
|
supportsWAL: boolean;
|
||||||
|
/** Maximum database size in bytes (if known) */
|
||||||
|
maxSize?: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQLite configuration options
|
||||||
|
*/
|
||||||
|
export interface SQLiteConfig {
|
||||||
|
/** Database name */
|
||||||
|
name: string;
|
||||||
|
/** Whether to use WAL mode (if supported) */
|
||||||
|
useWAL?: boolean;
|
||||||
|
/** Whether to use memory-mapped I/O (if supported) */
|
||||||
|
useMMap?: boolean;
|
||||||
|
/** Size of memory map in bytes (if using mmap) */
|
||||||
|
mmapSize?: number;
|
||||||
|
/** Whether to use prepared statements cache */
|
||||||
|
usePreparedStatements?: boolean;
|
||||||
|
/** Maximum number of prepared statements to cache */
|
||||||
|
maxPreparedStatements?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a SQLite query result with typed rows
|
||||||
|
*/
|
||||||
|
export interface SQLiteResult<T> {
|
||||||
|
/** The rows returned by the query */
|
||||||
|
rows: T[];
|
||||||
|
/** The number of rows affected by the query */
|
||||||
|
rowsAffected: number;
|
||||||
|
/** The last inserted row ID (if applicable) */
|
||||||
|
lastInsertId?: number;
|
||||||
|
/** Execution time in milliseconds */
|
||||||
|
executionTime: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQLite operations interface for platform-agnostic database access
|
||||||
|
*/
|
||||||
|
export interface SQLiteOperations {
|
||||||
|
/**
|
||||||
|
* Initializes the SQLite database with the given configuration
|
||||||
|
* @param config - SQLite configuration options
|
||||||
|
* @returns Promise resolving when initialization is complete
|
||||||
|
*/
|
||||||
|
initialize(config: SQLiteConfig): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a SQL query and returns typed results
|
||||||
|
* @param sql - The SQL query to execute
|
||||||
|
* @param params - Optional parameters for the query
|
||||||
|
* @returns Promise resolving to the query results
|
||||||
|
*/
|
||||||
|
query<T>(sql: string, params?: unknown[]): Promise<SQLiteResult<T>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a SQL query that modifies data (INSERT, UPDATE, DELETE)
|
||||||
|
* @param sql - The SQL query to execute
|
||||||
|
* @param params - Optional parameters for the query
|
||||||
|
* @returns Promise resolving to the number of rows affected
|
||||||
|
*/
|
||||||
|
execute(sql: string, params?: unknown[]): Promise<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes multiple SQL statements in a transaction
|
||||||
|
* @param statements - Array of SQL statements to execute
|
||||||
|
* @returns Promise resolving when the transaction is complete
|
||||||
|
*/
|
||||||
|
transaction(statements: { sql: string; params?: unknown[] }[]): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the maximum value of a column for matching rows
|
||||||
|
* @param table - The table to query
|
||||||
|
* @param column - The column to find the maximum value of
|
||||||
|
* @param where - Optional WHERE clause conditions
|
||||||
|
* @param params - Optional parameters for the WHERE clause
|
||||||
|
* @returns Promise resolving to the maximum value
|
||||||
|
*/
|
||||||
|
getMaxValue<T>(
|
||||||
|
table: string,
|
||||||
|
column: string,
|
||||||
|
where?: string,
|
||||||
|
params?: unknown[],
|
||||||
|
): Promise<T | null>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares a SQL statement for repeated execution
|
||||||
|
* @param sql - The SQL statement to prepare
|
||||||
|
* @returns A prepared statement that can be executed multiple times
|
||||||
|
*/
|
||||||
|
prepare<T>(sql: string): Promise<PreparedStatement<T>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current database size in bytes
|
||||||
|
* @returns Promise resolving to the database size
|
||||||
|
*/
|
||||||
|
getDatabaseSize(): Promise<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current database statistics
|
||||||
|
* @returns Promise resolving to database statistics
|
||||||
|
*/
|
||||||
|
getStats(): Promise<SQLiteStats>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the database connection
|
||||||
|
* @returns Promise resolving when the connection is closed
|
||||||
|
*/
|
||||||
|
close(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a prepared SQL statement
|
||||||
|
*/
|
||||||
|
export interface PreparedStatement<T> {
|
||||||
|
/** Executes the prepared statement with the given parameters */
|
||||||
|
execute(params?: unknown[]): Promise<SQLiteResult<T>>;
|
||||||
|
/** Frees the prepared statement */
|
||||||
|
finalize(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database statistics
|
||||||
|
*/
|
||||||
|
export interface SQLiteStats {
|
||||||
|
/** Total number of queries executed */
|
||||||
|
totalQueries: number;
|
||||||
|
/** Average query execution time in milliseconds */
|
||||||
|
avgExecutionTime: number;
|
||||||
|
/** Number of prepared statements in cache */
|
||||||
|
preparedStatements: number;
|
||||||
|
/** Current database size in bytes */
|
||||||
|
databaseSize: number;
|
||||||
|
/** Whether WAL mode is active */
|
||||||
|
walMode: boolean;
|
||||||
|
/** Whether memory mapping is active */
|
||||||
|
mmapActive: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,11 +210,12 @@ 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.
|
||||||
|
* Optional method - not all platforms need to implement this.
|
||||||
* @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
|
* @returns Promise that resolves when the write is complete
|
||||||
*/
|
*/
|
||||||
writeAndShareFile(fileName: string, content: string): Promise<void>;
|
writeAndShareFile?(fileName: string, content: string): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a file at the specified path.
|
* Deletes a file at the specified path.
|
||||||
@@ -98,4 +250,57 @@ export interface PlatformService {
|
|||||||
* @returns Promise that resolves when the deep link has been handled
|
* @returns Promise that resolves when the deep link has been handled
|
||||||
*/
|
*/
|
||||||
handleDeepLink(url: string): Promise<void>;
|
handleDeepLink(url: string): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the SQLite operations interface for the platform.
|
||||||
|
* For browsers, this will use absurd-sql with Web Worker support.
|
||||||
|
* @returns Promise resolving to the SQLite operations interface
|
||||||
|
*/
|
||||||
|
getSQLite?(): Promise<SQLiteOperations>;
|
||||||
|
|
||||||
|
// Account Management
|
||||||
|
/**
|
||||||
|
* Gets all accounts in the database
|
||||||
|
* @returns Promise resolving to array of accounts
|
||||||
|
*/
|
||||||
|
getAccounts(): Promise<Account[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a specific account by DID
|
||||||
|
* @param did - The DID of the account to retrieve
|
||||||
|
* @returns Promise resolving to the account or undefined if not found
|
||||||
|
*/
|
||||||
|
getAccount(did: string): Promise<Account | undefined>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new account to the database
|
||||||
|
* @param account - The account to add
|
||||||
|
* @returns Promise resolving when the account is added
|
||||||
|
*/
|
||||||
|
addAccount(account: Account): Promise<void>;
|
||||||
|
|
||||||
|
// Settings Management
|
||||||
|
/**
|
||||||
|
* Updates the master settings with the provided changes
|
||||||
|
* @param settingsChanges - The settings to update
|
||||||
|
* @returns Promise resolving when the update is complete
|
||||||
|
*/
|
||||||
|
updateMasterSettings(settingsChanges: Partial<Settings>): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the settings for the active account
|
||||||
|
* @returns Promise resolving to the active account settings
|
||||||
|
*/
|
||||||
|
getActiveAccountSettings(): Promise<Settings>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates settings for a specific account
|
||||||
|
* @param accountDid - The DID of the account to update settings for
|
||||||
|
* @param settingsChanges - The settings to update
|
||||||
|
* @returns Promise resolving when the update is complete
|
||||||
|
*/
|
||||||
|
updateAccountSettings(
|
||||||
|
accountDid: string,
|
||||||
|
settingsChanges: Partial<Settings>,
|
||||||
|
): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
43
src/services/WebPlatformService.ts
Normal file
43
src/services/WebPlatformService.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import {
|
||||||
|
PlatformService,
|
||||||
|
PlatformCapabilities,
|
||||||
|
SQLiteOperations,
|
||||||
|
} from "./PlatformService";
|
||||||
|
import { AbsurdSQLService } from "./sqlite/AbsurdSQLService";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
|
export class WebPlatformService implements PlatformService {
|
||||||
|
private sqliteService: AbsurdSQLService | null = null;
|
||||||
|
|
||||||
|
getCapabilities(): PlatformCapabilities {
|
||||||
|
return {
|
||||||
|
hasFileSystem: false,
|
||||||
|
hasCamera: "mediaDevices" in navigator,
|
||||||
|
isMobile: /iPhone|iPad|iPod|Android/i.test(navigator.userAgent),
|
||||||
|
isIOS: /iPhone|iPad|iPod/i.test(navigator.userAgent),
|
||||||
|
hasFileDownload: true,
|
||||||
|
needsFileHandlingInstructions: true,
|
||||||
|
sqlite: {
|
||||||
|
supported: true,
|
||||||
|
runsInWorker: true,
|
||||||
|
hasSharedArrayBuffer: typeof SharedArrayBuffer !== "undefined",
|
||||||
|
supportsWAL: true,
|
||||||
|
maxSize: 1024 * 1024 * 1024, // 1GB limit for IndexedDB
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSQLite(): Promise<SQLiteOperations> {
|
||||||
|
if (!this.sqliteService) {
|
||||||
|
this.sqliteService = new AbsurdSQLService();
|
||||||
|
}
|
||||||
|
return this.sqliteService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... existing file system and camera methods ...
|
||||||
|
|
||||||
|
async handleDeepLink(url: string): Promise<void> {
|
||||||
|
// Implement deep link handling for web platform
|
||||||
|
logger.info("Handling deep link:", url);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,9 @@ import { Filesystem, Directory, Encoding } from "@capacitor/filesystem";
|
|||||||
import { Camera, CameraResultType, CameraSource } from "@capacitor/camera";
|
import { Camera, CameraResultType, CameraSource } from "@capacitor/camera";
|
||||||
import { Share } from "@capacitor/share";
|
import { Share } from "@capacitor/share";
|
||||||
import { logger } from "../../utils/logger";
|
import { logger } from "../../utils/logger";
|
||||||
|
import { Account } from "../../db/tables/accounts";
|
||||||
|
import { Settings } from "../../db/tables/settings";
|
||||||
|
import { db } from "../../db";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Platform service implementation for Capacitor (mobile) platform.
|
* Platform service implementation for Capacitor (mobile) platform.
|
||||||
@@ -476,4 +479,35 @@ export class CapacitorPlatformService implements PlatformService {
|
|||||||
// This is just a placeholder for the interface
|
// This is just a placeholder for the interface
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Account Management
|
||||||
|
async getAccounts(): Promise<Account[]> {
|
||||||
|
return await db.accounts.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAccount(did: string): Promise<Account | undefined> {
|
||||||
|
return await db.accounts.where("did").equals(did).first();
|
||||||
|
}
|
||||||
|
|
||||||
|
async addAccount(account: Account): Promise<void> {
|
||||||
|
await db.accounts.add(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings Management
|
||||||
|
async updateMasterSettings(
|
||||||
|
settingsChanges: Partial<Settings>,
|
||||||
|
): Promise<void> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
async getActiveAccountSettings(): Promise<Settings> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAccountSettings(
|
||||||
|
accountDid: string,
|
||||||
|
settingsChanges: Partial<Settings>,
|
||||||
|
): Promise<void> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import {
|
|||||||
PlatformCapabilities,
|
PlatformCapabilities,
|
||||||
} from "../PlatformService";
|
} from "../PlatformService";
|
||||||
import { logger } from "../../utils/logger";
|
import { logger } from "../../utils/logger";
|
||||||
|
import { Account } from "../../db/tables/accounts";
|
||||||
|
import { Settings } from "../../db/tables/settings";
|
||||||
|
import { db } from "../../db";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Platform service implementation for Electron (desktop) platform.
|
* Platform service implementation for Electron (desktop) platform.
|
||||||
@@ -108,4 +111,35 @@ export class ElectronPlatformService implements PlatformService {
|
|||||||
logger.error("handleDeepLink not implemented in Electron platform");
|
logger.error("handleDeepLink not implemented in Electron platform");
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Account Management
|
||||||
|
async getAccounts(): Promise<Account[]> {
|
||||||
|
return await db.accounts.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAccount(did: string): Promise<Account | undefined> {
|
||||||
|
return await db.accounts.where("did").equals(did).first();
|
||||||
|
}
|
||||||
|
|
||||||
|
async addAccount(account: Account): Promise<void> {
|
||||||
|
await db.accounts.add(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings Management
|
||||||
|
async updateMasterSettings(
|
||||||
|
settingsChanges: Partial<Settings>,
|
||||||
|
): Promise<void> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
async getActiveAccountSettings(): Promise<Settings> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAccountSettings(
|
||||||
|
accountDid: string,
|
||||||
|
settingsChanges: Partial<Settings>,
|
||||||
|
): Promise<void> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import {
|
|||||||
PlatformCapabilities,
|
PlatformCapabilities,
|
||||||
} from "../PlatformService";
|
} from "../PlatformService";
|
||||||
import { logger } from "../../utils/logger";
|
import { logger } from "../../utils/logger";
|
||||||
|
import { Account } from "../../db/tables/accounts";
|
||||||
|
import { Settings } from "../../db/tables/settings";
|
||||||
|
import { db } from "../../db";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Platform service implementation for PyWebView platform.
|
* Platform service implementation for PyWebView platform.
|
||||||
@@ -109,4 +112,35 @@ export class PyWebViewPlatformService implements PlatformService {
|
|||||||
logger.error("handleDeepLink not implemented in PyWebView platform");
|
logger.error("handleDeepLink not implemented in PyWebView platform");
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Account Management
|
||||||
|
async getAccounts(): Promise<Account[]> {
|
||||||
|
return await db.accounts.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAccount(did: string): Promise<Account | undefined> {
|
||||||
|
return await db.accounts.where("did").equals(did).first();
|
||||||
|
}
|
||||||
|
|
||||||
|
async addAccount(account: Account): Promise<void> {
|
||||||
|
await db.accounts.add(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings Management
|
||||||
|
async updateMasterSettings(
|
||||||
|
settingsChanges: Partial<Settings>,
|
||||||
|
): Promise<void> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
async getActiveAccountSettings(): Promise<Settings> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAccountSettings(
|
||||||
|
accountDid: string,
|
||||||
|
settingsChanges: Partial<Settings>,
|
||||||
|
): Promise<void> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ import {
|
|||||||
PlatformService,
|
PlatformService,
|
||||||
PlatformCapabilities,
|
PlatformCapabilities,
|
||||||
} from "../PlatformService";
|
} from "../PlatformService";
|
||||||
|
import { Settings } from "../../db/tables/settings";
|
||||||
|
import { MASTER_SETTINGS_KEY } from "../../db/tables/settings";
|
||||||
|
import { db } from "../../db";
|
||||||
import { logger } from "../../utils/logger";
|
import { logger } from "../../utils/logger";
|
||||||
|
import { Account } from "../../db/tables/accounts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Platform service implementation for web browser platform.
|
* Platform service implementation for web browser platform.
|
||||||
@@ -359,4 +363,67 @@ export class WebPlatformService implements PlatformService {
|
|||||||
async writeAndShareFile(_fileName: string, _content: string): Promise<void> {
|
async writeAndShareFile(_fileName: string, _content: string): Promise<void> {
|
||||||
throw new Error("File system access not available in web platform");
|
throw new Error("File system access not available in web platform");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateMasterSettings(
|
||||||
|
settingsChanges: Partial<Settings>,
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
delete settingsChanges.accountDid; // just in case
|
||||||
|
delete settingsChanges.id; // ensure there is no "id" that would override the key
|
||||||
|
|
||||||
|
await db.settings.update(MASTER_SETTINGS_KEY, settingsChanges);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error updating master settings:", error);
|
||||||
|
throw new Error(
|
||||||
|
`Failed to update settings. We recommend you try again or restart the app.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getActiveAccountSettings(): Promise<Settings> {
|
||||||
|
const defaultSettings = (await db.settings.get(MASTER_SETTINGS_KEY)) || {};
|
||||||
|
if (!defaultSettings.activeDid) {
|
||||||
|
return defaultSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
const overrideSettings =
|
||||||
|
(await db.settings
|
||||||
|
.where("accountDid")
|
||||||
|
.equals(defaultSettings.activeDid)
|
||||||
|
.first()) || {};
|
||||||
|
|
||||||
|
return { ...defaultSettings, ...overrideSettings };
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAccountSettings(
|
||||||
|
accountDid: string,
|
||||||
|
settingsChanges: Partial<Settings>,
|
||||||
|
): Promise<void> {
|
||||||
|
settingsChanges.accountDid = accountDid;
|
||||||
|
delete settingsChanges.id; // key off account, not ID
|
||||||
|
|
||||||
|
const result = await db.settings
|
||||||
|
.where("accountDid")
|
||||||
|
.equals(accountDid)
|
||||||
|
.modify(settingsChanges);
|
||||||
|
|
||||||
|
if (result === 0) {
|
||||||
|
// If no record was updated, create a new one
|
||||||
|
settingsChanges.id = (await db.settings.count()) + 1;
|
||||||
|
await db.settings.add(settingsChanges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account Management
|
||||||
|
async getAccounts(): Promise<Account[]> {
|
||||||
|
return await db.accounts.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAccount(did: string): Promise<Account | undefined> {
|
||||||
|
return await db.accounts.where("did").equals(did).first();
|
||||||
|
}
|
||||||
|
|
||||||
|
async addAccount(account: Account): Promise<void> {
|
||||||
|
await db.accounts.add(account);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
248
src/services/sqlite/AbsurdSQLService.ts
Normal file
248
src/services/sqlite/AbsurdSQLService.ts
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
import initSqlJs, { Database } from "@jlongster/sql.js";
|
||||||
|
import { SQLiteFS } from "absurd-sql";
|
||||||
|
import { IndexedDBBackend } from "absurd-sql/dist/indexeddb-backend";
|
||||||
|
import { BaseSQLiteService } from "./BaseSQLiteService";
|
||||||
|
import {
|
||||||
|
SQLiteConfig,
|
||||||
|
SQLiteResult,
|
||||||
|
PreparedStatement,
|
||||||
|
} from "../PlatformService";
|
||||||
|
import { logger } from "../../utils/logger";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQLite implementation using absurd-sql for web browsers.
|
||||||
|
* Provides SQLite access in the browser using Web Workers and IndexedDB.
|
||||||
|
*/
|
||||||
|
export class AbsurdSQLService extends BaseSQLiteService {
|
||||||
|
private db: Database | null = null;
|
||||||
|
private worker: Worker | null = null;
|
||||||
|
private config: SQLiteConfig | null = null;
|
||||||
|
|
||||||
|
async initialize(config: SQLiteConfig): Promise<void> {
|
||||||
|
if (this.initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.config = config;
|
||||||
|
const SQL = await initSqlJs({
|
||||||
|
locateFile: (file) => `/sql-wasm/${file}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize the virtual file system
|
||||||
|
const backend = new IndexedDBBackend(this.config.name);
|
||||||
|
const fs = new SQLiteFS(SQL.FS, backend);
|
||||||
|
SQL.register_for_idb(fs);
|
||||||
|
|
||||||
|
// Create and initialize the database
|
||||||
|
this.db = new SQL.Database(this.config.name, {
|
||||||
|
filename: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configure database settings
|
||||||
|
if (this.config.useWAL) {
|
||||||
|
await this.execute("PRAGMA journal_mode = WAL");
|
||||||
|
this.stats.walMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.config.useMMap) {
|
||||||
|
const mmapSize = this.config.mmapSize ?? 30000000000;
|
||||||
|
await this.execute(`PRAGMA mmap_size = ${mmapSize}`);
|
||||||
|
this.stats.mmapActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set other pragmas for performance
|
||||||
|
await this.execute("PRAGMA synchronous = NORMAL");
|
||||||
|
await this.execute("PRAGMA temp_store = MEMORY");
|
||||||
|
await this.execute("PRAGMA cache_size = -2000"); // Use 2MB of cache
|
||||||
|
|
||||||
|
// Start the Web Worker for async operations
|
||||||
|
this.worker = new Worker(new URL("./sqlite.worker.ts", import.meta.url), {
|
||||||
|
type: "module",
|
||||||
|
});
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
await this.updateStats();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to initialize Absurd SQL:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async close(): Promise<void> {
|
||||||
|
if (!this.initialized || !this.db) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Finalize all prepared statements
|
||||||
|
for (const [_sql, stmt] of this.preparedStatements) {
|
||||||
|
logger.debug("finalizing statement", _sql);
|
||||||
|
await stmt.finalize();
|
||||||
|
}
|
||||||
|
this.preparedStatements.clear();
|
||||||
|
|
||||||
|
// Close the database
|
||||||
|
this.db.close();
|
||||||
|
this.db = null;
|
||||||
|
|
||||||
|
// Terminate the worker
|
||||||
|
if (this.worker) {
|
||||||
|
this.worker.terminate();
|
||||||
|
this.worker = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initialized = false;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to close Absurd SQL connection:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _executeQuery<T>(
|
||||||
|
sql: string,
|
||||||
|
params: unknown[] = [],
|
||||||
|
operation: "query" | "execute" = "query",
|
||||||
|
): Promise<SQLiteResult<T>> {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let lastInsertId: number | undefined = undefined;
|
||||||
|
|
||||||
|
if (operation === "query") {
|
||||||
|
const stmt = this.db.prepare(sql);
|
||||||
|
const rows: T[] = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (stmt.step()) {
|
||||||
|
rows.push(stmt.getAsObject() as T);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
stmt.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get last insert ID safely
|
||||||
|
const result = this.db.exec("SELECT last_insert_rowid() AS id");
|
||||||
|
lastInsertId =
|
||||||
|
(result?.[0]?.values?.[0]?.[0] as number | undefined) ?? undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
rows,
|
||||||
|
rowsAffected: this.db.getRowsModified(),
|
||||||
|
lastInsertId,
|
||||||
|
executionTime: 0, // Will be set by base class
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.db.run(sql, params);
|
||||||
|
|
||||||
|
// Get last insert ID after execute
|
||||||
|
const result = this.db.exec("SELECT last_insert_rowid() AS id");
|
||||||
|
lastInsertId =
|
||||||
|
(result?.[0]?.values?.[0]?.[0] as number | undefined) ?? undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
rows: [],
|
||||||
|
rowsAffected: this.db.getRowsModified(),
|
||||||
|
lastInsertId,
|
||||||
|
executionTime: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Absurd SQL query failed:", {
|
||||||
|
sql,
|
||||||
|
params,
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _beginTransaction(): Promise<void> {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
this.db.exec("BEGIN TRANSACTION");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _commitTransaction(): Promise<void> {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
this.db.exec("COMMIT");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _rollbackTransaction(): Promise<void> {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
this.db.exec("ROLLBACK");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _prepareStatement<T>(
|
||||||
|
_sql: string,
|
||||||
|
): Promise<PreparedStatement<T>> {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
const stmt = this.db.prepare(_sql);
|
||||||
|
return {
|
||||||
|
execute: async (params: unknown[] = []) => {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rows: T[] = [];
|
||||||
|
stmt.bind(params);
|
||||||
|
while (stmt.step()) {
|
||||||
|
rows.push(stmt.getAsObject() as T);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safely extract lastInsertId
|
||||||
|
const result = this.db.exec("SELECT last_insert_rowid()");
|
||||||
|
const rawId = result?.[0]?.values?.[0]?.[0];
|
||||||
|
const lastInsertId = typeof rawId === "number" ? rawId : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
rows,
|
||||||
|
rowsAffected: this.db.getRowsModified(),
|
||||||
|
lastInsertId,
|
||||||
|
executionTime: 0, // Will be set by base class
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
stmt.reset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
finalize: async () => {
|
||||||
|
stmt.free();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _finalizeStatement(_sql: string): Promise<void> {
|
||||||
|
// Statements are finalized when the PreparedStatement is finalized
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDatabaseSize(): Promise<number> {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = this.db.exec(
|
||||||
|
"SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size()",
|
||||||
|
);
|
||||||
|
|
||||||
|
const rawSize = result?.[0]?.values?.[0]?.[0];
|
||||||
|
const size = typeof rawSize === "number" ? rawSize : 0;
|
||||||
|
|
||||||
|
return size;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to get database size:", error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
383
src/services/sqlite/BaseSQLiteService.ts
Normal file
383
src/services/sqlite/BaseSQLiteService.ts
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
import {
|
||||||
|
SQLiteOperations,
|
||||||
|
SQLiteConfig,
|
||||||
|
SQLiteResult,
|
||||||
|
PreparedStatement,
|
||||||
|
SQLiteStats,
|
||||||
|
} from "../PlatformService";
|
||||||
|
import { Settings, MASTER_SETTINGS_KEY } from "../../db/tables/settings";
|
||||||
|
import { logger } from "../../utils/logger";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for SQLite implementations across different platforms.
|
||||||
|
* Provides common functionality and error handling.
|
||||||
|
*/
|
||||||
|
export abstract class BaseSQLiteService implements SQLiteOperations {
|
||||||
|
protected initialized = false;
|
||||||
|
protected stats: SQLiteStats = {
|
||||||
|
totalQueries: 0,
|
||||||
|
avgExecutionTime: 0,
|
||||||
|
preparedStatements: 0,
|
||||||
|
databaseSize: 0,
|
||||||
|
walMode: false,
|
||||||
|
mmapActive: false,
|
||||||
|
};
|
||||||
|
protected preparedStatements: Map<string, PreparedStatement<unknown>> =
|
||||||
|
new Map();
|
||||||
|
|
||||||
|
abstract initialize(config: SQLiteConfig): Promise<void>;
|
||||||
|
abstract close(): Promise<void>;
|
||||||
|
abstract getDatabaseSize(): Promise<number>;
|
||||||
|
|
||||||
|
protected async executeQuery<T>(
|
||||||
|
sql: string,
|
||||||
|
params: unknown[] = [],
|
||||||
|
operation: "query" | "execute" = "query",
|
||||||
|
): Promise<SQLiteResult<T>> {
|
||||||
|
if (!this.initialized) {
|
||||||
|
throw new Error("SQLite database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
try {
|
||||||
|
const result = await this._executeQuery<T>(sql, params, operation);
|
||||||
|
const executionTime = performance.now() - startTime;
|
||||||
|
|
||||||
|
// Update stats
|
||||||
|
this.stats.totalQueries++;
|
||||||
|
this.stats.avgExecutionTime =
|
||||||
|
(this.stats.avgExecutionTime * (this.stats.totalQueries - 1) +
|
||||||
|
executionTime) /
|
||||||
|
this.stats.totalQueries;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
executionTime,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("SQLite query failed:", {
|
||||||
|
sql,
|
||||||
|
params,
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract _executeQuery<T>(
|
||||||
|
sql: string,
|
||||||
|
params: unknown[],
|
||||||
|
operation: "query" | "execute",
|
||||||
|
): Promise<SQLiteResult<T>>;
|
||||||
|
|
||||||
|
async query<T>(
|
||||||
|
sql: string,
|
||||||
|
params: unknown[] = [],
|
||||||
|
): Promise<SQLiteResult<T>> {
|
||||||
|
return this.executeQuery<T>(sql, params, "query");
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(sql: string, params: unknown[] = []): Promise<number> {
|
||||||
|
const result = await this.executeQuery<unknown>(sql, params, "execute");
|
||||||
|
return result.rowsAffected;
|
||||||
|
}
|
||||||
|
|
||||||
|
async transaction(
|
||||||
|
statements: { sql: string; params?: unknown[] }[],
|
||||||
|
): Promise<void> {
|
||||||
|
if (!this.initialized) {
|
||||||
|
throw new Error("SQLite database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this._beginTransaction();
|
||||||
|
for (const { sql, params = [] } of statements) {
|
||||||
|
await this.executeQuery(sql, params, "execute");
|
||||||
|
}
|
||||||
|
await this._commitTransaction();
|
||||||
|
} catch (error) {
|
||||||
|
await this._rollbackTransaction();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract _beginTransaction(): Promise<void>;
|
||||||
|
protected abstract _commitTransaction(): Promise<void>;
|
||||||
|
protected abstract _rollbackTransaction(): Promise<void>;
|
||||||
|
|
||||||
|
async getMaxValue<T>(
|
||||||
|
table: string,
|
||||||
|
column: string,
|
||||||
|
where?: string,
|
||||||
|
params: unknown[] = [],
|
||||||
|
): Promise<T | null> {
|
||||||
|
const sql = `SELECT MAX(${column}) as max_value FROM ${table}${where ? ` WHERE ${where}` : ""}`;
|
||||||
|
const result = await this.query<{ max_value: T }>(sql, params);
|
||||||
|
return result.rows[0]?.max_value ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async prepare<T>(sql: string): Promise<PreparedStatement<T>> {
|
||||||
|
if (!this.initialized) {
|
||||||
|
throw new Error("SQLite database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
const stmt = await this._prepareStatement<T>(sql);
|
||||||
|
this.stats.preparedStatements++;
|
||||||
|
this.preparedStatements.set(sql, stmt);
|
||||||
|
|
||||||
|
return {
|
||||||
|
execute: async (params: unknown[] = []) => {
|
||||||
|
return this.executeQuery<T>(sql, params, "query");
|
||||||
|
},
|
||||||
|
finalize: async () => {
|
||||||
|
await this._finalizeStatement(sql);
|
||||||
|
this.preparedStatements.delete(sql);
|
||||||
|
this.stats.preparedStatements--;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract _prepareStatement<T>(
|
||||||
|
sql: string,
|
||||||
|
): Promise<PreparedStatement<T>>;
|
||||||
|
protected abstract _finalizeStatement(sql: string): Promise<void>;
|
||||||
|
|
||||||
|
async getStats(): Promise<SQLiteStats> {
|
||||||
|
return {
|
||||||
|
...this.stats,
|
||||||
|
databaseSize: await this.getDatabaseSize(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async updateStats(): Promise<void> {
|
||||||
|
this.stats.databaseSize = await this.getDatabaseSize();
|
||||||
|
// Platform-specific stats updates can be implemented in subclasses
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async setupSchema(): Promise<void> {
|
||||||
|
await this.execute(`
|
||||||
|
CREATE TABLE IF NOT EXISTS settings (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
accountDid TEXT,
|
||||||
|
activeDid TEXT,
|
||||||
|
apiServer TEXT,
|
||||||
|
filterFeedByNearby INTEGER,
|
||||||
|
filterFeedByVisible INTEGER,
|
||||||
|
finishedOnboarding INTEGER,
|
||||||
|
firstName TEXT,
|
||||||
|
hideRegisterPromptOnNewContact INTEGER,
|
||||||
|
isRegistered INTEGER,
|
||||||
|
lastName TEXT,
|
||||||
|
lastAckedOfferToUserJwtId TEXT,
|
||||||
|
lastAckedOfferToUserProjectsJwtId TEXT,
|
||||||
|
lastNotifiedClaimId TEXT,
|
||||||
|
lastViewedClaimId TEXT,
|
||||||
|
notifyingNewActivityTime TEXT,
|
||||||
|
notifyingReminderMessage TEXT,
|
||||||
|
notifyingReminderTime TEXT,
|
||||||
|
partnerApiServer TEXT,
|
||||||
|
passkeyExpirationMinutes INTEGER,
|
||||||
|
profileImageUrl TEXT,
|
||||||
|
searchBoxes TEXT,
|
||||||
|
showContactGivesInline INTEGER,
|
||||||
|
showGeneralAdvanced INTEGER,
|
||||||
|
showShortcutBvc INTEGER,
|
||||||
|
vapid TEXT,
|
||||||
|
warnIfProdServer INTEGER,
|
||||||
|
warnIfTestServer INTEGER,
|
||||||
|
webPushServer TEXT
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async settingsToRow(
|
||||||
|
settings: Partial<Settings>,
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
|
const row: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
// Convert boolean values to integers for SQLite
|
||||||
|
if ("filterFeedByNearby" in settings)
|
||||||
|
row.filterFeedByNearby = settings.filterFeedByNearby ? 1 : 0;
|
||||||
|
if ("filterFeedByVisible" in settings)
|
||||||
|
row.filterFeedByVisible = settings.filterFeedByVisible ? 1 : 0;
|
||||||
|
if ("finishedOnboarding" in settings)
|
||||||
|
row.finishedOnboarding = settings.finishedOnboarding ? 1 : 0;
|
||||||
|
if ("hideRegisterPromptOnNewContact" in settings)
|
||||||
|
row.hideRegisterPromptOnNewContact =
|
||||||
|
settings.hideRegisterPromptOnNewContact ? 1 : 0;
|
||||||
|
if ("isRegistered" in settings)
|
||||||
|
row.isRegistered = settings.isRegistered ? 1 : 0;
|
||||||
|
if ("showContactGivesInline" in settings)
|
||||||
|
row.showContactGivesInline = settings.showContactGivesInline ? 1 : 0;
|
||||||
|
if ("showGeneralAdvanced" in settings)
|
||||||
|
row.showGeneralAdvanced = settings.showGeneralAdvanced ? 1 : 0;
|
||||||
|
if ("showShortcutBvc" in settings)
|
||||||
|
row.showShortcutBvc = settings.showShortcutBvc ? 1 : 0;
|
||||||
|
if ("warnIfProdServer" in settings)
|
||||||
|
row.warnIfProdServer = settings.warnIfProdServer ? 1 : 0;
|
||||||
|
if ("warnIfTestServer" in settings)
|
||||||
|
row.warnIfTestServer = settings.warnIfTestServer ? 1 : 0;
|
||||||
|
|
||||||
|
// Handle JSON fields
|
||||||
|
if ("searchBoxes" in settings)
|
||||||
|
row.searchBoxes = JSON.stringify(settings.searchBoxes);
|
||||||
|
|
||||||
|
// Copy all other fields as is
|
||||||
|
Object.entries(settings).forEach(([key, value]) => {
|
||||||
|
if (!(key in row)) {
|
||||||
|
row[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async rowToSettings(
|
||||||
|
row: Record<string, unknown>,
|
||||||
|
): Promise<Settings> {
|
||||||
|
const settings: Settings = {};
|
||||||
|
|
||||||
|
// Convert integer values back to booleans
|
||||||
|
if ("filterFeedByNearby" in row)
|
||||||
|
settings.filterFeedByNearby = !!row.filterFeedByNearby;
|
||||||
|
if ("filterFeedByVisible" in row)
|
||||||
|
settings.filterFeedByVisible = !!row.filterFeedByVisible;
|
||||||
|
if ("finishedOnboarding" in row)
|
||||||
|
settings.finishedOnboarding = !!row.finishedOnboarding;
|
||||||
|
if ("hideRegisterPromptOnNewContact" in row)
|
||||||
|
settings.hideRegisterPromptOnNewContact =
|
||||||
|
!!row.hideRegisterPromptOnNewContact;
|
||||||
|
if ("isRegistered" in row) settings.isRegistered = !!row.isRegistered;
|
||||||
|
if ("showContactGivesInline" in row)
|
||||||
|
settings.showContactGivesInline = !!row.showContactGivesInline;
|
||||||
|
if ("showGeneralAdvanced" in row)
|
||||||
|
settings.showGeneralAdvanced = !!row.showGeneralAdvanced;
|
||||||
|
if ("showShortcutBvc" in row)
|
||||||
|
settings.showShortcutBvc = !!row.showShortcutBvc;
|
||||||
|
if ("warnIfProdServer" in row)
|
||||||
|
settings.warnIfProdServer = !!row.warnIfProdServer;
|
||||||
|
if ("warnIfTestServer" in row)
|
||||||
|
settings.warnIfTestServer = !!row.warnIfTestServer;
|
||||||
|
|
||||||
|
// Parse JSON fields
|
||||||
|
if ("searchBoxes" in row && row.searchBoxes) {
|
||||||
|
try {
|
||||||
|
settings.searchBoxes = JSON.parse(row.searchBoxes);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error parsing searchBoxes JSON:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy all other fields as is
|
||||||
|
Object.entries(row).forEach(([key, value]) => {
|
||||||
|
if (!(key in settings)) {
|
||||||
|
(settings as Record<string, unknown>)[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateMasterSettings(
|
||||||
|
settingsChanges: Partial<Settings>,
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const row = await this.settingsToRow(settingsChanges);
|
||||||
|
row.id = MASTER_SETTINGS_KEY;
|
||||||
|
delete row.accountDid;
|
||||||
|
|
||||||
|
const result = await this.execute(
|
||||||
|
`UPDATE settings SET ${Object.keys(row)
|
||||||
|
.map((k) => `${k} = ?`)
|
||||||
|
.join(", ")} WHERE id = ?`,
|
||||||
|
[...Object.values(row), MASTER_SETTINGS_KEY],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result === 0) {
|
||||||
|
// If no record was updated, create a new one
|
||||||
|
await this.execute(
|
||||||
|
`INSERT INTO settings (${Object.keys(row).join(", ")}) VALUES (${Object.keys(
|
||||||
|
row,
|
||||||
|
)
|
||||||
|
.map(() => "?")
|
||||||
|
.join(", ")})`,
|
||||||
|
Object.values(row),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error updating master settings:", error);
|
||||||
|
throw new Error("Failed to update settings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getActiveAccountSettings(): Promise<Settings> {
|
||||||
|
try {
|
||||||
|
const defaultSettings = await this.query<Record<string, unknown>>(
|
||||||
|
"SELECT * FROM settings WHERE id = ?",
|
||||||
|
[MASTER_SETTINGS_KEY],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!defaultSettings.rows.length) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = await this.rowToSettings(defaultSettings.rows[0]);
|
||||||
|
|
||||||
|
if (!settings.activeDid) {
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
const overrideSettings = await this.query<Record<string, unknown>>(
|
||||||
|
"SELECT * FROM settings WHERE accountDid = ?",
|
||||||
|
[settings.activeDid],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!overrideSettings.rows.length) {
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
const override = await this.rowToSettings(overrideSettings.rows[0]);
|
||||||
|
return { ...settings, ...override };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error getting active account settings:", error);
|
||||||
|
throw new Error("Failed to get settings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAccountSettings(
|
||||||
|
accountDid: string,
|
||||||
|
settingsChanges: Partial<Settings>,
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const row = await this.settingsToRow(settingsChanges);
|
||||||
|
row.accountDid = accountDid;
|
||||||
|
|
||||||
|
const result = await this.execute(
|
||||||
|
`UPDATE settings SET ${Object.keys(row)
|
||||||
|
.map((k) => `${k} = ?`)
|
||||||
|
.join(", ")} WHERE accountDid = ?`,
|
||||||
|
[...Object.values(row), accountDid],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result === 0) {
|
||||||
|
// If no record was updated, create a new one
|
||||||
|
const idResult = await this.query<{ max: number }>(
|
||||||
|
"SELECT MAX(id) as max FROM settings",
|
||||||
|
);
|
||||||
|
row.id = (idResult.rows[0]?.max || 0) + 1;
|
||||||
|
|
||||||
|
await this.execute(
|
||||||
|
`INSERT INTO settings (${Object.keys(row).join(", ")}) VALUES (${Object.keys(
|
||||||
|
row,
|
||||||
|
)
|
||||||
|
.map(() => "?")
|
||||||
|
.join(", ")})`,
|
||||||
|
Object.values(row),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error updating account settings:", error);
|
||||||
|
throw new Error("Failed to update settings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
176
src/services/sqlite/CapacitorSQLiteService.ts
Normal file
176
src/services/sqlite/CapacitorSQLiteService.ts
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import {
|
||||||
|
CapacitorSQLite,
|
||||||
|
SQLiteConnection,
|
||||||
|
SQLiteDBConnection,
|
||||||
|
} from "@capacitor-community/sqlite";
|
||||||
|
import { BaseSQLiteService } from "./BaseSQLiteService";
|
||||||
|
import {
|
||||||
|
SQLiteConfig,
|
||||||
|
SQLiteResult,
|
||||||
|
PreparedStatement,
|
||||||
|
} from "../PlatformService";
|
||||||
|
import { logger } from "../../utils/logger";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQLite implementation using the Capacitor SQLite plugin.
|
||||||
|
* Provides native SQLite access on mobile platforms.
|
||||||
|
*/
|
||||||
|
export class CapacitorSQLiteService extends BaseSQLiteService {
|
||||||
|
private connection: SQLiteDBConnection | null = null;
|
||||||
|
private sqlite: SQLiteConnection | null = null;
|
||||||
|
|
||||||
|
async initialize(config: SQLiteConfig): Promise<void> {
|
||||||
|
if (this.initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.sqlite = new SQLiteConnection(CapacitorSQLite);
|
||||||
|
const db = await this.sqlite.createConnection(
|
||||||
|
config.name,
|
||||||
|
config.useWAL ?? false,
|
||||||
|
"no-encryption",
|
||||||
|
1,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
await db.open();
|
||||||
|
this.connection = db;
|
||||||
|
|
||||||
|
// Configure database settings
|
||||||
|
if (config.useWAL) {
|
||||||
|
await this.execute("PRAGMA journal_mode = WAL");
|
||||||
|
this.stats.walMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set other pragmas for performance
|
||||||
|
await this.execute("PRAGMA synchronous = NORMAL");
|
||||||
|
await this.execute("PRAGMA temp_store = MEMORY");
|
||||||
|
await this.execute("PRAGMA mmap_size = 30000000000");
|
||||||
|
this.stats.mmapActive = true;
|
||||||
|
|
||||||
|
// Set up database schema
|
||||||
|
await this.setupSchema();
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
await this.updateStats();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to initialize Capacitor SQLite:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async close(): Promise<void> {
|
||||||
|
if (!this.initialized || !this.connection || !this.sqlite) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.connection.close();
|
||||||
|
await this.sqlite.closeConnection(this.connection);
|
||||||
|
this.connection = null;
|
||||||
|
this.sqlite = null;
|
||||||
|
this.initialized = false;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to close Capacitor SQLite connection:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _executeQuery<T>(
|
||||||
|
sql: string,
|
||||||
|
params: unknown[] = [],
|
||||||
|
operation: "query" | "execute" = "query",
|
||||||
|
): Promise<SQLiteResult<T>> {
|
||||||
|
if (!this.connection) {
|
||||||
|
throw new Error("Database connection not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (operation === "query") {
|
||||||
|
const result = await this.connection.query(sql, params);
|
||||||
|
return {
|
||||||
|
rows: result.values as T[],
|
||||||
|
rowsAffected: result.changes?.changes ?? 0,
|
||||||
|
lastInsertId: result.changes?.lastId,
|
||||||
|
executionTime: 0, // Will be set by base class
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const result = await this.connection.run(sql, params);
|
||||||
|
return {
|
||||||
|
rows: [],
|
||||||
|
rowsAffected: result.changes?.changes ?? 0,
|
||||||
|
lastInsertId: result.changes?.lastId,
|
||||||
|
executionTime: 0, // Will be set by base class
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Capacitor SQLite query failed:", {
|
||||||
|
sql,
|
||||||
|
params,
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _beginTransaction(): Promise<void> {
|
||||||
|
if (!this.connection) {
|
||||||
|
throw new Error("Database connection not initialized");
|
||||||
|
}
|
||||||
|
await this.connection.execute("BEGIN TRANSACTION");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _commitTransaction(): Promise<void> {
|
||||||
|
if (!this.connection) {
|
||||||
|
throw new Error("Database connection not initialized");
|
||||||
|
}
|
||||||
|
await this.connection.execute("COMMIT");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _rollbackTransaction(): Promise<void> {
|
||||||
|
if (!this.connection) {
|
||||||
|
throw new Error("Database connection not initialized");
|
||||||
|
}
|
||||||
|
await this.connection.execute("ROLLBACK");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _prepareStatement<T>(
|
||||||
|
sql: string,
|
||||||
|
): Promise<PreparedStatement<T>> {
|
||||||
|
if (!this.connection) {
|
||||||
|
throw new Error("Database connection not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capacitor SQLite doesn't support prepared statements directly,
|
||||||
|
// so we'll simulate it by storing the SQL
|
||||||
|
return {
|
||||||
|
execute: async (params: unknown[] = []) => {
|
||||||
|
return this.executeQuery<T>(sql, params, "query");
|
||||||
|
},
|
||||||
|
finalize: async () => {
|
||||||
|
// No cleanup needed for Capacitor SQLite
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _finalizeStatement(_sql: string): Promise<void> {
|
||||||
|
// No cleanup needed for Capacitor SQLite
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDatabaseSize(): Promise<number> {
|
||||||
|
if (!this.connection) {
|
||||||
|
throw new Error("Database connection not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await this.connection.query(
|
||||||
|
"SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size()",
|
||||||
|
);
|
||||||
|
return result.values?.[0]?.size ?? 0;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to get database size:", error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
150
src/services/sqlite/sqlite.worker.ts
Normal file
150
src/services/sqlite/sqlite.worker.ts
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import initSqlJs, { Database } from "@jlongster/sql.js";
|
||||||
|
import { SQLiteFS } from "absurd-sql";
|
||||||
|
import { IndexedDBBackend } from "absurd-sql/dist/indexeddb-backend";
|
||||||
|
|
||||||
|
interface WorkerMessage {
|
||||||
|
type: "init" | "query" | "execute" | "transaction" | "close";
|
||||||
|
id: string;
|
||||||
|
dbName?: string;
|
||||||
|
sql?: string;
|
||||||
|
params?: unknown[];
|
||||||
|
statements?: { sql: string; params?: unknown[] }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WorkerResponse {
|
||||||
|
id: string;
|
||||||
|
error?: string;
|
||||||
|
result?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
let db: Database | null = null;
|
||||||
|
|
||||||
|
async function initialize(dbName: string): Promise<void> {
|
||||||
|
if (db) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SQL = await initSqlJs({
|
||||||
|
locateFile: (file: string) => `/sql-wasm/${file}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize the virtual file system
|
||||||
|
const backend = new IndexedDBBackend(dbName);
|
||||||
|
const fs = new SQLiteFS(SQL.FS, backend);
|
||||||
|
SQL.register_for_idb(fs);
|
||||||
|
|
||||||
|
// Create and initialize the database
|
||||||
|
db = new SQL.Database(dbName, {
|
||||||
|
filename: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configure database settings
|
||||||
|
db.exec("PRAGMA synchronous = NORMAL");
|
||||||
|
db.exec("PRAGMA temp_store = MEMORY");
|
||||||
|
db.exec("PRAGMA cache_size = -2000"); // Use 2MB of cache
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeQuery(
|
||||||
|
sql: string,
|
||||||
|
params: unknown[] = [],
|
||||||
|
): Promise<unknown> {
|
||||||
|
if (!db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
const stmt = db.prepare(sql);
|
||||||
|
try {
|
||||||
|
const rows: unknown[] = [];
|
||||||
|
stmt.bind(params);
|
||||||
|
while (stmt.step()) {
|
||||||
|
rows.push(stmt.getAsObject());
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
rows,
|
||||||
|
rowsAffected: db.getRowsModified(),
|
||||||
|
lastInsertId: db.exec("SELECT last_insert_rowid()")[0]?.values[0]?.[0],
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
stmt.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeTransaction(
|
||||||
|
statements: { sql: string; params?: unknown[] }[],
|
||||||
|
): Promise<void> {
|
||||||
|
if (!db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.exec("BEGIN TRANSACTION");
|
||||||
|
for (const { sql, params = [] } of statements) {
|
||||||
|
const stmt = db.prepare(sql);
|
||||||
|
try {
|
||||||
|
stmt.bind(params);
|
||||||
|
stmt.step();
|
||||||
|
} finally {
|
||||||
|
stmt.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.exec("COMMIT");
|
||||||
|
} catch (error) {
|
||||||
|
db.exec("ROLLBACK");
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function close(): Promise<void> {
|
||||||
|
if (db) {
|
||||||
|
db.close();
|
||||||
|
db = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onmessage = async (event: MessageEvent<WorkerMessage>) => {
|
||||||
|
const { type, id, dbName, sql, params, statements } = event.data;
|
||||||
|
const response: WorkerResponse = { id };
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (type) {
|
||||||
|
case "init":
|
||||||
|
if (!dbName) {
|
||||||
|
throw new Error("Database name is required for initialization");
|
||||||
|
}
|
||||||
|
await initialize(dbName);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "query":
|
||||||
|
if (!sql) {
|
||||||
|
throw new Error("SQL query is required");
|
||||||
|
}
|
||||||
|
response.result = await executeQuery(sql, params);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "execute":
|
||||||
|
if (!sql) {
|
||||||
|
throw new Error("SQL statement is required");
|
||||||
|
}
|
||||||
|
response.result = await executeQuery(sql, params);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "transaction":
|
||||||
|
if (!statements?.length) {
|
||||||
|
throw new Error("Transaction statements are required");
|
||||||
|
}
|
||||||
|
await executeTransaction(statements);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "close":
|
||||||
|
await close();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown message type: ${type}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
response.error = error instanceof Error ? error.message : String(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.postMessage(response);
|
||||||
|
};
|
||||||
45
src/types/absurd-sql.d.ts
vendored
Normal file
45
src/types/absurd-sql.d.ts
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
declare module "@jlongster/sql.js" {
|
||||||
|
export interface Database {
|
||||||
|
exec(
|
||||||
|
sql: string,
|
||||||
|
params?: unknown[],
|
||||||
|
): { columns: string[]; values: unknown[][] }[];
|
||||||
|
prepare(sql: string): Statement;
|
||||||
|
run(sql: string, params?: unknown[]): void;
|
||||||
|
getRowsModified(): number;
|
||||||
|
close(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Statement {
|
||||||
|
step(): boolean;
|
||||||
|
getAsObject(): Record<string, unknown>;
|
||||||
|
bind(params: unknown[]): void;
|
||||||
|
reset(): void;
|
||||||
|
free(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InitSqlJsStatic {
|
||||||
|
Database: new (
|
||||||
|
filename?: string,
|
||||||
|
options?: { filename: boolean },
|
||||||
|
) => Database;
|
||||||
|
FS: unknown;
|
||||||
|
register_for_idb(fs: unknown): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function initSqlJs(options?: {
|
||||||
|
locateFile?: (file: string) => string;
|
||||||
|
}): Promise<InitSqlJsStatic>;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "absurd-sql" {
|
||||||
|
export class SQLiteFS {
|
||||||
|
constructor(fs: unknown, backend: unknown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "absurd-sql/dist/indexeddb-backend" {
|
||||||
|
export class IndexedDBBackend {
|
||||||
|
constructor(dbName: string);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
// Empty module to satisfy Node.js built-in module imports
|
// Empty module to satisfy Node.js built-in module imports
|
||||||
export default {};
|
export default {};
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ const crypto = {
|
|||||||
},
|
},
|
||||||
createHash: () => ({
|
createHash: () => ({
|
||||||
update: () => ({
|
update: () => ({
|
||||||
digest: () => new Uint8Array(32) // Return empty hash
|
digest: () => new Uint8Array(32), // Return empty hash
|
||||||
})
|
}),
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default crypto;
|
export default crypto;
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
// Minimal fs module implementation for browser
|
// Minimal fs module implementation for browser
|
||||||
const fs = {
|
const fs = {
|
||||||
readFileSync: () => {
|
readFileSync: () => {
|
||||||
throw new Error('fs.readFileSync is not supported in browser');
|
throw new Error("fs.readFileSync is not supported in browser");
|
||||||
},
|
},
|
||||||
writeFileSync: () => {
|
writeFileSync: () => {
|
||||||
throw new Error('fs.writeFileSync is not supported in browser');
|
throw new Error("fs.writeFileSync is not supported in browser");
|
||||||
},
|
},
|
||||||
existsSync: () => false,
|
existsSync: () => false,
|
||||||
mkdirSync: () => {},
|
mkdirSync: () => {},
|
||||||
readdirSync: () => [],
|
readdirSync: () => [],
|
||||||
statSync: () => ({
|
statSync: () => ({
|
||||||
isDirectory: () => false,
|
isDirectory: () => false,
|
||||||
isFile: () => false
|
isFile: () => false,
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default fs;
|
export default fs;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
// Minimal path module implementation for browser
|
// Minimal path module implementation for browser
|
||||||
const path = {
|
const path = {
|
||||||
resolve: (...parts) => parts.join('/'),
|
resolve: (...parts) => parts.join("/"),
|
||||||
join: (...parts) => parts.join('/'),
|
join: (...parts) => parts.join("/"),
|
||||||
dirname: (p) => p.split('/').slice(0, -1).join('/'),
|
dirname: (p) => p.split("/").slice(0, -1).join("/"),
|
||||||
basename: (p) => p.split('/').pop(),
|
basename: (p) => p.split("/").pop(),
|
||||||
extname: (p) => {
|
extname: (p) => {
|
||||||
const parts = p.split('.');
|
const parts = p.split(".");
|
||||||
return parts.length > 1 ? '.' + parts.pop() : '';
|
return parts.length > 1 ? "." + parts.pop() : "";
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default path;
|
export default path;
|
||||||
|
|||||||
@@ -994,13 +994,6 @@ import {
|
|||||||
IMAGE_TYPE_PROFILE,
|
IMAGE_TYPE_PROFILE,
|
||||||
NotificationIface,
|
NotificationIface,
|
||||||
} from "../constants/app";
|
} from "../constants/app";
|
||||||
import {
|
|
||||||
db,
|
|
||||||
logConsoleAndDb,
|
|
||||||
retrieveSettingsForActiveAccount,
|
|
||||||
updateAccountSettings,
|
|
||||||
} from "../db/index";
|
|
||||||
import { Account } from "../db/tables/accounts";
|
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import {
|
import {
|
||||||
DEFAULT_PASSKEY_EXPIRATION_MINUTES,
|
DEFAULT_PASSKEY_EXPIRATION_MINUTES,
|
||||||
@@ -1215,11 +1208,12 @@ export default class AccountViewView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes component state with values from the database or defaults.
|
* Initializes component state using PlatformService for database operations
|
||||||
|
* Keeps all endorserServer functionality unchanged
|
||||||
*/
|
*/
|
||||||
async initializeState() {
|
async initializeState() {
|
||||||
await db.open();
|
const platform = this.$platform;
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const settings = await platform.getActiveAccountSettings();
|
||||||
|
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
@@ -1252,6 +1246,29 @@ export default class AccountViewView extends Vue {
|
|||||||
this.webPushServerInput = settings.webPushServer || this.webPushServerInput;
|
this.webPushServerInput = settings.webPushServer || this.webPushServerInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates account settings using PlatformService
|
||||||
|
* Keeps all endorserServer functionality unchanged
|
||||||
|
*/
|
||||||
|
async updateSettings(settingsChanges: Record<string, unknown>) {
|
||||||
|
try {
|
||||||
|
const platform = this.$platform;
|
||||||
|
await platform.updateAccountSettings(this.activeDid, settingsChanges);
|
||||||
|
await this.initializeState();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error updating settings:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: "There was a problem updating your settings.",
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// call fn, copy text to the clipboard, then redo fn after 2 seconds
|
// call fn, copy text to the clipboard, then redo fn after 2 seconds
|
||||||
doCopyTwoSecRedo(text: string, fn: () => void) {
|
doCopyTwoSecRedo(text: string, fn: () => void) {
|
||||||
fn();
|
fn();
|
||||||
|
|||||||
@@ -439,13 +439,11 @@ import { useClipboard } from "@vueuse/core";
|
|||||||
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import { NotificationIface } from "../constants/app";
|
import { NotificationIface } from "../constants/app";
|
||||||
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import * as serverUtil from "../libs/endorserServer";
|
import * as serverUtil from "../libs/endorserServer";
|
||||||
import { GenericVerifiableCredential, GiveSummaryRecord } from "../interfaces";
|
import { GenericVerifiableCredential, GiveSummaryRecord } from "../interfaces";
|
||||||
import { displayAmount } from "../libs/endorserServer";
|
import { displayAmount } from "../libs/endorserServer";
|
||||||
import * as libsUtil from "../libs/util";
|
import * as libsUtil from "../libs/util";
|
||||||
import { retrieveAccountDids } from "../libs/util";
|
|
||||||
import TopMessage from "../components/TopMessage.vue";
|
import TopMessage from "../components/TopMessage.vue";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
/**
|
/**
|
||||||
@@ -526,14 +524,17 @@ export default class ConfirmGiftView extends Vue {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes component settings and user data
|
* Initializes component settings and user data
|
||||||
|
* Only database operations are migrated to PlatformService
|
||||||
|
* API-related utilities remain using serverUtil
|
||||||
*/
|
*/
|
||||||
private async initializeSettings() {
|
private async initializeSettings() {
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const platform = this.$platform;
|
||||||
|
const settings = await platform.getActiveAccountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.allContacts = await db.contacts.toArray();
|
this.allContacts = await platform.getAllContacts();
|
||||||
this.isRegistered = settings.isRegistered || false;
|
this.isRegistered = settings.isRegistered || false;
|
||||||
this.allMyDids = await retrieveAccountDids();
|
this.allMyDids = await platform.getAllAccountDids();
|
||||||
|
|
||||||
// Check share capability
|
// Check share capability
|
||||||
// When Chrome compatibility is fixed https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API#api.navigator.canshare
|
// When Chrome compatibility is fixed https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API#api.navigator.canshare
|
||||||
|
|||||||
@@ -1063,7 +1063,8 @@ export default class ContactsView extends Vue {
|
|||||||
);
|
);
|
||||||
if (regResult.success) {
|
if (regResult.success) {
|
||||||
contact.registered = true;
|
contact.registered = true;
|
||||||
await db.contacts.update(contact.did, { registered: true });
|
const platform = this.$platform;
|
||||||
|
await platform.updateContact(contact.did, { registered: true });
|
||||||
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -298,57 +298,43 @@ Raymer * @version 1.0.0 */
|
|||||||
import { UAParser } from "ua-parser-js";
|
import { UAParser } from "ua-parser-js";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { Router } from "vue-router";
|
import { Router } from "vue-router";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||||
//import App from "../App.vue";
|
import { BoundingBox } from "../types/BoundingBox";
|
||||||
import EntityIcon from "../components/EntityIcon.vue";
|
import { Contact } from "../types/Contact";
|
||||||
import GiftedDialog from "../components/GiftedDialog.vue";
|
|
||||||
import GiftedPrompts from "../components/GiftedPrompts.vue";
|
|
||||||
import FeedFilters from "../components/FeedFilters.vue";
|
|
||||||
import InfiniteScroll from "../components/InfiniteScroll.vue";
|
|
||||||
import OnboardingDialog from "../components/OnboardingDialog.vue";
|
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
|
||||||
import TopMessage from "../components/TopMessage.vue";
|
|
||||||
import UserNameDialog from "../components/UserNameDialog.vue";
|
|
||||||
import ChoiceButtonDialog from "../components/ChoiceButtonDialog.vue";
|
|
||||||
import ImageViewer from "../components/ImageViewer.vue";
|
|
||||||
import ActivityListItem from "../components/ActivityListItem.vue";
|
|
||||||
import {
|
import {
|
||||||
AppString,
|
OnboardingDialog,
|
||||||
NotificationIface,
|
OnboardPage,
|
||||||
PASSKEYS_ENABLED,
|
} from "../components/OnboardingDialog.vue";
|
||||||
} from "../constants/app";
|
import { QuickNav } from "../components/QuickNav.vue";
|
||||||
|
import { TopMessage } from "../components/TopMessage.vue";
|
||||||
|
import { EntityIcon } from "../components/EntityIcon.vue";
|
||||||
|
import { GiftedDialog } from "../components/GiftedDialog.vue";
|
||||||
|
import { GiftedPrompts } from "../components/GiftedPrompts.vue";
|
||||||
|
import { FeedFilters } from "../components/FeedFilters.vue";
|
||||||
|
import { UserNameDialog } from "../components/UserNameDialog.vue";
|
||||||
|
import { ActivityListItem } from "../components/ActivityListItem.vue";
|
||||||
|
import { AppString } from "../AppString";
|
||||||
|
import { PASSKEYS_ENABLED } from "../config";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
import { checkIsAnyFeedFilterOn } from "../utils/feedFilters";
|
||||||
import {
|
import {
|
||||||
db,
|
|
||||||
logConsoleAndDb,
|
|
||||||
retrieveSettingsForActiveAccount,
|
|
||||||
updateAccountSettings,
|
|
||||||
} from "../db/index";
|
|
||||||
import { Contact } from "../db/tables/contacts";
|
|
||||||
import {
|
|
||||||
BoundingBox,
|
|
||||||
checkIsAnyFeedFilterOn,
|
|
||||||
MASTER_SETTINGS_KEY,
|
|
||||||
} from "../db/tables/settings";
|
|
||||||
import {
|
|
||||||
contactForDid,
|
|
||||||
containsNonHiddenDid,
|
|
||||||
didInfoForContact,
|
|
||||||
fetchEndorserRateLimits,
|
fetchEndorserRateLimits,
|
||||||
getHeaders,
|
|
||||||
getNewOffersToUser,
|
getNewOffersToUser,
|
||||||
getNewOffersToUserProjects,
|
getNewOffersToUserProjects,
|
||||||
getPlanFromCache,
|
} from "../api/endorser";
|
||||||
} from "../libs/endorserServer";
|
|
||||||
import {
|
import {
|
||||||
generateSaveAndActivateIdentity,
|
generateSaveAndActivateIdentity,
|
||||||
retrieveAccountDids,
|
retrieveAccountDids,
|
||||||
GiverReceiverInputInfo,
|
} from "../api/identity";
|
||||||
OnboardPage,
|
import { NotificationIface } from "../constants/app";
|
||||||
} from "../libs/util";
|
import {
|
||||||
|
containsNonHiddenDid,
|
||||||
|
didInfoForContact,
|
||||||
|
} from "../libs/endorserServer";
|
||||||
import { GiveSummaryRecord } from "../interfaces";
|
import { GiveSummaryRecord } from "../interfaces";
|
||||||
import * as serverUtil from "../libs/endorserServer";
|
import * as serverUtil from "../libs/endorserServer";
|
||||||
import { logger } from "../utils/logger";
|
|
||||||
import { GiveRecordWithContactInfo } from "../types";
|
import { GiveRecordWithContactInfo } from "../types";
|
||||||
|
import { ChoiceButtonDialog, ImageViewer } from "../components/index";
|
||||||
|
|
||||||
interface Claim {
|
interface Claim {
|
||||||
claim?: Claim; // For nested claims in Verifiable Credentials
|
claim?: Claim; // For nested claims in Verifiable Credentials
|
||||||
@@ -419,18 +405,18 @@ interface FeedError {
|
|||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
EntityIcon,
|
FontAwesomeIcon,
|
||||||
FeedFilters,
|
|
||||||
GiftedDialog,
|
|
||||||
GiftedPrompts,
|
|
||||||
InfiniteScroll,
|
|
||||||
OnboardingDialog,
|
|
||||||
ChoiceButtonDialog,
|
|
||||||
QuickNav,
|
QuickNav,
|
||||||
TopMessage,
|
TopMessage,
|
||||||
|
EntityIcon,
|
||||||
|
GiftedDialog,
|
||||||
|
GiftedPrompts,
|
||||||
|
FeedFilters,
|
||||||
UserNameDialog,
|
UserNameDialog,
|
||||||
ImageViewer,
|
|
||||||
ActivityListItem,
|
ActivityListItem,
|
||||||
|
OnboardingDialog,
|
||||||
|
ChoiceButtonDialog,
|
||||||
|
ImageViewer,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class HomeView extends Vue {
|
export default class HomeView extends Vue {
|
||||||
@@ -520,10 +506,11 @@ export default class HomeView extends Vue {
|
|||||||
this.allMyDids = [newDid];
|
this.allMyDids = [newDid];
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const platform = this.$platform;
|
||||||
|
const settings = await platform.getActiveAccountSettings();
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
this.allContacts = await db.contacts.toArray();
|
this.allContacts = await platform.getAllContacts();
|
||||||
this.feedLastViewedClaimId = settings.lastViewedClaimId;
|
this.feedLastViewedClaimId = settings.lastViewedClaimId;
|
||||||
this.givenName = settings.firstName || "";
|
this.givenName = settings.firstName || "";
|
||||||
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
|
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
|
||||||
@@ -552,9 +539,9 @@ export default class HomeView extends Vue {
|
|||||||
this.activeDid,
|
this.activeDid,
|
||||||
);
|
);
|
||||||
if (resp.status === 200) {
|
if (resp.status === 200) {
|
||||||
await updateAccountSettings(this.activeDid, {
|
await platform.updateAccountSettings(this.activeDid, {
|
||||||
isRegistered: true,
|
isRegistered: true,
|
||||||
...(await retrieveSettingsForActiveAccount()),
|
...settings,
|
||||||
});
|
});
|
||||||
this.isRegistered = true;
|
this.isRegistered = true;
|
||||||
}
|
}
|
||||||
@@ -590,14 +577,14 @@ export default class HomeView extends Vue {
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
logConsoleAndDb("Error retrieving settings or feed: " + err, true);
|
logger.error("Error retrieving settings or feed:", err);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text:
|
text:
|
||||||
(err as { userMessage?: string })?.userMessage ||
|
err?.userMessage ||
|
||||||
"There was an error retrieving your settings or the latest activity.",
|
"There was an error retrieving your settings or the latest activity.",
|
||||||
},
|
},
|
||||||
5000,
|
5000,
|
||||||
@@ -618,7 +605,8 @@ export default class HomeView extends Vue {
|
|||||||
* Called by mounted() and reloadFeedOnChange()
|
* Called by mounted() and reloadFeedOnChange()
|
||||||
*/
|
*/
|
||||||
private async loadSettings() {
|
private async loadSettings() {
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const platform = this.$platform;
|
||||||
|
const settings = await platform.getActiveAccountSettings();
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
this.feedLastViewedClaimId = settings.lastViewedClaimId;
|
this.feedLastViewedClaimId = settings.lastViewedClaimId;
|
||||||
@@ -642,7 +630,7 @@ export default class HomeView extends Vue {
|
|||||||
* Called by mounted() and initializeIdentity()
|
* Called by mounted() and initializeIdentity()
|
||||||
*/
|
*/
|
||||||
private async loadContacts() {
|
private async loadContacts() {
|
||||||
this.allContacts = await db.contacts.toArray();
|
this.allContacts = await this.$platform.getAllContacts();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -663,10 +651,12 @@ export default class HomeView extends Vue {
|
|||||||
this.activeDid,
|
this.activeDid,
|
||||||
);
|
);
|
||||||
if (resp.status === 200) {
|
if (resp.status === 200) {
|
||||||
await updateAccountSettings(this.activeDid, {
|
const platform = this.$platform;
|
||||||
|
const settings = await platform.getActiveAccountSettings();
|
||||||
|
await platform.updateAccountSettings(this.activeDid, {
|
||||||
apiServer: this.apiServer,
|
apiServer: this.apiServer,
|
||||||
isRegistered: true,
|
isRegistered: true,
|
||||||
...(await retrieveSettingsForActiveAccount()),
|
...settings,
|
||||||
});
|
});
|
||||||
this.isRegistered = true;
|
this.isRegistered = true;
|
||||||
}
|
}
|
||||||
@@ -728,7 +718,8 @@ export default class HomeView extends Vue {
|
|||||||
* Called by mounted()
|
* Called by mounted()
|
||||||
*/
|
*/
|
||||||
private async checkOnboarding() {
|
private async checkOnboarding() {
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const platform = this.$platform;
|
||||||
|
const settings = await platform.getActiveAccountSettings();
|
||||||
if (!settings.finishedOnboarding) {
|
if (!settings.finishedOnboarding) {
|
||||||
(this.$refs.onboardingDialog as OnboardingDialog).open(OnboardPage.Home);
|
(this.$refs.onboardingDialog as OnboardingDialog).open(OnboardPage.Home);
|
||||||
}
|
}
|
||||||
@@ -744,7 +735,7 @@ export default class HomeView extends Vue {
|
|||||||
* @param err Error object with optional userMessage
|
* @param err Error object with optional userMessage
|
||||||
*/
|
*/
|
||||||
private handleError(err: unknown) {
|
private handleError(err: unknown) {
|
||||||
logConsoleAndDb("Error retrieving settings or feed: " + err, true);
|
logger.error("Error retrieving settings or feed:", err);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -790,7 +781,8 @@ export default class HomeView extends Vue {
|
|||||||
* Called by FeedFilters component when filters change
|
* Called by FeedFilters component when filters change
|
||||||
*/
|
*/
|
||||||
async reloadFeedOnChange() {
|
async reloadFeedOnChange() {
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const platform = this.$platform;
|
||||||
|
const settings = await platform.getActiveAccountSettings();
|
||||||
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
|
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
|
||||||
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
|
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
|
||||||
this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings);
|
this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings);
|
||||||
@@ -1064,7 +1056,7 @@ export default class HomeView extends Vue {
|
|||||||
* @returns The fulfills plan object
|
* @returns The fulfills plan object
|
||||||
*/
|
*/
|
||||||
private async getFulfillsPlan(record: GiveSummaryRecord) {
|
private async getFulfillsPlan(record: GiveSummaryRecord) {
|
||||||
return await getPlanFromCache(
|
return await this.$platform.getPlanFromCache(
|
||||||
record.fulfillsPlanHandleId,
|
record.fulfillsPlanHandleId,
|
||||||
this.axios,
|
this.axios,
|
||||||
this.apiServer,
|
this.apiServer,
|
||||||
@@ -1142,7 +1134,7 @@ export default class HomeView extends Vue {
|
|||||||
* Called by processRecord()
|
* Called by processRecord()
|
||||||
*/
|
*/
|
||||||
private async getProvidedByPlan(provider: Provider | undefined) {
|
private async getProvidedByPlan(provider: Provider | undefined) {
|
||||||
return await getPlanFromCache(
|
return await this.$platform.getPlanFromCache(
|
||||||
provider?.identifier as string,
|
provider?.identifier as string,
|
||||||
this.axios,
|
this.axios,
|
||||||
this.apiServer,
|
this.apiServer,
|
||||||
@@ -1197,14 +1189,14 @@ export default class HomeView extends Vue {
|
|||||||
giver: didInfoForContact(
|
giver: didInfoForContact(
|
||||||
giverDid,
|
giverDid,
|
||||||
this.activeDid,
|
this.activeDid,
|
||||||
contactForDid(giverDid, this.allContacts),
|
this.$platform.getContactForDid(giverDid, this.allContacts),
|
||||||
this.allMyDids,
|
this.allMyDids,
|
||||||
),
|
),
|
||||||
image: claim.image,
|
image: claim.image,
|
||||||
issuer: didInfoForContact(
|
issuer: didInfoForContact(
|
||||||
record.issuerDid,
|
record.issuerDid,
|
||||||
this.activeDid,
|
this.activeDid,
|
||||||
contactForDid(record.issuerDid, this.allContacts),
|
this.$platform.getContactForDid(record.issuerDid, this.allContacts),
|
||||||
this.allMyDids,
|
this.allMyDids,
|
||||||
),
|
),
|
||||||
providerPlanHandleId: provider?.identifier as string,
|
providerPlanHandleId: provider?.identifier as string,
|
||||||
@@ -1213,7 +1205,7 @@ export default class HomeView extends Vue {
|
|||||||
receiver: didInfoForContact(
|
receiver: didInfoForContact(
|
||||||
recipientDid,
|
recipientDid,
|
||||||
this.activeDid,
|
this.activeDid,
|
||||||
contactForDid(recipientDid, this.allContacts),
|
this.$platform.getContactForDid(recipientDid, this.allContacts),
|
||||||
this.allMyDids,
|
this.allMyDids,
|
||||||
),
|
),
|
||||||
} as GiveRecordWithContactInfo;
|
} as GiveRecordWithContactInfo;
|
||||||
@@ -1230,8 +1222,7 @@ export default class HomeView extends Vue {
|
|||||||
this.feedLastViewedClaimId == null ||
|
this.feedLastViewedClaimId == null ||
|
||||||
this.feedLastViewedClaimId < records[0].jwtId
|
this.feedLastViewedClaimId < records[0].jwtId
|
||||||
) {
|
) {
|
||||||
await db.open();
|
await this.$platform.updateAccountSettings(this.activeDid, {
|
||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
|
||||||
lastViewedClaimId: records[0].jwtId,
|
lastViewedClaimId: records[0].jwtId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1264,13 +1255,13 @@ export default class HomeView extends Vue {
|
|||||||
* @internal
|
* @internal
|
||||||
* Called by updateAllFeed()
|
* Called by updateAllFeed()
|
||||||
* @param endorserApiServer API server URL
|
* @param endorserApiServer API server URL
|
||||||
* @param beforeId OptioCalled by updateAllFeed()nal ID to fetch earlier results
|
* @param beforeId Optional ID to fetch earlier results
|
||||||
* @returns claims in reverse chronological order
|
* @returns claims in reverse chronological order
|
||||||
*/
|
*/
|
||||||
async retrieveGives(endorserApiServer: string, beforeId?: string) {
|
async retrieveGives(endorserApiServer: string, beforeId?: string) {
|
||||||
const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId;
|
const beforeQuery = beforeId == null ? "" : "&beforeId=" + beforeId;
|
||||||
const doNotShowErrorAgain = !!beforeId; // don't show error again if we're loading more
|
const doNotShowErrorAgain = !!beforeId; // don't show error again if we're loading more
|
||||||
const headers = await getHeaders(
|
const headers = await this.$platform.getHeaders(
|
||||||
this.activeDid,
|
this.activeDid,
|
||||||
doNotShowErrorAgain ? undefined : this.$notify,
|
doNotShowErrorAgain ? undefined : this.$notify,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -106,14 +106,9 @@ import { Router } from "vue-router";
|
|||||||
|
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import { NotificationIface } from "../constants/app";
|
import { NotificationIface } from "../constants/app";
|
||||||
import {
|
|
||||||
accountsDBPromise,
|
|
||||||
db,
|
|
||||||
retrieveSettingsForActiveAccount,
|
|
||||||
} from "../db/index";
|
|
||||||
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
|
||||||
import { retrieveAllAccountsMetadata } from "../libs/util";
|
import { retrieveAllAccountsMetadata } from "../libs/util";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class IdentitySwitcherView extends Vue {
|
export default class IdentitySwitcherView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
@@ -127,7 +122,8 @@ export default class IdentitySwitcherView extends Vue {
|
|||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
try {
|
try {
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const platform = this.$platform;
|
||||||
|
const settings = await platform.getActiveAccountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.apiServerInput = settings.apiServer || "";
|
this.apiServerInput = settings.apiServer || "";
|
||||||
@@ -162,10 +158,8 @@ export default class IdentitySwitcherView extends Vue {
|
|||||||
if (did === "0") {
|
if (did === "0") {
|
||||||
did = undefined;
|
did = undefined;
|
||||||
}
|
}
|
||||||
await db.open();
|
const platform = this.$platform;
|
||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
await platform.updateAccountSettings(this.activeDid, { activeDid: did });
|
||||||
activeDid: did,
|
|
||||||
});
|
|
||||||
this.$router.push({ name: "account" });
|
this.$router.push({ name: "account" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,9 +171,8 @@ export default class IdentitySwitcherView extends Vue {
|
|||||||
title: "Delete Identity?",
|
title: "Delete Identity?",
|
||||||
text: "Are you sure you want to erase this identity? (There is no undo. You may want to select it and back it up just in case.)",
|
text: "Are you sure you want to erase this identity? (There is no undo. You may want to select it and back it up just in case.)",
|
||||||
onYes: async () => {
|
onYes: async () => {
|
||||||
// one of the few times we use accountsDBPromise directly; try to avoid more usage
|
const platform = this.$platform;
|
||||||
const accountsDB = await accountsDBPromise;
|
await platform.deleteAccount(id);
|
||||||
await accountsDB.accounts.delete(id);
|
|
||||||
this.otherIdentities = this.otherIdentities.filter(
|
this.otherIdentities = this.otherIdentities.filter(
|
||||||
(ident) => ident.id !== id,
|
(ident) => ident.id !== id,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -328,30 +328,81 @@ export default class InviteOneView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
addNewContact(did: string, notes: string) {
|
async addNewContact(did: string, notes: string) {
|
||||||
(this.$refs.contactNameDialog as ContactNameDialog).open(
|
(this.$refs.contactNameDialog as ContactNameDialog).open(
|
||||||
"To Whom Did You Send The Invite?",
|
"To Whom Did You Send The Invite?",
|
||||||
"Their name will be added to your contact list.",
|
"Their name will be added to your contact list.",
|
||||||
(name) => {
|
async (name) => {
|
||||||
// the person obviously registered themselves and this user already granted visibility, so we just add them
|
try {
|
||||||
const contact = {
|
// Get the SQLite interface from the platform service
|
||||||
did: did,
|
const sqlite = await this.$platform.getSQLite();
|
||||||
name: name,
|
|
||||||
registered: true,
|
// Create the contact object
|
||||||
};
|
const contact = {
|
||||||
db.contacts.add(contact);
|
did: did,
|
||||||
this.contactsRedeemed[did] = contact;
|
name: name,
|
||||||
this.$notify(
|
registered: true,
|
||||||
{
|
notes: notes,
|
||||||
group: "alert",
|
// Convert contact methods to JSON string as per schema
|
||||||
type: "success",
|
contactMethods: JSON.stringify([]),
|
||||||
title: "Contact Added",
|
// Other fields can be null/undefined as they're optional
|
||||||
text: `${name} has been added to your contacts.`,
|
nextPubKeyHashB64: null,
|
||||||
},
|
profileImageUrl: null,
|
||||||
3000,
|
publicKeyBase64: null,
|
||||||
);
|
seesMe: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Insert the contact using a transaction
|
||||||
|
await sqlite.transaction([
|
||||||
|
{
|
||||||
|
sql: `
|
||||||
|
INSERT INTO contacts (
|
||||||
|
did, name, registered, notes, contactMethods,
|
||||||
|
nextPubKeyHashB64, profileImageUrl, publicKeyBase64, seesMe
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`,
|
||||||
|
params: [
|
||||||
|
contact.did,
|
||||||
|
contact.name,
|
||||||
|
contact.registered ? 1 : 0, // Convert boolean to integer for SQLite
|
||||||
|
contact.notes,
|
||||||
|
contact.contactMethods,
|
||||||
|
contact.nextPubKeyHashB64,
|
||||||
|
contact.profileImageUrl,
|
||||||
|
contact.publicKeyBase64,
|
||||||
|
contact.seesMe ? 1 : 0, // Convert boolean to integer for SQLite
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update the local contacts cache
|
||||||
|
this.contactsRedeemed[did] = contact;
|
||||||
|
|
||||||
|
// Notify success
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Contact Added",
|
||||||
|
text: `${name} has been added to your contacts.`,
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
// Handle any errors
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error Adding Contact",
|
||||||
|
text: "Failed to add contact to database.",
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
logger.error("Error adding contact:", error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
() => {},
|
() => {}, // onCancel callback
|
||||||
notes,
|
notes,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,11 +154,6 @@ import GiftedDialog from "../components/GiftedDialog.vue";
|
|||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import EntityIcon from "../components/EntityIcon.vue";
|
import EntityIcon from "../components/EntityIcon.vue";
|
||||||
import { NotificationIface } from "../constants/app";
|
import { NotificationIface } from "../constants/app";
|
||||||
import {
|
|
||||||
db,
|
|
||||||
retrieveSettingsForActiveAccount,
|
|
||||||
updateAccountSettings,
|
|
||||||
} from "../db/index";
|
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import { Router } from "vue-router";
|
import { Router } from "vue-router";
|
||||||
import { OfferSummaryRecord, OfferToPlanSummaryRecord } from "../interfaces";
|
import { OfferSummaryRecord, OfferToPlanSummaryRecord } from "../interfaces";
|
||||||
@@ -169,6 +164,7 @@ import {
|
|||||||
getNewOffersToUserProjects,
|
getNewOffersToUserProjects,
|
||||||
} from "../libs/endorserServer";
|
} from "../libs/endorserServer";
|
||||||
import { retrieveAccountDids } from "../libs/util";
|
import { retrieveAccountDids } from "../libs/util";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { GiftedDialog, QuickNav, EntityIcon },
|
components: { GiftedDialog, QuickNav, EntityIcon },
|
||||||
@@ -194,14 +190,15 @@ export default class NewActivityView extends Vue {
|
|||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
try {
|
try {
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const platform = this.$platform;
|
||||||
|
const settings = await platform.getActiveAccountSettings();
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId || "";
|
this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId || "";
|
||||||
this.lastAckedOfferToUserProjectsJwtId =
|
this.lastAckedOfferToUserProjectsJwtId =
|
||||||
settings.lastAckedOfferToUserProjectsJwtId || "";
|
settings.lastAckedOfferToUserProjectsJwtId || "";
|
||||||
|
|
||||||
this.allContacts = await db.contacts.toArray();
|
this.allContacts = await platform.getContacts();
|
||||||
this.allMyDids = await retrieveAccountDids();
|
this.allMyDids = await retrieveAccountDids();
|
||||||
|
|
||||||
const offersToUserData = await getNewOffersToUser(
|
const offersToUserData = await getNewOffersToUser(
|
||||||
@@ -240,7 +237,8 @@ export default class NewActivityView extends Vue {
|
|||||||
async expandOffersToUserAndMarkRead() {
|
async expandOffersToUserAndMarkRead() {
|
||||||
this.showOffersDetails = !this.showOffersDetails;
|
this.showOffersDetails = !this.showOffersDetails;
|
||||||
if (this.showOffersDetails) {
|
if (this.showOffersDetails) {
|
||||||
await updateAccountSettings(this.activeDid, {
|
const platform = this.$platform;
|
||||||
|
await platform.updateAccountSettings(this.activeDid, {
|
||||||
lastAckedOfferToUserJwtId: this.newOffersToUser[0].jwtId,
|
lastAckedOfferToUserJwtId: this.newOffersToUser[0].jwtId,
|
||||||
});
|
});
|
||||||
// note that we don't update this.lastAckedOfferToUserJwtId in case they
|
// note that we don't update this.lastAckedOfferToUserJwtId in case they
|
||||||
@@ -261,14 +259,15 @@ export default class NewActivityView extends Vue {
|
|||||||
const index = this.newOffersToUser.findIndex(
|
const index = this.newOffersToUser.findIndex(
|
||||||
(offer) => offer.jwtId === jwtId,
|
(offer) => offer.jwtId === jwtId,
|
||||||
);
|
);
|
||||||
|
const platform = this.$platform;
|
||||||
if (index !== -1 && index < this.newOffersToUser.length - 1) {
|
if (index !== -1 && index < this.newOffersToUser.length - 1) {
|
||||||
// Set to the next offer's jwtId
|
// Set to the next offer's jwtId
|
||||||
await updateAccountSettings(this.activeDid, {
|
await platform.updateAccountSettings(this.activeDid, {
|
||||||
lastAckedOfferToUserJwtId: this.newOffersToUser[index + 1].jwtId,
|
lastAckedOfferToUserJwtId: this.newOffersToUser[index + 1].jwtId,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// it's the last entry (or not found), so just keep it the same
|
// it's the last entry (or not found), so just keep it the same
|
||||||
await updateAccountSettings(this.activeDid, {
|
await platform.updateAccountSettings(this.activeDid, {
|
||||||
lastAckedOfferToUserJwtId: this.lastAckedOfferToUserJwtId,
|
lastAckedOfferToUserJwtId: this.lastAckedOfferToUserJwtId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -287,7 +286,8 @@ export default class NewActivityView extends Vue {
|
|||||||
this.showOffersToUserProjectsDetails =
|
this.showOffersToUserProjectsDetails =
|
||||||
!this.showOffersToUserProjectsDetails;
|
!this.showOffersToUserProjectsDetails;
|
||||||
if (this.showOffersToUserProjectsDetails) {
|
if (this.showOffersToUserProjectsDetails) {
|
||||||
await updateAccountSettings(this.activeDid, {
|
const platform = this.$platform;
|
||||||
|
await platform.updateAccountSettings(this.activeDid, {
|
||||||
lastAckedOfferToUserProjectsJwtId:
|
lastAckedOfferToUserProjectsJwtId:
|
||||||
this.newOffersToUserProjects[0].jwtId,
|
this.newOffersToUserProjects[0].jwtId,
|
||||||
});
|
});
|
||||||
@@ -309,15 +309,16 @@ export default class NewActivityView extends Vue {
|
|||||||
const index = this.newOffersToUserProjects.findIndex(
|
const index = this.newOffersToUserProjects.findIndex(
|
||||||
(offer) => offer.jwtId === jwtId,
|
(offer) => offer.jwtId === jwtId,
|
||||||
);
|
);
|
||||||
|
const platform = this.$platform;
|
||||||
if (index !== -1 && index < this.newOffersToUserProjects.length - 1) {
|
if (index !== -1 && index < this.newOffersToUserProjects.length - 1) {
|
||||||
// Set to the next offer's jwtId
|
// Set to the next offer's jwtId
|
||||||
await updateAccountSettings(this.activeDid, {
|
await platform.updateAccountSettings(this.activeDid, {
|
||||||
lastAckedOfferToUserProjectsJwtId:
|
lastAckedOfferToUserProjectsJwtId:
|
||||||
this.newOffersToUserProjects[index + 1].jwtId,
|
this.newOffersToUserProjects[index + 1].jwtId,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// it's the last entry (or not found), so just keep it the same
|
// it's the last entry (or not found), so just keep it the same
|
||||||
await updateAccountSettings(this.activeDid, {
|
await platform.updateAccountSettings(this.activeDid, {
|
||||||
lastAckedOfferToUserProjectsJwtId:
|
lastAckedOfferToUserProjectsJwtId:
|
||||||
this.lastAckedOfferToUserProjectsJwtId,
|
this.lastAckedOfferToUserProjectsJwtId,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -145,11 +145,6 @@ import { Router } from "vue-router";
|
|||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import TopMessage from "../components/TopMessage.vue";
|
import TopMessage from "../components/TopMessage.vue";
|
||||||
import { NotificationIface } from "../constants/app";
|
import { NotificationIface } from "../constants/app";
|
||||||
import {
|
|
||||||
accountsDBPromise,
|
|
||||||
db,
|
|
||||||
retrieveSettingsForActiveAccount,
|
|
||||||
} from "../db/index";
|
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import {
|
import {
|
||||||
GenericCredWrapper,
|
GenericCredWrapper,
|
||||||
@@ -165,6 +160,7 @@ import {
|
|||||||
getHeaders,
|
getHeaders,
|
||||||
} from "../libs/endorserServer";
|
} from "../libs/endorserServer";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
methods: { claimSpecialDescription },
|
methods: { claimSpecialDescription },
|
||||||
components: {
|
components: {
|
||||||
@@ -172,7 +168,7 @@ import { logger } from "../utils/logger";
|
|||||||
TopMessage,
|
TopMessage,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class QuickActionBvcBeginView extends Vue {
|
export default class QuickActionBvcEndView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
@@ -191,10 +187,11 @@ export default class QuickActionBvcBeginView extends Vue {
|
|||||||
async created() {
|
async created() {
|
||||||
this.loadingConfirms = true;
|
this.loadingConfirms = true;
|
||||||
|
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const platform = this.$platform;
|
||||||
|
const settings = await platform.getActiveAccountSettings();
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
this.allContacts = await db.contacts.toArray();
|
this.allContacts = await platform.getContacts();
|
||||||
|
|
||||||
let currentOrPreviousSat = DateTime.now().setZone("America/Denver");
|
let currentOrPreviousSat = DateTime.now().setZone("America/Denver");
|
||||||
if (currentOrPreviousSat.weekday < 6) {
|
if (currentOrPreviousSat.weekday < 6) {
|
||||||
@@ -213,10 +210,8 @@ export default class QuickActionBvcBeginView extends Vue {
|
|||||||
suppressMilliseconds: true,
|
suppressMilliseconds: true,
|
||||||
}) || "";
|
}) || "";
|
||||||
|
|
||||||
const accountsDB = await accountsDBPromise;
|
const accounts = await platform.getAllAccounts();
|
||||||
await accountsDB.open();
|
this.allMyDids = accounts.map((acc) => acc.did);
|
||||||
const allAccounts = await accountsDB.accounts.toArray();
|
|
||||||
this.allMyDids = allAccounts.map((acc) => acc.did);
|
|
||||||
const headers = await getHeaders(this.activeDid);
|
const headers = await getHeaders(this.activeDid);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
|
|||||||
@@ -113,9 +113,9 @@ import { Router } from "vue-router";
|
|||||||
|
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import { NotificationIface } from "../constants/app";
|
import { NotificationIface } from "../constants/app";
|
||||||
import { db, retrieveSettingsForActiveAccount } from "../db/index";
|
import { BoundingBox } from "../db/tables/settings";
|
||||||
import { BoundingBox, MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
const DEFAULT_LAT_LONG_DIFF = 0.01;
|
const DEFAULT_LAT_LONG_DIFF = 0.01;
|
||||||
const WORLD_ZOOM = 2;
|
const WORLD_ZOOM = 2;
|
||||||
const DEFAULT_ZOOM = 2;
|
const DEFAULT_ZOOM = 2;
|
||||||
@@ -147,7 +147,8 @@ export default class SearchAreaView extends Vue {
|
|||||||
searchBox: { name: string; bbox: BoundingBox } | null = null;
|
searchBox: { name: string; bbox: BoundingBox } | null = null;
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const platform = this.$platform;
|
||||||
|
const settings = await platform.getActiveAccountSettings();
|
||||||
this.searchBox = settings.searchBoxes?.[0] || null;
|
this.searchBox = settings.searchBoxes?.[0] || null;
|
||||||
this.resetLatLong();
|
this.resetLatLong();
|
||||||
}
|
}
|
||||||
@@ -204,8 +205,8 @@ export default class SearchAreaView extends Vue {
|
|||||||
westLong: this.localCenterLong - this.localLongDiff,
|
westLong: this.localCenterLong - this.localLongDiff,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
await db.open();
|
const platform = this.$platform;
|
||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
await platform.updateMasterSettings({
|
||||||
searchBoxes: [newSearchBox],
|
searchBoxes: [newSearchBox],
|
||||||
});
|
});
|
||||||
this.searchBox = newSearchBox;
|
this.searchBox = newSearchBox;
|
||||||
@@ -251,8 +252,8 @@ export default class SearchAreaView extends Vue {
|
|||||||
|
|
||||||
public async forgetSearchBox() {
|
public async forgetSearchBox() {
|
||||||
try {
|
try {
|
||||||
await db.open();
|
const platform = this.$platform;
|
||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
await platform.updateMasterSettings({
|
||||||
searchBoxes: [],
|
searchBoxes: [],
|
||||||
filterFeedByNearby: false,
|
filterFeedByNearby: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"include": [
|
"include": [
|
||||||
"src/electron/**/*.ts",
|
"src/electron/**/*.ts",
|
||||||
"src/utils/**/*.ts",
|
"src/utils/**/*.ts",
|
||||||
"src/constants/**/*.ts"
|
"src/constants/**/*.ts",
|
||||||
|
"src/services/**/*.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,12 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import { createBuildConfig } from "./vite.config.common.mts";
|
import { createBuildConfig } from "./vite.config.common.mts";
|
||||||
|
|
||||||
export default defineConfig(async () => createBuildConfig('capacitor'));
|
export default defineConfig(
|
||||||
|
async () => {
|
||||||
|
const baseConfig = await createBuildConfig('capacitor');
|
||||||
|
return mergeConfig(baseConfig, {
|
||||||
|
optimizeDeps: {
|
||||||
|
include: ['@capacitor-community/sqlite']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -30,7 +30,7 @@ export default defineConfig(async () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ['@/utils/logger']
|
include: ['@/utils/logger', '@capacitor-community/sqlite']
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user