Compare commits
1 Commits
logging-up
...
build-dev-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff3901c796 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -127,5 +127,4 @@ electron/out/
|
||||
|
||||
# Gradle cache files
|
||||
android/.gradle/file-system.probe
|
||||
android/.gradle/caches/
|
||||
coverage
|
||||
android/.gradle/caches/
|
||||
@@ -20,6 +20,9 @@ The web build system is fully integrated into `package.json` with the following
|
||||
# Development (starts dev server)
|
||||
npm run build:web:dev # Development server with hot reload
|
||||
|
||||
# Development build to dist (no HMR)
|
||||
npm run build:web:dev:dist # Development configuration built to dist folder
|
||||
|
||||
# Production builds
|
||||
npm run build:web:test # Testing environment build
|
||||
npm run build:web:prod # Production environment build
|
||||
@@ -56,6 +59,7 @@ The `build-web.sh` script supports comprehensive command-line options:
|
||||
|
||||
# Environment-specific builds
|
||||
./scripts/build-web.sh --dev # Development server
|
||||
./scripts/build-web.sh --dev --build-dev-to-dist # Development build to dist
|
||||
./scripts/build-web.sh --test # Testing build
|
||||
./scripts/build-web.sh --prod # Production build
|
||||
|
||||
@@ -73,6 +77,7 @@ The `build-web.sh` script supports comprehensive command-line options:
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--dev`, `--development` | Development mode (starts dev server) | ✅ |
|
||||
| `--build-dev-to-dist` | Build development configuration to dist folder | |
|
||||
| `--test` | Testing environment build | |
|
||||
| `--prod`, `--production` | Production environment build | |
|
||||
| `--docker` | Build and create Docker image | |
|
||||
@@ -92,6 +97,14 @@ The `build-web.sh` script supports comprehensive command-line options:
|
||||
4. **Hot Reload**: Enable live reload and HMR
|
||||
5. **PWA Setup**: Configure PWA for development
|
||||
|
||||
### Development Build to Dist Flow
|
||||
|
||||
1. **Environment Setup**: Load development environment variables
|
||||
2. **Validation**: Check for required dependencies
|
||||
3. **Build Process**: Run Vite build with development configuration
|
||||
4. **Output**: Create static files in dist folder with development settings
|
||||
5. **No HMR**: No development server started
|
||||
|
||||
### Production Mode Flow
|
||||
|
||||
1. **Environment Setup**: Load production environment variables
|
||||
@@ -278,7 +291,30 @@ docker push timesafari-web:production
|
||||
- **Hot Reload**: Enabled with Vite HMR
|
||||
- **Source Maps**: Enabled for debugging
|
||||
|
||||
### Production Mode
|
||||
### Development Build to Dist
|
||||
|
||||
```bash
|
||||
dist/
|
||||
├── index.html # Main HTML file
|
||||
├── manifest.webmanifest # PWA manifest
|
||||
├── sw.js # Service worker
|
||||
├── workbox-*.js # Workbox library
|
||||
└── assets/
|
||||
├── index-*.js # Main application bundle (development config)
|
||||
├── index-*.css # Stylesheet bundle (development config)
|
||||
├── icons/ # PWA icons
|
||||
└── images/ # Optimized images
|
||||
```
|
||||
|
||||
**Features**:
|
||||
|
||||
- Development environment variables
|
||||
- Source maps enabled
|
||||
- No minification
|
||||
- PWA enabled for testing
|
||||
- No HMR server running
|
||||
|
||||
### Production Mode File Structure
|
||||
|
||||
```bash
|
||||
dist/
|
||||
@@ -347,6 +383,20 @@ npm run build:web:dev
|
||||
# PWA features available
|
||||
```
|
||||
|
||||
### Development Build Workflow
|
||||
|
||||
```bash
|
||||
# Build development configuration to dist
|
||||
npm run build:web:dev:dist
|
||||
|
||||
# Serve the development build locally
|
||||
npm run build:web:serve
|
||||
|
||||
# Access at http://localhost:8080
|
||||
# No hot reload, but development configuration
|
||||
# Useful for testing development settings without HMR
|
||||
```
|
||||
|
||||
### Testing Workflow
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
# Debug Logging Control
|
||||
|
||||
## Overview
|
||||
|
||||
Debug logging in TimeSafari can be controlled via environment variables to reduce console noise during development and production.
|
||||
|
||||
## Current Behavior
|
||||
|
||||
By default, debug logging is **disabled** to reduce console noise. Debug logs are very verbose and include detailed information about:
|
||||
|
||||
- Camera operations (ImageMethodDialog, PhotoDialog)
|
||||
- Database operations (CapacitorPlatformService)
|
||||
- QR Scanner operations
|
||||
- Platform service operations
|
||||
- Component lifecycle events
|
||||
|
||||
## How to Enable Debug Logging
|
||||
|
||||
### Option 1: Environment Variable (Recommended)
|
||||
|
||||
Set the `VITE_DEBUG_LOGGING` environment variable to `true`:
|
||||
|
||||
```bash
|
||||
# For development
|
||||
VITE_DEBUG_LOGGING=true npm run dev
|
||||
|
||||
# For web builds
|
||||
VITE_DEBUG_LOGGING=true npm run build:web:dev
|
||||
|
||||
# For Electron builds
|
||||
VITE_DEBUG_LOGGING=true npm run build:electron:dev
|
||||
```
|
||||
|
||||
### Option 2: .env File
|
||||
|
||||
Create or modify `.env.local` file:
|
||||
|
||||
```bash
|
||||
# Enable debug logging
|
||||
VITE_DEBUG_LOGGING=true
|
||||
```
|
||||
|
||||
### Option 3: Package.json Scripts
|
||||
|
||||
Add debug variants to your package.json scripts:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev:debug": "VITE_DEBUG_LOGGING=true npm run dev",
|
||||
"build:web:debug": "VITE_DEBUG_LOGGING=true npm run build:web:dev",
|
||||
"build:electron:debug": "VITE_DEBUG_LOGGING=true npm run build:electron:dev"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Debug Logging Rules
|
||||
|
||||
Debug logging follows these rules:
|
||||
|
||||
1. **Only shows in development mode** (not production)
|
||||
2. **Only shows for web platform** (not Electron)
|
||||
3. **Must be explicitly enabled** via `VITE_DEBUG_LOGGING=true`
|
||||
4. **Never logged to database** (to reduce noise)
|
||||
5. **Very verbose** - includes detailed component state and operations
|
||||
|
||||
## Components with Debug Logging
|
||||
|
||||
The following components include debug logging:
|
||||
|
||||
- **ImageMethodDialog.vue** - Camera operations, preview state
|
||||
- **PhotoDialog.vue** - Camera operations, video setup
|
||||
- **AmountInput.vue** - Input validation, increment/decrement
|
||||
- **GiftedDialog.vue** - Amount updates, form state
|
||||
- **CapacitorPlatformService.ts** - Database operations, migrations
|
||||
- **QRScanner services** - Camera permissions, scanner state
|
||||
- **PlatformServiceMixin.ts** - Service initialization
|
||||
|
||||
## Example Debug Output
|
||||
|
||||
When enabled, you'll see output like:
|
||||
|
||||
```
|
||||
[ImageMethodDialog] open called
|
||||
[ImageMethodDialog] Camera facing mode: user
|
||||
[ImageMethodDialog] Should mirror video: true
|
||||
[ImageMethodDialog] Platform capabilities: {isMobile: false, hasCamera: true}
|
||||
[ImageMethodDialog] Starting camera preview from open()
|
||||
[ImageMethodDialog] startCameraPreview called
|
||||
[ImageMethodDialog] Current showCameraPreview state: true
|
||||
[ImageMethodDialog] MediaDevices available: true
|
||||
[ImageMethodDialog] getUserMedia constraints: {video: {facingMode: "user"}}
|
||||
[ImageMethodDialog] Setting video element srcObject
|
||||
[ImageMethodDialog] Video metadata loaded, starting playback
|
||||
[ImageMethodDialog] Video element started playing successfully
|
||||
```
|
||||
|
||||
## Disabling Debug Logging
|
||||
|
||||
To disable debug logging:
|
||||
|
||||
1. **Remove the environment variable:**
|
||||
```bash
|
||||
unset VITE_DEBUG_LOGGING
|
||||
```
|
||||
|
||||
2. **Or set it to false:**
|
||||
```bash
|
||||
VITE_DEBUG_LOGGING=false npm run dev
|
||||
```
|
||||
|
||||
3. **Or remove from .env file:**
|
||||
```bash
|
||||
# Comment out or remove this line
|
||||
# VITE_DEBUG_LOGGING=true
|
||||
```
|
||||
|
||||
## Production Behavior
|
||||
|
||||
In production builds, debug logging is **always disabled** regardless of the environment variable setting. This ensures:
|
||||
|
||||
- No debug output in production
|
||||
- No performance impact from debug logging
|
||||
- Clean console output for end users
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Debug Logging Not Working
|
||||
|
||||
1. **Check environment variable:**
|
||||
```bash
|
||||
echo $VITE_DEBUG_LOGGING
|
||||
```
|
||||
|
||||
2. **Verify it's set to "true":**
|
||||
```bash
|
||||
VITE_DEBUG_LOGGING=true npm run dev
|
||||
```
|
||||
|
||||
3. **Check if you're in development mode:**
|
||||
- Debug logging only works in development (`NODE_ENV !== "production"`)
|
||||
- Production builds always disable debug logging
|
||||
|
||||
### Too Much Debug Output
|
||||
|
||||
If debug logging is too verbose:
|
||||
|
||||
1. **Disable it completely:**
|
||||
```bash
|
||||
unset VITE_DEBUG_LOGGING
|
||||
```
|
||||
|
||||
2. **Or modify specific components** to use `logger.log` instead of `logger.debug`
|
||||
|
||||
3. **Or add conditional logging** in components:
|
||||
```typescript
|
||||
if (process.env.VITE_DEBUG_LOGGING === "true") {
|
||||
logger.debug("Detailed debug info");
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use debug logging sparingly** - only for troubleshooting
|
||||
2. **Disable in production** - debug logging is automatically disabled
|
||||
3. **Use specific component prefixes** - makes it easier to filter output
|
||||
4. **Consider log levels** - use `logger.log` for important info, `logger.debug` for verbose details
|
||||
5. **Test without debug logging** - ensure your app works without debug output
|
||||
|
||||
## Future Improvements
|
||||
|
||||
Potential enhancements to the debug logging system:
|
||||
|
||||
1. **Component-specific debug flags** - enable debug for specific components only
|
||||
2. **Log level filtering** - show only certain types of debug messages
|
||||
3. **Debug UI panel** - in-app debug information display
|
||||
4. **Structured logging** - JSON format for better parsing
|
||||
5. **Performance monitoring** - track impact of debug logging
|
||||
@@ -1,113 +0,0 @@
|
||||
# $updateSettings to $saveSettings Consolidation Plan
|
||||
|
||||
## Overview
|
||||
Consolidate `$updateSettings` method into `$saveSettings` to eliminate code duplication and improve maintainability. The `$updateSettings` method is currently just a thin wrapper around `$saveSettings` and `$saveUserSettings`, providing no additional functionality.
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
### Current Implementation
|
||||
```typescript
|
||||
// Current $updateSettings - just a wrapper
|
||||
async $updateSettings(changes: Partial<Settings>, did?: string): Promise<boolean> {
|
||||
try {
|
||||
if (did) {
|
||||
return await this.$saveUserSettings(did, changes);
|
||||
} else {
|
||||
return await this.$saveSettings(changes);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("[PlatformServiceMixin] Error updating settings:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Usage Statistics
|
||||
- **$updateSettings**: 42 references across codebase
|
||||
- **$saveSettings**: 38 references across codebase
|
||||
- **$saveUserSettings**: 12 references across codebase
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Phase 1: Documentation and Planning ✅
|
||||
- [x] Document current usage patterns
|
||||
- [x] Identify all call sites
|
||||
- [x] Create migration plan
|
||||
|
||||
### Phase 2: Implementation
|
||||
- [ ] Update `$saveSettings` to accept optional `did` parameter
|
||||
- [ ] Add error handling to `$saveSettings` (currently missing)
|
||||
- [ ] Deprecate `$updateSettings` with migration notice
|
||||
- [ ] Update all call sites to use `$saveSettings` directly
|
||||
|
||||
### Phase 3: Cleanup
|
||||
- [ ] Remove `$updateSettings` method
|
||||
- [ ] Update documentation
|
||||
- [ ] Update tests
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Enhanced $saveSettings Method
|
||||
```typescript
|
||||
async $saveSettings(changes: Partial<Settings>, did?: string): Promise<boolean> {
|
||||
try {
|
||||
// Convert settings for database storage
|
||||
const convertedChanges = this._convertSettingsForStorage(changes);
|
||||
|
||||
if (did) {
|
||||
// User-specific settings
|
||||
return await this.$saveUserSettings(did, convertedChanges);
|
||||
} else {
|
||||
// Default settings
|
||||
return await this.$saveSettings(convertedChanges);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("[PlatformServiceMixin] Error saving settings:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Migration Benefits
|
||||
1. **Reduced Code Duplication**: Single method handles both use cases
|
||||
2. **Improved Maintainability**: One place to fix issues
|
||||
3. **Consistent Error Handling**: Unified error handling approach
|
||||
4. **Better Type Safety**: Single method signature to maintain
|
||||
|
||||
### Risk Assessment
|
||||
- **Low Risk**: `$updateSettings` is just a wrapper, no complex logic
|
||||
- **Backward Compatible**: Can maintain both methods during transition
|
||||
- **Testable**: Existing tests can be updated incrementally
|
||||
|
||||
## Call Site Migration Examples
|
||||
|
||||
### Before (using $updateSettings)
|
||||
```typescript
|
||||
await this.$updateSettings({ searchBoxes: [newSearchBox] });
|
||||
await this.$updateSettings({ filterFeedByNearby: false }, userDid);
|
||||
```
|
||||
|
||||
### After (using $saveSettings)
|
||||
```typescript
|
||||
await this.$saveSettings({ searchBoxes: [newSearchBox] });
|
||||
await this.$saveSettings({ filterFeedByNearby: false }, userDid);
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
1. **Unit Tests**: Update existing tests to use `$saveSettings`
|
||||
2. **Integration Tests**: Verify both default and user-specific settings work
|
||||
3. **Migration Tests**: Ensure searchBoxes conversion still works
|
||||
4. **Performance Tests**: Verify no performance regression
|
||||
|
||||
## Timeline
|
||||
- **Phase 1**: ✅ Complete
|
||||
- **Phase 2**: 1-2 days
|
||||
- **Phase 3**: 1 day
|
||||
- **Total**: 2-3 days
|
||||
|
||||
## Success Criteria
|
||||
- [ ] All existing functionality preserved
|
||||
- [ ] No performance regression
|
||||
- [ ] All tests passing
|
||||
- [ ] Reduced code duplication
|
||||
- [ ] Improved maintainability
|
||||
@@ -14,10 +14,7 @@ import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher }
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
if (!gotTheLock) {
|
||||
// Debug logging - only show when VITE_DEBUG_LOGGING is enabled
|
||||
if (process.env.VITE_DEBUG_LOGGING === 'true') {
|
||||
console.log('[Electron] Another instance is already running. Exiting immediately...');
|
||||
}
|
||||
console.log('[Electron] Another instance is already running. Exiting immediately...');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@@ -93,10 +90,7 @@ if (electronIsDev) {
|
||||
|
||||
// Handle second instance launch (focus existing window and show dialog)
|
||||
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||
// Debug logging - only show when VITE_DEBUG_LOGGING is enabled
|
||||
if (process.env.VITE_DEBUG_LOGGING === 'true') {
|
||||
console.log('[Electron] Second instance attempted to launch');
|
||||
}
|
||||
console.log('[Electron] Second instance attempted to launch');
|
||||
|
||||
// Someone tried to run a second instance, we should focus our window instead
|
||||
const mainWindow = myCapacitorApp.getMainWindow();
|
||||
@@ -169,10 +163,7 @@ ipcMain.handle('export-data-to-downloads', async (_event, fileName: string, data
|
||||
// Write the file to the Downloads directory
|
||||
await fs.writeFile(filePath, data, 'utf-8');
|
||||
|
||||
// Debug logging - only show when VITE_DEBUG_LOGGING is enabled
|
||||
if (process.env.VITE_DEBUG_LOGGING === 'true') {
|
||||
console.log(`[Electron Main] File exported successfully: ${filePath}`);
|
||||
}
|
||||
console.log(`[Electron Main] File exported successfully: ${filePath}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -3,10 +3,7 @@ import { contextBridge, ipcRenderer } from 'electron';
|
||||
require('./rt/electron-rt');
|
||||
//////////////////////////////
|
||||
// User Defined Preload scripts below
|
||||
// Debug logging - only show when VITE_DEBUG_LOGGING is enabled
|
||||
if (process.env.VITE_DEBUG_LOGGING === 'true') {
|
||||
console.log('User Preload!');
|
||||
}
|
||||
console.log('User Preload!');
|
||||
|
||||
/**
|
||||
* Expose secure IPC APIs to the renderer process.
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"build:ios:deploy": "./scripts/build-ios.sh --deploy",
|
||||
"build:web": "./scripts/build-web.sh",
|
||||
"build:web:dev": "./scripts/build-web.sh --dev",
|
||||
"build:web:dev:dist": "./scripts/build-web.sh --dev --build-dev-to-dist",
|
||||
"build:web:test": "./scripts/build-web.sh --test",
|
||||
"build:web:prod": "./scripts/build-web.sh --prod",
|
||||
"build:web:docker": "./scripts/build-web.sh --docker",
|
||||
|
||||
@@ -186,8 +186,8 @@ clean_ios_build() {
|
||||
log_debug "Cleaned ios/App/DerivedData/"
|
||||
fi
|
||||
|
||||
# Clean Capacitor (using npm script instead of invalid cap clean command)
|
||||
npm run clean:ios || true
|
||||
# Clean Capacitor
|
||||
npx cap clean ios || true
|
||||
|
||||
log_success "iOS build cleaned"
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ BUILD_MODE="development"
|
||||
BUILD_ACTION="build"
|
||||
DOCKER_BUILD=false
|
||||
SERVE_BUILD=false
|
||||
BUILD_DEV_TO_DIST=false
|
||||
|
||||
# Function to show usage
|
||||
show_usage() {
|
||||
@@ -61,6 +62,7 @@ OPTIONS:
|
||||
--help Show this help message
|
||||
--verbose Enable verbose logging
|
||||
--env Show environment variables
|
||||
--build-dev-to-dist Build development configuration to dist folder
|
||||
|
||||
EXAMPLES:
|
||||
$0 # Development build
|
||||
@@ -70,6 +72,7 @@ EXAMPLES:
|
||||
$0 --docker:test # Test + Docker
|
||||
$0 --docker:prod # Production + Docker
|
||||
$0 --serve # Build and serve
|
||||
$0 --build-dev-to-dist # Build development configuration to dist folder
|
||||
|
||||
BUILD MODES:
|
||||
development: Starts Vite development server with hot reload (default)
|
||||
@@ -125,6 +128,10 @@ parse_web_args() {
|
||||
print_env_vars "VITE_"
|
||||
exit 0
|
||||
;;
|
||||
--build-dev-to-dist)
|
||||
BUILD_DEV_TO_DIST=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
log_warn "Unknown option: $1"
|
||||
shift
|
||||
@@ -321,10 +328,28 @@ setup_web_environment
|
||||
|
||||
# Handle different build modes
|
||||
if [ "$BUILD_MODE" = "development" ] && [ "$DOCKER_BUILD" = false ] && [ "$SERVE_BUILD" = false ]; then
|
||||
# Development mode: Start dev server
|
||||
log_info "Development mode detected - starting development server"
|
||||
start_dev_server
|
||||
# Note: start_dev_server doesn't return, it runs the server
|
||||
# Check if we want to build development to dist instead of starting dev server
|
||||
if [ "$BUILD_DEV_TO_DIST" = true ]; then
|
||||
# Development build mode: Build to dist with development configuration
|
||||
log_info "Development build mode detected - building to dist with development configuration"
|
||||
|
||||
# Step 1: Clean dist directory
|
||||
log_info "Cleaning dist directory..."
|
||||
clean_build_artifacts "dist"
|
||||
|
||||
# Step 2: Execute Vite build with development configuration
|
||||
safe_execute "Vite development build" "execute_vite_build development" || exit 3
|
||||
|
||||
log_success "Development build completed successfully!"
|
||||
log_info "Development build output available in: dist/"
|
||||
print_footer "Web Development Build"
|
||||
exit 0
|
||||
else
|
||||
# Development mode: Start dev server
|
||||
log_info "Development mode detected - starting development server"
|
||||
start_dev_server
|
||||
# Note: start_dev_server doesn't return, it runs the server
|
||||
fi
|
||||
elif [ "$SERVE_BUILD" = true ]; then
|
||||
# Serve mode: Build then serve
|
||||
log_info "Serve mode detected - building then serving"
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<a
|
||||
class="cursor-pointer"
|
||||
data-testid="circle-info-link"
|
||||
@click="emitLoadClaim(record.jwtId)"
|
||||
@click="$emit('loadClaim', record.jwtId)"
|
||||
>
|
||||
<font-awesome icon="circle-info" class="fa-fw text-slate-500" />
|
||||
</a>
|
||||
@@ -67,7 +67,7 @@
|
||||
>
|
||||
<a
|
||||
class="block bg-slate-100/50 backdrop-blur-md px-6 py-4 cursor-pointer"
|
||||
@click="emitViewImage(record.image)"
|
||||
@click="$emit('viewImage', record.image)"
|
||||
>
|
||||
<img
|
||||
class="w-full h-auto max-w-lg max-h-96 object-contain mx-auto drop-shadow-md"
|
||||
@@ -80,7 +80,7 @@
|
||||
|
||||
<!-- Description -->
|
||||
<p class="font-medium">
|
||||
<a class="cursor-pointer" @click="emitLoadClaim(record.jwtId)">
|
||||
<a class="cursor-pointer" @click="$emit('loadClaim', record.jwtId)">
|
||||
{{ description }}
|
||||
</a>
|
||||
</p>
|
||||
@@ -248,7 +248,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue, Emit } from "vue-facing-decorator";
|
||||
import { Component, Prop, Vue } from "vue-facing-decorator";
|
||||
import { GiveRecordWithContactInfo } from "@/interfaces/give";
|
||||
import EntityIcon from "./EntityIcon.vue";
|
||||
import { isGiveClaimType, notifyWhyCannotConfirm } from "../libs/util";
|
||||
@@ -340,19 +340,7 @@ export default class ActivityListItem extends Vue {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Emit methods using @Emit decorator
|
||||
@Emit("viewImage")
|
||||
emitViewImage(imageUrl: string) {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
@Emit("loadClaim")
|
||||
emitLoadClaim(jwtId: string) {
|
||||
return jwtId;
|
||||
}
|
||||
|
||||
@Emit("confirmClaim")
|
||||
emitConfirmClaim() {
|
||||
handleConfirmClick() {
|
||||
if (!this.canConfirm) {
|
||||
notifyWhyCannotConfirm(
|
||||
(msg, timeout) => this.notify.info(msg.text ?? "", timeout),
|
||||
@@ -364,11 +352,7 @@ export default class ActivityListItem extends Vue {
|
||||
);
|
||||
return;
|
||||
}
|
||||
return this.record;
|
||||
}
|
||||
|
||||
handleConfirmClick() {
|
||||
this.emitConfirmClaim();
|
||||
this.$emit("confirmClaim", this.record);
|
||||
}
|
||||
|
||||
get friendlyDate(): string {
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
:checked="allContactsSelected"
|
||||
class="align-middle ml-2 h-6 w-6"
|
||||
data-testId="contactCheckAllBottom"
|
||||
@click="emitToggleAllSelection"
|
||||
@click="$emit('toggle-all-selection')"
|
||||
/>
|
||||
<button
|
||||
v-if="!showGiveNumbers"
|
||||
:class="copyButtonClass"
|
||||
:disabled="copyButtonDisabled"
|
||||
@click="emitCopySelected"
|
||||
@click="$emit('copy-selected')"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
@@ -20,7 +20,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop, Emit } from "vue-facing-decorator";
|
||||
import { Component, Vue, Prop } from "vue-facing-decorator";
|
||||
|
||||
/**
|
||||
* ContactBulkActions - Contact bulk actions component
|
||||
@@ -38,16 +38,5 @@ export default class ContactBulkActions extends Vue {
|
||||
@Prop({ required: true }) allContactsSelected!: boolean;
|
||||
@Prop({ required: true }) copyButtonClass!: string;
|
||||
@Prop({ required: true }) copyButtonDisabled!: boolean;
|
||||
|
||||
// Emit methods using @Emit decorator
|
||||
@Emit("toggle-all-selection")
|
||||
emitToggleAllSelection() {
|
||||
// No parameters needed
|
||||
}
|
||||
|
||||
@Emit("copy-selected")
|
||||
emitCopySelected() {
|
||||
// No parameters needed
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop, Emit } from "vue-facing-decorator";
|
||||
import { Component, Vue, Prop } from "vue-facing-decorator";
|
||||
|
||||
/**
|
||||
* ContactInputForm - Contact input form component
|
||||
@@ -165,9 +165,9 @@ export default class ContactInputForm extends Vue {
|
||||
* Handle QR scan button click
|
||||
* Emits qr-scan event for parent handling
|
||||
*/
|
||||
@Emit("qr-scan")
|
||||
private handleQRScan(): void {
|
||||
// QR scan button clicked - event emitted to parent
|
||||
console.log("[ContactInputForm] QR scan button clicked");
|
||||
this.$emit("qr-scan");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -8,21 +8,21 @@
|
||||
:checked="allContactsSelected"
|
||||
class="align-middle ml-2 h-6 w-6"
|
||||
data-testId="contactCheckAllTop"
|
||||
@click="emitToggleAllSelection"
|
||||
@click="$emit('toggle-all-selection')"
|
||||
/>
|
||||
<button
|
||||
v-if="!showGiveNumbers"
|
||||
:class="copyButtonClass"
|
||||
:disabled="copyButtonDisabled"
|
||||
data-testId="copySelectedContactsButtonTop"
|
||||
@click="emitCopySelected"
|
||||
@click="$emit('copy-selected')"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
<font-awesome
|
||||
icon="circle-info"
|
||||
class="text-2xl text-blue-500 ml-2"
|
||||
@click="emitShowCopyInfo"
|
||||
@click="$emit('show-copy-info')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,7 +33,7 @@
|
||||
v-if="showGiveNumbers"
|
||||
class="text-md bg-gradient-to-b shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 rounded-md"
|
||||
:class="giveAmountsButtonClass"
|
||||
@click="emitToggleGiveTotals"
|
||||
@click="$emit('toggle-give-totals')"
|
||||
>
|
||||
{{ giveAmountsButtonText }}
|
||||
<font-awesome icon="left-right" class="fa-fw" />
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
<button
|
||||
class="text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-1.5 rounded-md"
|
||||
@click="emitToggleShowActions"
|
||||
@click="$emit('toggle-show-actions')"
|
||||
>
|
||||
{{ showActionsButtonText }}
|
||||
</button>
|
||||
@@ -50,7 +50,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop, Emit } from "vue-facing-decorator";
|
||||
import { Component, Vue, Prop } from "vue-facing-decorator";
|
||||
|
||||
/**
|
||||
* ContactListHeader - Contact list header component
|
||||
@@ -71,31 +71,5 @@ export default class ContactListHeader extends Vue {
|
||||
@Prop({ required: true }) giveAmountsButtonText!: string;
|
||||
@Prop({ required: true }) showActionsButtonText!: string;
|
||||
@Prop({ required: true }) giveAmountsButtonClass!: Record<string, boolean>;
|
||||
|
||||
// Emit methods using @Emit decorator
|
||||
@Emit("toggle-all-selection")
|
||||
emitToggleAllSelection() {
|
||||
// No parameters needed
|
||||
}
|
||||
|
||||
@Emit("copy-selected")
|
||||
emitCopySelected() {
|
||||
// No parameters needed
|
||||
}
|
||||
|
||||
@Emit("show-copy-info")
|
||||
emitShowCopyInfo() {
|
||||
// No parameters needed
|
||||
}
|
||||
|
||||
@Emit("toggle-give-totals")
|
||||
emitToggleGiveTotals() {
|
||||
// No parameters needed
|
||||
}
|
||||
|
||||
@Emit("toggle-show-actions")
|
||||
emitToggleShowActions() {
|
||||
// No parameters needed
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
:checked="isSelected"
|
||||
class="ml-2 h-6 w-6 flex-shrink-0"
|
||||
data-testId="contactCheckOne"
|
||||
@click="emitToggleSelection(contact.did)"
|
||||
@click="$emit('toggle-selection', contact.did)"
|
||||
/>
|
||||
|
||||
<EntityIcon
|
||||
:contact="contact"
|
||||
:icon-size="48"
|
||||
class="shrink-0 align-text-bottom border border-slate-300 rounded cursor-pointer overflow-hidden"
|
||||
@click="emitShowIdenticon(contact)"
|
||||
@click="$emit('show-identicon', contact)"
|
||||
/>
|
||||
|
||||
<div class="overflow-hidden">
|
||||
@@ -63,7 +63,7 @@
|
||||
<button
|
||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2.5 py-1.5 rounded-l-md"
|
||||
:title="getGiveDescriptionForContact(contact.did, true)"
|
||||
@click="emitShowGiftedDialog(contact.did, activeDid)"
|
||||
@click="$emit('show-gifted-dialog', contact.did, activeDid)"
|
||||
>
|
||||
{{ getGiveAmountForContact(contact.did, true) }}
|
||||
</button>
|
||||
@@ -71,7 +71,7 @@
|
||||
<button
|
||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2.5 py-1.5 rounded-r-md border-l"
|
||||
:title="getGiveDescriptionForContact(contact.did, false)"
|
||||
@click="emitShowGiftedDialog(activeDid, contact.did)"
|
||||
@click="$emit('show-gifted-dialog', activeDid, contact.did)"
|
||||
>
|
||||
{{ getGiveAmountForContact(contact.did, false) }}
|
||||
</button>
|
||||
@@ -81,7 +81,7 @@
|
||||
<button
|
||||
class="text-sm bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-1.5 rounded-md"
|
||||
data-testId="offerButton"
|
||||
@click="emitOpenOfferDialog(contact.did, contact.name)"
|
||||
@click="$emit('open-offer-dialog', contact.did, contact.name)"
|
||||
>
|
||||
Offer
|
||||
</button>
|
||||
@@ -102,7 +102,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop, Emit } from "vue-facing-decorator";
|
||||
import { Component, Vue, Prop } from "vue-facing-decorator";
|
||||
import EntityIcon from "./EntityIcon.vue";
|
||||
import { Contact } from "../db/tables/contacts";
|
||||
import { AppString } from "../constants/app";
|
||||
@@ -140,27 +140,6 @@ export default class ContactListItem extends Vue {
|
||||
// Constants
|
||||
AppString = AppString;
|
||||
|
||||
// Emit methods using @Emit decorator
|
||||
@Emit("toggle-selection")
|
||||
emitToggleSelection(did: string) {
|
||||
return did;
|
||||
}
|
||||
|
||||
@Emit("show-identicon")
|
||||
emitShowIdenticon(contact: Contact) {
|
||||
return contact;
|
||||
}
|
||||
|
||||
@Emit("show-gifted-dialog")
|
||||
emitShowGiftedDialog(fromDid: string, toDid: string) {
|
||||
return { fromDid, toDid };
|
||||
}
|
||||
|
||||
@Emit("open-offer-dialog")
|
||||
emitOpenOfferDialog(did: string, name: string | undefined) {
|
||||
return { did, name };
|
||||
}
|
||||
|
||||
/**
|
||||
* Format contact name with non-breaking spaces
|
||||
*/
|
||||
|
||||
@@ -55,7 +55,10 @@
|
||||
aria-label="Delete profile image"
|
||||
@click="deleteImage"
|
||||
>
|
||||
<font-awesome icon="trash-can" aria-hidden="true" />
|
||||
<font-awesome
|
||||
icon="trash-can"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
<div v-else class="text-center">
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="flex-1 flex items-center justify-center p-2">
|
||||
<div class="w-full h-full flex items-center justify-center">
|
||||
<img
|
||||
:src="imageUrl"
|
||||
:src="transformedImageUrl"
|
||||
class="max-h-[calc(100vh-5rem)] w-full h-full object-contain"
|
||||
alt="expanded shared content"
|
||||
@click="close"
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
:contact="contact"
|
||||
:icon-size="512"
|
||||
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
||||
@click="emitClose"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop, Emit } from "vue-facing-decorator";
|
||||
import { Component, Vue, Prop } from "vue-facing-decorator";
|
||||
import EntityIcon from "./EntityIcon.vue";
|
||||
import { Contact } from "../db/tables/contacts";
|
||||
|
||||
@@ -34,11 +34,5 @@ import { Contact } from "../db/tables/contacts";
|
||||
})
|
||||
export default class LargeIdenticonModal extends Vue {
|
||||
@Prop({ required: true }) contact!: Contact | undefined;
|
||||
|
||||
// Emit methods using @Emit decorator
|
||||
@Emit("close")
|
||||
emitClose() {
|
||||
// No parameters needed
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -162,6 +162,8 @@
|
||||
/* TODO: Human Testing Required - PlatformServiceMixin Migration */
|
||||
// Priority: High | Migrated: 2025-07-06 | Author: Matthew Raymer
|
||||
//
|
||||
// TESTING NEEDED: Component migrated from legacy logConsoleAndDb to PlatformServiceMixin
|
||||
// but requires human validation due to meeting component accessibility limitations.
|
||||
//
|
||||
// Test Scenarios Required:
|
||||
// 1. Load members list with valid meeting password
|
||||
@@ -172,10 +174,11 @@
|
||||
// 6. Cross-platform testing: web, mobile, desktop
|
||||
//
|
||||
// Reference: docs/migration-testing/migration-checklist-MembersList.md
|
||||
// Migration Details: Replaced 3 logConsoleAndDb() calls with this.$logAndConsole()
|
||||
// Validation: Passes lint checks and TypeScript compilation
|
||||
// Navigation: Contacts → Chair Icon → Start/Join Meeting → Members List
|
||||
|
||||
import { Component, Vue, Prop, Emit } from "vue-facing-decorator";
|
||||
import { Component, Vue, Prop } from "vue-facing-decorator";
|
||||
|
||||
import {
|
||||
errorStringForLog,
|
||||
@@ -219,12 +222,6 @@ export default class MembersList extends Vue {
|
||||
@Prop({ required: true }) password!: string;
|
||||
@Prop({ default: false }) showOrganizerTools!: boolean;
|
||||
|
||||
// Emit methods using @Emit decorator
|
||||
@Emit("error")
|
||||
emitError(message: string) {
|
||||
return message;
|
||||
}
|
||||
|
||||
decryptedMembers: DecryptedMember[] = [];
|
||||
firstName = "";
|
||||
isLoading = true;
|
||||
@@ -265,7 +262,10 @@ export default class MembersList extends Vue {
|
||||
"Error fetching members: " + errorStringForLog(error),
|
||||
true,
|
||||
);
|
||||
this.emitError(serverMessageForUser(error) || "Failed to fetch members.");
|
||||
this.$emit(
|
||||
"error",
|
||||
serverMessageForUser(error) || "Failed to fetch members.",
|
||||
);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
@@ -478,7 +478,8 @@ export default class MembersList extends Vue {
|
||||
"Error toggling admission: " + errorStringForLog(error),
|
||||
true,
|
||||
);
|
||||
this.emitError(
|
||||
this.$emit(
|
||||
"error",
|
||||
serverMessageForUser(error) ||
|
||||
"Failed to update member admission status.",
|
||||
);
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
>
|
||||
Let's go!
|
||||
<br />
|
||||
See & record things you've received.
|
||||
See & record gratitude.
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
out of <b>{{ imageLimits?.maxImagesPerWeek ?? "?" }}</b> for this week.
|
||||
Your image counter resets at
|
||||
<b class="whitespace-nowrap">{{
|
||||
readableDate(imageLimits?.nextWeekBeginDateTime)
|
||||
readableDate(imageLimits?.nextWeekBeginDateTime || "")
|
||||
}}</b>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -273,8 +273,8 @@ export async function logToDb(
|
||||
// Prevent infinite logging loops - if we're already trying to log to database,
|
||||
// just log to console instead to break circular dependency
|
||||
if (isLoggingToDatabase) {
|
||||
// Use logger.debug for controlled debug output instead of direct console.log
|
||||
logger.debug(`[DB-PREVENTED-${level.toUpperCase()}] ${message}`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[DB-PREVENTED-${level.toUpperCase()}] ${message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,7 @@ export type ContactMethod = {
|
||||
|
||||
export type Contact = {
|
||||
//
|
||||
// When adding a property:
|
||||
// - Consider whether it should be added when exporting & sharing contacts, eg. DataExportSection
|
||||
// - If it's a boolean, it should be converted from a 0/1 integer in PlatformServiceMixin._mapColumnsToValues
|
||||
// When adding a property, consider whether it should be added when exporting & sharing contacts, eg. DataExportSection
|
||||
|
||||
did: string;
|
||||
contactMethods?: Array<ContactMethod>;
|
||||
|
||||
@@ -34,7 +34,6 @@ import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
|
||||
import { IIdentifier } from "@veramo/core";
|
||||
import { DEFAULT_ROOT_DERIVATION_PATH } from "./crypto";
|
||||
|
||||
// Consolidate this with src/utils/PlatformServiceMixin._parseJsonField
|
||||
function parseJsonField<T>(value: unknown, defaultValue: T): T {
|
||||
if (typeof value === "string") {
|
||||
try {
|
||||
@@ -502,7 +501,15 @@ export function findAllVisibleToDids(
|
||||
import * as R from 'ramda';
|
||||
//import { findAllVisibleToDids } from './src/libs/util'; // doesn't work because other dependencies fail so gotta copy-and-paste function
|
||||
|
||||
// Test/debug console.log statements removed - use logger.debug() if needed
|
||||
console.log(R.equals(findAllVisibleToDids(null), {}));
|
||||
console.log(R.equals(findAllVisibleToDids(9), {}));
|
||||
console.log(R.equals(findAllVisibleToDids([]), {}));
|
||||
console.log(R.equals(findAllVisibleToDids({}), {}));
|
||||
console.log(R.equals(findAllVisibleToDids({ issuer: "abc" }), {}));
|
||||
console.log(R.equals(findAllVisibleToDids({ issuerVisibleToDids: ["abc"] }), { ".issuer": ["abc"] }));
|
||||
console.log(R.equals(findAllVisibleToDids([{ issuerVisibleToDids: ["abc"] }]), { "[0].issuer": ["abc"] }));
|
||||
console.log(R.equals(findAllVisibleToDids(["xyz", { fluff: { issuerVisibleToDids: ["abc"] } }]), { "[1].fluff.issuer": ["abc"] }));
|
||||
console.log(R.equals(findAllVisibleToDids(["xyz", { fluff: { issuerVisibleToDids: ["abc"] }, stuff: [ { did: "HIDDEN", agentDidVisibleToDids: ["def", "ghi"] } ] }]), { "[1].fluff.issuer": ["abc"], "[1].stuff[0].agentDid": ["def", "ghi"] }));
|
||||
|
||||
*
|
||||
**/
|
||||
@@ -966,28 +973,28 @@ export async function importFromMnemonic(
|
||||
if (isTestUser0) {
|
||||
// Set up Test User #0 specific settings with enhanced error handling
|
||||
const platformService = await getPlatformService();
|
||||
|
||||
|
||||
try {
|
||||
// First, ensure the DID-specific settings record exists
|
||||
await platformService.insertDidSpecificSettings(newId.did);
|
||||
|
||||
|
||||
// Then update with Test User #0 specific settings
|
||||
await platformService.updateDidSpecificSettings(newId.did, {
|
||||
firstName: "User Zero",
|
||||
isRegistered: true,
|
||||
});
|
||||
|
||||
|
||||
// Verify the settings were saved correctly
|
||||
const verificationResult = await platformService.dbQuery(
|
||||
"SELECT firstName, isRegistered FROM settings WHERE accountDid = ?",
|
||||
[newId.did],
|
||||
);
|
||||
|
||||
|
||||
if (verificationResult?.values?.length) {
|
||||
const settings = verificationResult.values[0];
|
||||
const firstName = settings[0];
|
||||
const isRegistered = settings[1];
|
||||
|
||||
|
||||
logger.info("[importFromMnemonic] Test User #0 settings verification", {
|
||||
did: newId.did,
|
||||
firstName,
|
||||
@@ -995,50 +1002,40 @@ export async function importFromMnemonic(
|
||||
expectedFirstName: "User Zero",
|
||||
expectedIsRegistered: true,
|
||||
});
|
||||
|
||||
|
||||
// If settings weren't saved correctly, try individual updates
|
||||
if (firstName !== "User Zero" || isRegistered !== 1) {
|
||||
logger.warn(
|
||||
"[importFromMnemonic] Test User #0 settings not saved correctly, retrying with individual updates",
|
||||
);
|
||||
|
||||
logger.warn("[importFromMnemonic] Test User #0 settings not saved correctly, retrying with individual updates");
|
||||
|
||||
await platformService.dbExec(
|
||||
"UPDATE settings SET firstName = ? WHERE accountDid = ?",
|
||||
["User Zero", newId.did],
|
||||
);
|
||||
|
||||
|
||||
await platformService.dbExec(
|
||||
"UPDATE settings SET isRegistered = ? WHERE accountDid = ?",
|
||||
[1, newId.did],
|
||||
);
|
||||
|
||||
|
||||
// Verify again
|
||||
const retryResult = await platformService.dbQuery(
|
||||
"SELECT firstName, isRegistered FROM settings WHERE accountDid = ?",
|
||||
[newId.did],
|
||||
);
|
||||
|
||||
|
||||
if (retryResult?.values?.length) {
|
||||
const retrySettings = retryResult.values[0];
|
||||
logger.info(
|
||||
"[importFromMnemonic] Test User #0 settings after retry",
|
||||
{
|
||||
firstName: retrySettings[0],
|
||||
isRegistered: retrySettings[1],
|
||||
},
|
||||
);
|
||||
logger.info("[importFromMnemonic] Test User #0 settings after retry", {
|
||||
firstName: retrySettings[0],
|
||||
isRegistered: retrySettings[1],
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.error(
|
||||
"[importFromMnemonic] Failed to verify Test User #0 settings - no record found",
|
||||
);
|
||||
logger.error("[importFromMnemonic] Failed to verify Test User #0 settings - no record found");
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
"[importFromMnemonic] Error setting up Test User #0 settings:",
|
||||
error,
|
||||
);
|
||||
logger.error("[importFromMnemonic] Error setting up Test User #0 settings:", error);
|
||||
// Don't throw - allow the import to continue even if settings fail
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,12 +250,6 @@ onerror = function (error) {
|
||||
* Auto-initialize on worker startup (removed to prevent circular dependency)
|
||||
* Initialization now happens on first database operation
|
||||
*/
|
||||
// Use logger.debug for controlled debug output instead of direct console.log
|
||||
// Note: This is a worker context, so we use a simple debug message
|
||||
if (typeof self !== "undefined" && self.name) {
|
||||
// Worker context - use simple debug output
|
||||
self.postMessage({
|
||||
type: "debug",
|
||||
message: "[SQLWorker] Worker loaded, ready to receive messages",
|
||||
});
|
||||
}
|
||||
// Use console for critical startup message to avoid circular dependency
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("[SQLWorker] Worker loaded, ready to receive messages");
|
||||
|
||||
@@ -42,8 +42,9 @@ export class PlatformServiceFactory {
|
||||
const platform = process.env.VITE_PLATFORM || "web";
|
||||
|
||||
if (!PlatformServiceFactory.creationLogged) {
|
||||
// Use logger.debug for controlled debug output instead of direct console.log
|
||||
logger.debug(
|
||||
// Use console for critical startup message to avoid circular dependency
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`[PlatformServiceFactory] Creating singleton instance for platform: ${platform}`,
|
||||
);
|
||||
PlatformServiceFactory.creationLogged = true;
|
||||
|
||||
@@ -174,7 +174,7 @@ export class DeepLinkHandler {
|
||||
const validRoute = routeSchema.parse(path) as DeepLinkRoute;
|
||||
routeName = ROUTE_MAP[validRoute].name;
|
||||
} catch (error) {
|
||||
logger.error(`[DeepLink] Invalid route path: ${path}`);
|
||||
console.error(`[DeepLink] Invalid route path: ${path}`);
|
||||
|
||||
// Redirect to error page with information about the invalid link
|
||||
await this.router.replace({
|
||||
@@ -201,7 +201,7 @@ export class DeepLinkHandler {
|
||||
validatedQuery = await schema.parseAsync(query);
|
||||
} catch (error) {
|
||||
// For parameter validation errors, provide specific error feedback
|
||||
logger.error(
|
||||
console.error(
|
||||
`[DeepLink] Invalid parameters for route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with params: ${JSON.stringify(params)} ... and query: ${JSON.stringify(query)}`,
|
||||
);
|
||||
await this.router.replace({
|
||||
@@ -226,7 +226,7 @@ export class DeepLinkHandler {
|
||||
query: validatedQuery,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
console.error(
|
||||
`[DeepLink] Error routing to route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with validated params: ${JSON.stringify(validatedParams)} ... and validated query: ${JSON.stringify(validatedQuery)}`,
|
||||
);
|
||||
// For parameter validation errors, provide specific error feedback
|
||||
@@ -260,7 +260,7 @@ export class DeepLinkHandler {
|
||||
await this.validateAndRoute(path, sanitizedParams, query);
|
||||
} catch (error) {
|
||||
const deepLinkError = error as DeepLinkError;
|
||||
logger.error(
|
||||
console.error(
|
||||
`[DeepLink] Error (${deepLinkError.code}): ${deepLinkError.details}`,
|
||||
);
|
||||
|
||||
|
||||
@@ -97,8 +97,9 @@ export class WebPlatformService implements PlatformService {
|
||||
}
|
||||
} else {
|
||||
// We're in a worker context - skip initBackend call
|
||||
// Use logger.debug for controlled debug output instead of direct console.log
|
||||
logger.debug(
|
||||
// Use console for critical startup message to avoid circular dependency
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
"[WebPlatformService] Skipping initBackend call in worker context",
|
||||
);
|
||||
}
|
||||
@@ -692,7 +693,11 @@ export class WebPlatformService implements PlatformService {
|
||||
const setClause = keys.map((key) => `${key} = ?`).join(", ");
|
||||
const sql = `UPDATE settings SET ${setClause} WHERE accountDid = ?`;
|
||||
const params = [...keys.map((key) => settings[key]), did];
|
||||
// Debug logging removed - use logger.debug() if needed
|
||||
console.log(
|
||||
"[WebPlatformService] updateDidSpecificSettings",
|
||||
sql,
|
||||
JSON.stringify(params, null, 2),
|
||||
);
|
||||
await this.dbExec(sql, params);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,92 +1,26 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>PlatformServiceMixin Test</h2>
|
||||
<div class="space-y-2">
|
||||
<button
|
||||
:class="
|
||||
activeTest === 'insert'
|
||||
? 'bg-green-500 text-white'
|
||||
: 'bg-gray-200 hover:bg-gray-300'
|
||||
"
|
||||
class="px-4 py-2 rounded mr-2 transition-colors"
|
||||
@click="testInsert"
|
||||
>
|
||||
Test Insert
|
||||
</button>
|
||||
<button
|
||||
:class="
|
||||
activeTest === 'update'
|
||||
? 'bg-green-500 text-white'
|
||||
: 'bg-gray-200 hover:bg-gray-300'
|
||||
"
|
||||
class="px-4 py-2 rounded mr-2 transition-colors"
|
||||
@click="testUpdate"
|
||||
>
|
||||
Test Update
|
||||
</button>
|
||||
<button
|
||||
:class="
|
||||
activeTest === 'searchBoxes'
|
||||
? 'bg-green-500 text-white'
|
||||
: 'bg-gray-200 hover:bg-gray-300'
|
||||
"
|
||||
class="px-4 py-2 rounded mr-2 transition-colors"
|
||||
@click="testSearchBoxesConversion"
|
||||
>
|
||||
Test SearchBoxes Conversion
|
||||
</button>
|
||||
<button
|
||||
:class="
|
||||
activeTest === 'database'
|
||||
? 'bg-green-500 text-white'
|
||||
: 'bg-gray-200 hover:bg-gray-300'
|
||||
"
|
||||
class="px-4 py-2 rounded mr-2 transition-colors"
|
||||
@click="testDatabaseStorage"
|
||||
>
|
||||
Test Database Storage Format
|
||||
</button>
|
||||
<button
|
||||
:class="
|
||||
activeTest === 'userZero'
|
||||
? 'bg-green-500 text-white'
|
||||
: primaryButtonClasses
|
||||
"
|
||||
class="transition-colors"
|
||||
@click="testUserZeroSettings"
|
||||
>
|
||||
Test User #0 Settings
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="userZeroTestResult"
|
||||
class="mt-4 p-4 border border-gray-300 rounded-md bg-gray-50"
|
||||
<button @click="testInsert">Test Insert</button>
|
||||
<button @click="testUpdate">Test Update</button>
|
||||
<button
|
||||
:class="primaryButtonClasses"
|
||||
@click="testUserZeroSettings"
|
||||
>
|
||||
<h4 class="font-semibold mb-2">User #0 Settings Test Result:</h4>
|
||||
<pre class="text-sm">{{
|
||||
JSON.stringify(userZeroTestResult, null, 2)
|
||||
}}</pre>
|
||||
</div>
|
||||
Test User #0 Settings
|
||||
</button>
|
||||
|
||||
<div v-if="result" class="mt-4">
|
||||
<div class="p-4 border border-blue-300 rounded-md bg-blue-50">
|
||||
<h4 class="font-semibold mb-2 text-blue-800">Test Results:</h4>
|
||||
<div
|
||||
class="whitespace-pre-wrap text-sm font-mono bg-white p-3 rounded border"
|
||||
>
|
||||
{{ result }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="userZeroTestResult" class="mt-4 p-4 border border-gray-300 rounded-md bg-gray-50">
|
||||
<h4 class="font-semibold mb-2">User #0 Settings Test Result:</h4>
|
||||
<pre class="text-sm">{{ JSON.stringify(userZeroTestResult, null, 2) }}</pre>
|
||||
</div>
|
||||
<pre>{{ result }}</pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
import { logger } from "@/utils/logger";
|
||||
|
||||
@Component({
|
||||
mixins: [PlatformServiceMixin],
|
||||
@@ -94,15 +28,8 @@ import { logger } from "@/utils/logger";
|
||||
export default class PlatformServiceMixinTest extends Vue {
|
||||
result: string = "";
|
||||
userZeroTestResult: any = null;
|
||||
activeTest: string = ""; // Track which test is currently active
|
||||
|
||||
// Add the missing computed property
|
||||
get primaryButtonClasses() {
|
||||
return "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded";
|
||||
}
|
||||
|
||||
testInsert() {
|
||||
this.activeTest = "insert";
|
||||
const contact = {
|
||||
name: "Alice",
|
||||
age: 30,
|
||||
@@ -114,7 +41,6 @@ export default class PlatformServiceMixinTest extends Vue {
|
||||
}
|
||||
|
||||
testUpdate() {
|
||||
this.activeTest = "update";
|
||||
const changes = { name: "Bob", isActive: false };
|
||||
const { sql, params } = this.$generateUpdateStatement(
|
||||
changes,
|
||||
@@ -125,138 +51,20 @@ export default class PlatformServiceMixinTest extends Vue {
|
||||
this.result = `SQL: ${sql}\nParams: ${JSON.stringify(params)}`;
|
||||
}
|
||||
|
||||
testSearchBoxesConversion() {
|
||||
this.activeTest = "searchBoxes";
|
||||
// Test the _convertSettingsForStorage helper method
|
||||
const testSettings = {
|
||||
firstName: "John",
|
||||
searchBoxes: [
|
||||
{
|
||||
name: "Test Area",
|
||||
bbox: {
|
||||
eastLong: 1.0,
|
||||
maxLat: 1.0,
|
||||
minLat: 0.0,
|
||||
westLong: 0.0,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const converted = (this as any)._convertSettingsForStorage(testSettings);
|
||||
|
||||
this.result = `# 🔄 SearchBoxes Conversion Test (Helper Method)
|
||||
|
||||
## 📥 Input Settings
|
||||
\`\`\`json
|
||||
{
|
||||
"firstName": "John",
|
||||
"searchBoxes": ${JSON.stringify(testSettings.searchBoxes, null, 2)}
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
## 🔧 After _convertSettingsForStorage()
|
||||
\`\`\`json
|
||||
{
|
||||
"firstName": "John",
|
||||
"searchBoxes": "${converted.searchBoxes}"
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
## ✅ Conversion Results
|
||||
- **Original Type**: \`${typeof testSettings.searchBoxes}\`
|
||||
- **Converted Type**: \`${typeof converted.searchBoxes}\`
|
||||
- **Conversion**: Array → JSON String ✅
|
||||
|
||||
## 📝 Note
|
||||
This tests the helper method only - no database interaction`;
|
||||
}
|
||||
|
||||
async testDatabaseStorage() {
|
||||
this.activeTest = "database";
|
||||
try {
|
||||
this.result = "🔄 Testing database storage format...";
|
||||
|
||||
// Create test settings with searchBoxes array
|
||||
const testSettings = {
|
||||
searchBoxes: [
|
||||
{
|
||||
name: "Test Area",
|
||||
bbox: {
|
||||
eastLong: 1.0,
|
||||
maxLat: 1.0,
|
||||
minLat: 0.0,
|
||||
westLong: 0.0,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Save to database using our fixed method
|
||||
const success = await this.$saveSettings(testSettings);
|
||||
|
||||
if (success) {
|
||||
// Now query the raw database to see how it's actually stored
|
||||
const rawResult = await this.$dbQuery(
|
||||
"SELECT searchBoxes FROM settings WHERE id = ?",
|
||||
[MASTER_SETTINGS_KEY],
|
||||
);
|
||||
|
||||
if (rawResult?.values?.length) {
|
||||
const rawSearchBoxes = rawResult.values[0][0]; // First column of first row
|
||||
|
||||
this.result = `# 🔧 Database Storage Format Test (Full Database Cycle)
|
||||
|
||||
## 📥 Input (JavaScript Array)
|
||||
\`\`\`json
|
||||
${JSON.stringify(testSettings.searchBoxes, null, 2)}
|
||||
\`\`\`
|
||||
|
||||
## 💾 Database Storage (JSON String)
|
||||
\`\`\`sql
|
||||
"${rawSearchBoxes}"
|
||||
\`\`\`
|
||||
|
||||
## ✅ Verification
|
||||
- **Type**: \`${typeof rawSearchBoxes}\`
|
||||
- **Is JSON String**: \`${typeof rawSearchBoxes === "string" && rawSearchBoxes.startsWith("[")}\`
|
||||
- **Conversion Working**: ✅ **YES** - Array converted to JSON string for database storage
|
||||
|
||||
## 🔄 Process Flow
|
||||
1. **Input**: JavaScript array with bounding box coordinates
|
||||
2. **Conversion**: \`_convertSettingsForStorage()\` converts array to JSON string
|
||||
3. **Storage**: JSON string saved to database using \`$saveSettings()\`
|
||||
4. **Retrieval**: JSON string parsed back to array for application use
|
||||
|
||||
## 📝 Note
|
||||
This tests the complete save → retrieve cycle with actual database interaction`;
|
||||
} else {
|
||||
this.result = "❌ No data found in database";
|
||||
}
|
||||
} else {
|
||||
this.result = "❌ Failed to save settings";
|
||||
}
|
||||
} catch (error) {
|
||||
this.result = `❌ Error: ${error}`;
|
||||
}
|
||||
}
|
||||
|
||||
async testUserZeroSettings() {
|
||||
this.activeTest = "userZero";
|
||||
try {
|
||||
// User #0's DID
|
||||
const userZeroDid = "did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F";
|
||||
|
||||
|
||||
this.result = "Testing User #0 settings...";
|
||||
|
||||
|
||||
// Test the debug methods
|
||||
await this.$debugMergedSettings(userZeroDid);
|
||||
|
||||
|
||||
// Get the actual settings
|
||||
const didSettings = await this.$debugDidSettings(userZeroDid);
|
||||
const accountSettings = await this.$accountSettings(userZeroDid);
|
||||
|
||||
|
||||
this.userZeroTestResult = {
|
||||
didSettings,
|
||||
accountSettings,
|
||||
@@ -264,11 +72,11 @@ This tests the complete save → retrieve cycle with actual database interaction
|
||||
firstName: accountSettings.firstName,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
|
||||
this.result = `User #0 settings test completed. isRegistered: ${accountSettings.isRegistered}`;
|
||||
} catch (error) {
|
||||
this.result = `Error testing User #0 settings: ${error}`;
|
||||
logger.error("Error testing User #0 settings:", error);
|
||||
console.error("Error testing User #0 settings:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
83
src/types/global.d.ts
vendored
Normal file
83
src/types/global.d.ts
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
import type { QueryExecResult, SqlValue } from "./database";
|
||||
|
||||
declare module '@jlongster/sql.js' {
|
||||
interface SQL {
|
||||
Database: new (path: string, options?: { filename: boolean }) => Database;
|
||||
FS: {
|
||||
mkdir: (path: string) => void;
|
||||
mount: (fs: any, options: any, path: string) => void;
|
||||
open: (path: string, flags: string) => any;
|
||||
close: (stream: any) => void;
|
||||
};
|
||||
register_for_idb: (fs: any) => void;
|
||||
}
|
||||
|
||||
interface Database {
|
||||
exec: (sql: string, params?: unknown[]) => Promise<QueryExecResult[]>;
|
||||
run: (sql: string, params?: unknown[]) => Promise<{ changes: number; lastId?: number }>;
|
||||
get: (sql: string, params?: unknown[]) => Promise<SqlValue[]>;
|
||||
all: (sql: string, params?: unknown[]) => Promise<SqlValue[][]>;
|
||||
prepare: (sql: string) => Promise<Statement>;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
interface Statement {
|
||||
run: (params?: unknown[]) => Promise<{ changes: number; lastId?: number }>;
|
||||
get: (params?: unknown[]) => Promise<SqlValue[]>;
|
||||
all: (params?: unknown[]) => Promise<SqlValue[][]>;
|
||||
finalize: () => void;
|
||||
}
|
||||
|
||||
const initSqlJs: (options?: {
|
||||
locateFile?: (file: string) => string;
|
||||
}) => Promise<SQL>;
|
||||
|
||||
export default initSqlJs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Electron API types for the main world context bridge.
|
||||
*
|
||||
* These types define the secure IPC APIs exposed by the preload script
|
||||
* to the renderer process for native Electron functionality.
|
||||
*/
|
||||
interface ElectronAPI {
|
||||
/**
|
||||
* Export data to the user's Downloads folder.
|
||||
*
|
||||
* @param fileName - The name of the file to save (e.g., 'backup-2025-07-06.json')
|
||||
* @param data - The content to write to the file (string)
|
||||
* @returns Promise with success status, file path, or error message
|
||||
*/
|
||||
exportData: (fileName: string, data: string) => Promise<{
|
||||
success: boolean;
|
||||
path?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Global window interface extension for Electron APIs.
|
||||
*
|
||||
* This makes the electronAPI available on the window object
|
||||
* in TypeScript without type errors.
|
||||
*/
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: ElectronAPI;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue instance interface extension for global properties.
|
||||
*
|
||||
* This makes global properties available on Vue instances
|
||||
* in TypeScript without type errors.
|
||||
*/
|
||||
declare module 'vue' {
|
||||
interface ComponentCustomProperties {
|
||||
$notify: (notification: any, timeout?: number) => void;
|
||||
$route: import('vue-router').RouteLocationNormalizedLoaded;
|
||||
$router: import('vue-router').Router;
|
||||
}
|
||||
}
|
||||
67
src/types/modules.d.ts
vendored
Normal file
67
src/types/modules.d.ts
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { QueryExecResult, SqlValue } from "./database";
|
||||
|
||||
declare module '@jlongster/sql.js' {
|
||||
interface SQL {
|
||||
Database: new (path: string, options?: { filename: boolean }) => Database;
|
||||
FS: {
|
||||
mkdir: (path: string) => void;
|
||||
mount: (fs: any, options: any, path: string) => void;
|
||||
open: (path: string, flags: string) => any;
|
||||
close: (stream: any) => void;
|
||||
};
|
||||
register_for_idb: (fs: any) => void;
|
||||
}
|
||||
|
||||
interface Database {
|
||||
exec: (sql: string, params?: unknown[]) => Promise<QueryExecResult[]>;
|
||||
run: (sql: string, params?: unknown[]) => Promise<{ changes: number; lastId?: number }>;
|
||||
get: (sql: string, params?: unknown[]) => Promise<SqlValue[]>;
|
||||
all: (sql: string, params?: unknown[]) => Promise<SqlValue[][]>;
|
||||
prepare: (sql: string) => Promise<Statement>;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
interface Statement {
|
||||
run: (params?: unknown[]) => Promise<{ changes: number; lastId?: number }>;
|
||||
get: (params?: unknown[]) => Promise<SqlValue[]>;
|
||||
all: (params?: unknown[]) => Promise<SqlValue[][]>;
|
||||
finalize: () => void;
|
||||
}
|
||||
|
||||
const initSqlJs: (options?: {
|
||||
locateFile?: (file: string) => string;
|
||||
}) => Promise<SQL>;
|
||||
|
||||
export default initSqlJs;
|
||||
}
|
||||
|
||||
declare module 'absurd-sql' {
|
||||
import type { SQL } from '@jlongster/sql.js';
|
||||
export class SQLiteFS {
|
||||
constructor(fs: any, backend: any);
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'absurd-sql/dist/indexeddb-backend' {
|
||||
export default class IndexedDBBackend {
|
||||
constructor();
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'absurd-sql/dist/indexeddb-main-thread' {
|
||||
import type { QueryExecResult } from './database';
|
||||
export interface SQLiteOptions {
|
||||
filename?: string;
|
||||
autoLoad?: boolean;
|
||||
debug?: boolean;
|
||||
}
|
||||
|
||||
export interface SQLiteDatabase {
|
||||
exec: (sql: string, params?: unknown[]) => Promise<QueryExecResult[]>;
|
||||
close: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function initSqlJs(options?: any): Promise<any>;
|
||||
export function createDatabase(options?: SQLiteOptions): Promise<SQLiteDatabase>;
|
||||
export function openDatabase(options?: SQLiteOptions): Promise<SQLiteDatabase>;
|
||||
}
|
||||
@@ -44,11 +44,7 @@ import type {
|
||||
PlatformService,
|
||||
PlatformCapabilities,
|
||||
} from "@/services/PlatformService";
|
||||
import {
|
||||
MASTER_SETTINGS_KEY,
|
||||
type Settings,
|
||||
type SettingsWithJsonStrings,
|
||||
} from "@/db/tables/settings";
|
||||
import { MASTER_SETTINGS_KEY, type Settings } from "@/db/tables/settings";
|
||||
import { logger } from "@/utils/logger";
|
||||
import { Contact } from "@/db/tables/contacts";
|
||||
import { Account } from "@/db/tables/accounts";
|
||||
@@ -63,14 +59,14 @@ import {
|
||||
// TYPESCRIPT INTERFACES
|
||||
// =================================================
|
||||
|
||||
// /**
|
||||
// * Cache entry interface for storing data with TTL
|
||||
// */
|
||||
// interface CacheEntry<T> {
|
||||
// data: T;
|
||||
// timestamp: number;
|
||||
// ttl: number; // milliseconds
|
||||
// }
|
||||
/**
|
||||
* Cache entry interface for storing data with TTL
|
||||
*/
|
||||
interface CacheEntry<T> {
|
||||
data: T;
|
||||
timestamp: number;
|
||||
ttl: number; // milliseconds
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue component interface that uses the PlatformServiceMixin
|
||||
@@ -83,21 +79,21 @@ interface VueComponentWithMixin {
|
||||
platformService(): PlatformService;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Global cache store for mixin instances
|
||||
// * Uses WeakMap to avoid memory leaks when components are destroyed
|
||||
// */
|
||||
// const componentCaches = new WeakMap<
|
||||
// VueComponentWithMixin,
|
||||
// Map<string, CacheEntry<unknown>>
|
||||
// >();
|
||||
//
|
||||
// /**
|
||||
// * Cache configuration constants
|
||||
// */
|
||||
// const CACHE_DEFAULTS = {
|
||||
// default: 15000, // 15 seconds default TTL
|
||||
// } as const;
|
||||
/**
|
||||
* Global cache store for mixin instances
|
||||
* Uses WeakMap to avoid memory leaks when components are destroyed
|
||||
*/
|
||||
const componentCaches = new WeakMap<
|
||||
VueComponentWithMixin,
|
||||
Map<string, CacheEntry<unknown>>
|
||||
>();
|
||||
|
||||
/**
|
||||
* Cache configuration constants
|
||||
*/
|
||||
const CACHE_DEFAULTS = {
|
||||
default: 15000, // 15 seconds default TTL
|
||||
} as const;
|
||||
|
||||
const _memoryLogs: string[] = [];
|
||||
|
||||
@@ -182,8 +178,8 @@ export const PlatformServiceMixin = {
|
||||
logger.debug(
|
||||
`[PlatformServiceMixin] ActiveDid changed from ${oldDid} to ${newDid}`,
|
||||
);
|
||||
// // Clear caches that might be affected by the change
|
||||
// (this as any).$clearAllCaches();
|
||||
// Clear caches that might be affected by the change
|
||||
(this as any).$clearAllCaches();
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
@@ -207,8 +203,8 @@ export const PlatformServiceMixin = {
|
||||
logger.debug(
|
||||
`[PlatformServiceMixin] ActiveDid updated from ${oldDid} to ${newDid}`,
|
||||
);
|
||||
// // Clear caches that might be affected by the change
|
||||
// this.$clearAllCaches();
|
||||
// Clear caches that might be affected by the change
|
||||
this.$clearAllCaches();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -224,20 +220,13 @@ export const PlatformServiceMixin = {
|
||||
const obj: Record<string, unknown> = {};
|
||||
columns.forEach((column, index) => {
|
||||
let value = row[index];
|
||||
|
||||
|
||||
// Convert SQLite integer booleans to JavaScript booleans
|
||||
if (
|
||||
column === "isRegistered" ||
|
||||
column === "finishedOnboarding" ||
|
||||
column === "filterFeedByVisible" ||
|
||||
column === "filterFeedByNearby" ||
|
||||
column === "hideRegisterPromptOnNewContact" ||
|
||||
column === "showContactGivesInline" ||
|
||||
column === "showGeneralAdvanced" ||
|
||||
column === "showShortcutBvc" ||
|
||||
column === "warnIfProdServer" ||
|
||||
column === "warnIfTestServer"
|
||||
) {
|
||||
if (column === 'isRegistered' || column === 'finishedOnboarding' ||
|
||||
column === 'filterFeedByVisible' || column === 'filterFeedByNearby' ||
|
||||
column === 'hideRegisterPromptOnNewContact' || column === 'showContactGivesInline' ||
|
||||
column === 'showGeneralAdvanced' || column === 'showShortcutBvc' ||
|
||||
column === 'warnIfProdServer' || column === 'warnIfTestServer') {
|
||||
if (value === 1) {
|
||||
value = true;
|
||||
} else if (value === 0) {
|
||||
@@ -245,7 +234,7 @@ export const PlatformServiceMixin = {
|
||||
}
|
||||
// Keep null values as null
|
||||
}
|
||||
|
||||
|
||||
obj[column] = value;
|
||||
});
|
||||
return obj;
|
||||
@@ -255,8 +244,6 @@ export const PlatformServiceMixin = {
|
||||
/**
|
||||
* Self-contained implementation of parseJsonField
|
||||
* Safely parses JSON strings with fallback to default value
|
||||
*
|
||||
* Consolidate this with src/libs/util.ts parseJsonField
|
||||
*/
|
||||
_parseJsonField<T>(value: unknown, defaultValue: T): T {
|
||||
if (typeof value === "string") {
|
||||
@@ -269,92 +256,71 @@ export const PlatformServiceMixin = {
|
||||
return (value as T) || defaultValue;
|
||||
},
|
||||
|
||||
// =================================================
|
||||
// CACHING UTILITY METHODS
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Convert Settings object to SettingsWithJsonStrings for database storage
|
||||
* Handles conversion of complex objects like searchBoxes to JSON strings
|
||||
* @param settings Settings object to convert
|
||||
* @returns SettingsWithJsonStrings object ready for database storage
|
||||
* Get or initialize cache for this component instance
|
||||
*/
|
||||
_convertSettingsForStorage(
|
||||
settings: Partial<Settings>,
|
||||
): Partial<SettingsWithJsonStrings> {
|
||||
const converted = { ...settings } as Partial<SettingsWithJsonStrings>;
|
||||
|
||||
// Convert searchBoxes array to JSON string if present
|
||||
if (settings.searchBoxes !== undefined) {
|
||||
(converted as any).searchBoxes = Array.isArray(settings.searchBoxes)
|
||||
? JSON.stringify(settings.searchBoxes)
|
||||
: String(settings.searchBoxes);
|
||||
_getCache(): Map<string, CacheEntry<unknown>> {
|
||||
let cache = componentCaches.get(this as unknown as VueComponentWithMixin);
|
||||
if (!cache) {
|
||||
cache = new Map();
|
||||
componentCaches.set(this as unknown as VueComponentWithMixin, cache);
|
||||
}
|
||||
|
||||
return converted;
|
||||
return cache;
|
||||
},
|
||||
|
||||
// // =================================================
|
||||
// // CACHING UTILITY METHODS
|
||||
// // =================================================
|
||||
/**
|
||||
* Check if cache entry is valid (not expired)
|
||||
*/
|
||||
_isCacheValid(entry: CacheEntry<unknown>): boolean {
|
||||
return Date.now() - entry.timestamp < entry.ttl;
|
||||
},
|
||||
|
||||
// /**
|
||||
// * Get or initialize cache for this component instance
|
||||
// */
|
||||
// _getCache(): Map<string, CacheEntry<unknown>> {
|
||||
// let cache = componentCaches.get(this as unknown as VueComponentWithMixin);
|
||||
// if (!cache) {
|
||||
// cache = new Map();
|
||||
// componentCaches.set(this as unknown as VueComponentWithMixin, cache);
|
||||
// }
|
||||
// return cache;
|
||||
// },
|
||||
/**
|
||||
* Get data from cache if valid, otherwise return null
|
||||
*/
|
||||
_getCached<T>(key: string): T | null {
|
||||
const cache = this._getCache();
|
||||
const entry = cache.get(key);
|
||||
if (entry && this._isCacheValid(entry)) {
|
||||
return entry.data as T;
|
||||
}
|
||||
cache.delete(key); // Clean up expired entries
|
||||
return null;
|
||||
},
|
||||
|
||||
// /**
|
||||
// * Check if cache entry is valid (not expired)
|
||||
// */
|
||||
// _isCacheValid(entry: CacheEntry<unknown>): boolean {
|
||||
// return Date.now() - entry.timestamp < entry.ttl;
|
||||
// },
|
||||
/**
|
||||
* Store data in cache with TTL
|
||||
*/
|
||||
_setCached<T>(key: string, data: T, ttl?: number): T {
|
||||
const cache = this._getCache();
|
||||
const actualTtl = ttl || CACHE_DEFAULTS.default;
|
||||
cache.set(key, {
|
||||
data,
|
||||
timestamp: Date.now(),
|
||||
ttl: actualTtl,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
// /**
|
||||
// * Get data from cache if valid, otherwise return null
|
||||
// */
|
||||
// _getCached<T>(key: string): T | null {
|
||||
// const cache = this._getCache();
|
||||
// const entry = cache.get(key);
|
||||
// if (entry && this._isCacheValid(entry)) {
|
||||
// return entry.data as T;
|
||||
// }
|
||||
// cache.delete(key); // Clean up expired entries
|
||||
// return null;
|
||||
// },
|
||||
/**
|
||||
* Invalidate specific cache entry
|
||||
*/
|
||||
_invalidateCache(key: string): void {
|
||||
const cache = this._getCache();
|
||||
cache.delete(key);
|
||||
},
|
||||
|
||||
// /**
|
||||
// * Store data in cache with TTL
|
||||
// */
|
||||
// _setCached<T>(key: string, data: T, ttl?: number): T {
|
||||
// const cache = this._getCache();
|
||||
// const actualTtl = ttl || CACHE_DEFAULTS.default;
|
||||
// cache.set(key, {
|
||||
// data,
|
||||
// timestamp: Date.now(),
|
||||
// ttl: actualTtl,
|
||||
// });
|
||||
// return data;
|
||||
// },
|
||||
|
||||
// /**
|
||||
// * Invalidate specific cache entry
|
||||
// */
|
||||
// _invalidateCache(key: string): void {
|
||||
// const cache = this._getCache();
|
||||
// cache.delete(key);
|
||||
// },
|
||||
|
||||
// /**
|
||||
// * Clear all cache entries for this component
|
||||
// */
|
||||
// _clearCache(): void {
|
||||
// const cache = this._getCache();
|
||||
// cache.clear();
|
||||
// },
|
||||
/**
|
||||
* Clear all cache entries for this component
|
||||
*/
|
||||
_clearCache(): void {
|
||||
const cache = this._getCache();
|
||||
cache.clear();
|
||||
},
|
||||
|
||||
// =================================================
|
||||
// ENHANCED DATABASE METHODS (with error handling)
|
||||
@@ -757,7 +723,10 @@ export const PlatformServiceMixin = {
|
||||
// Merge with any provided defaults (these take highest precedence)
|
||||
const finalSettings = { ...mergedSettings, ...defaults };
|
||||
|
||||
// Debug logging removed - use logger.debug() if needed
|
||||
console.log(
|
||||
"[PlatformServiceMixin] $accountSettings",
|
||||
JSON.stringify(finalSettings, null, 2),
|
||||
);
|
||||
return finalSettings;
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
@@ -777,9 +746,6 @@ export const PlatformServiceMixin = {
|
||||
/**
|
||||
* Save default settings - $saveSettings()
|
||||
* Ultra-concise shortcut for updateDefaultSettings
|
||||
*
|
||||
* ✅ KEEP: This method will be the primary settings save method after consolidation
|
||||
*
|
||||
* @param changes Settings changes to save
|
||||
* @returns Promise<boolean> Success status
|
||||
*/
|
||||
@@ -794,13 +760,10 @@ export const PlatformServiceMixin = {
|
||||
|
||||
if (Object.keys(safeChanges).length === 0) return true;
|
||||
|
||||
// Convert settings for database storage (handles searchBoxes conversion)
|
||||
const convertedChanges = this._convertSettingsForStorage(safeChanges);
|
||||
|
||||
const setParts: string[] = [];
|
||||
const params: unknown[] = [];
|
||||
|
||||
Object.entries(convertedChanges).forEach(([key, value]) => {
|
||||
Object.entries(safeChanges).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
setParts.push(`${key} = ?`);
|
||||
params.push(value);
|
||||
@@ -847,13 +810,10 @@ export const PlatformServiceMixin = {
|
||||
|
||||
if (Object.keys(safeChanges).length === 0) return true;
|
||||
|
||||
// Convert settings for database storage (handles searchBoxes conversion)
|
||||
const convertedChanges = this._convertSettingsForStorage(safeChanges);
|
||||
|
||||
const setParts: string[] = [];
|
||||
const params: unknown[] = [];
|
||||
|
||||
Object.entries(convertedChanges).forEach(([key, value]) => {
|
||||
Object.entries(safeChanges).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
setParts.push(`${key} = ?`);
|
||||
params.push(value);
|
||||
@@ -912,13 +872,13 @@ export const PlatformServiceMixin = {
|
||||
return await this.$contacts();
|
||||
},
|
||||
|
||||
// /**
|
||||
// * Clear all caches for this component - $clearAllCaches()
|
||||
// * Useful for manual cache management
|
||||
// */
|
||||
// $clearAllCaches(): void {
|
||||
// this._clearCache();
|
||||
// },
|
||||
/**
|
||||
* Clear all caches for this component - $clearAllCaches()
|
||||
* Useful for manual cache management
|
||||
*/
|
||||
$clearAllCaches(): void {
|
||||
this._clearCache();
|
||||
},
|
||||
|
||||
// =================================================
|
||||
// HIGH-LEVEL ENTITY OPERATIONS (eliminate verbose SQL patterns)
|
||||
@@ -1224,17 +1184,6 @@ export const PlatformServiceMixin = {
|
||||
* @param did Optional DID for user-specific settings
|
||||
* @returns Promise<boolean> Success status
|
||||
*/
|
||||
/**
|
||||
* Update settings - $updateSettings()
|
||||
* Ultra-concise shortcut for updating settings (default or user-specific)
|
||||
*
|
||||
* ⚠️ DEPRECATED: This method will be removed in favor of $saveSettings()
|
||||
* Use $saveSettings(changes, did?) instead for better consistency
|
||||
*
|
||||
* @param changes Settings changes to save
|
||||
* @param did Optional DID for user-specific settings
|
||||
* @returns Promise<boolean> Success status
|
||||
*/
|
||||
async $updateSettings(
|
||||
changes: Partial<Settings>,
|
||||
did?: string,
|
||||
@@ -1452,9 +1401,7 @@ export const PlatformServiceMixin = {
|
||||
);
|
||||
|
||||
if (!result?.values?.length) {
|
||||
logger.warn(
|
||||
`[PlatformServiceMixin] No settings found for DID: ${did}`,
|
||||
);
|
||||
logger.warn(`[PlatformServiceMixin] No settings found for DID: ${did}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1464,9 +1411,7 @@ export const PlatformServiceMixin = {
|
||||
);
|
||||
|
||||
if (!mappedResults.length) {
|
||||
logger.warn(
|
||||
`[PlatformServiceMixin] Failed to map settings for DID: ${did}`,
|
||||
);
|
||||
logger.warn(`[PlatformServiceMixin] Failed to map settings for DID: ${did}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1481,10 +1426,7 @@ export const PlatformServiceMixin = {
|
||||
|
||||
return settings;
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`[PlatformServiceMixin] Error debugging settings for DID ${did}:`,
|
||||
error,
|
||||
);
|
||||
logger.error(`[PlatformServiceMixin] Error debugging settings for DID ${did}:`, error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
@@ -1498,24 +1440,14 @@ export const PlatformServiceMixin = {
|
||||
async $debugMergedSettings(did: string): Promise<void> {
|
||||
try {
|
||||
// Get default settings
|
||||
const defaultSettings = await this.$getSettings(
|
||||
MASTER_SETTINGS_KEY,
|
||||
{},
|
||||
);
|
||||
logger.info(
|
||||
`[PlatformServiceMixin] Default settings:`,
|
||||
defaultSettings,
|
||||
);
|
||||
const defaultSettings = await this.$getSettings(MASTER_SETTINGS_KEY, {});
|
||||
logger.info(`[PlatformServiceMixin] Default settings:`, defaultSettings);
|
||||
|
||||
// Get DID-specific settings
|
||||
const didSettings = await this.$debugDidSettings(did);
|
||||
|
||||
// Get merged settings
|
||||
const mergedSettings = await this.$getMergedSettings(
|
||||
MASTER_SETTINGS_KEY,
|
||||
did,
|
||||
defaultSettings || {},
|
||||
);
|
||||
const mergedSettings = await this.$getMergedSettings(MASTER_SETTINGS_KEY, did, defaultSettings || {});
|
||||
|
||||
logger.info(`[PlatformServiceMixin] Merged settings for ${did}:`, {
|
||||
defaultSettings,
|
||||
@@ -1524,10 +1456,7 @@ export const PlatformServiceMixin = {
|
||||
isRegistered: mergedSettings.isRegistered,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`[PlatformServiceMixin] Error debugging merged settings for DID ${did}:`,
|
||||
error,
|
||||
);
|
||||
logger.error(`[PlatformServiceMixin] Error debugging merged settings for DID ${did}:`, error);
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -1701,7 +1630,7 @@ declare module "@vue/runtime-core" {
|
||||
// Cache management methods
|
||||
$refreshSettings(): Promise<Settings>;
|
||||
$refreshContacts(): Promise<Contact[]>;
|
||||
// $clearAllCaches(): void;
|
||||
$clearAllCaches(): void;
|
||||
|
||||
// High-level entity operations (eliminate verbose SQL patterns)
|
||||
$mapResults<T>(
|
||||
|
||||
@@ -45,7 +45,6 @@ export function safeStringify(obj: unknown) {
|
||||
// Determine if we should suppress verbose logging (for Electron)
|
||||
const isElectron = process.env.VITE_PLATFORM === "electron";
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
const isDebugEnabled = process.env.VITE_DEBUG_LOGGING === "true";
|
||||
|
||||
// Track initialization state to prevent circular dependencies
|
||||
let isInitializing = true;
|
||||
@@ -109,8 +108,8 @@ async function logToDatabase(
|
||||
// Enhanced logger with self-contained database methods
|
||||
export const logger = {
|
||||
debug: (message: string, ...args: unknown[]) => {
|
||||
// Debug logs are very verbose - only show when explicitly enabled
|
||||
if (isDebugEnabled && !isProduction && !isElectron) {
|
||||
// Debug logs are very verbose - only show in development mode for web
|
||||
if (!isProduction && !isElectron) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(message, ...args);
|
||||
}
|
||||
|
||||
@@ -1396,6 +1396,10 @@ export default class AccountViewView extends Vue {
|
||||
if (imageResp.status === 200) {
|
||||
this.imageLimits = imageResp.data;
|
||||
} else {
|
||||
await this.$saveSettings({
|
||||
profileImageUrl: "",
|
||||
});
|
||||
this.profileImageUrl = "";
|
||||
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IMAGE_ACCESS;
|
||||
this.notify.warning(ACCOUNT_VIEW_CONSTANTS.LIMITS.CANNOT_UPLOAD_IMAGES);
|
||||
return;
|
||||
@@ -1410,14 +1414,17 @@ export default class AccountViewView extends Vue {
|
||||
if (endorserResp.status === 200) {
|
||||
this.endorserLimits = endorserResp.data;
|
||||
} else {
|
||||
await this.$saveSettings({
|
||||
profileImageUrl: "",
|
||||
});
|
||||
this.profileImageUrl = "";
|
||||
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_LIMITS_FOUND;
|
||||
this.notify.warning(ACCOUNT_VIEW_CONSTANTS.LIMITS.BAD_SERVER_RESPONSE);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
this.limitsMessage =
|
||||
ACCOUNT_VIEW_CONSTANTS.LIMITS.ERROR_RETRIEVING_LIMITS;
|
||||
logger.error("Error retrieving limits: ", error);
|
||||
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.ERROR_RETRIEVING_LIMITS;
|
||||
console.log("error: ", error);
|
||||
// this.notify.error(this.limitsMessage, TIMEOUTS.STANDARD);
|
||||
} finally {
|
||||
this.loadingLimits = false;
|
||||
@@ -1476,7 +1483,7 @@ export default class AccountViewView extends Vue {
|
||||
async deleteImage(): Promise<void> {
|
||||
try {
|
||||
// Extract the image ID from the full URL
|
||||
const imageId = this.profileImageUrl?.split("/").pop();
|
||||
const imageId = this.profileImageUrl?.split('/').pop();
|
||||
if (!imageId) {
|
||||
this.notify.error("Invalid image URL");
|
||||
return;
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
VALID_DEEP_LINK_ROUTES,
|
||||
deepLinkSchemas,
|
||||
} from "../interfaces/deepLinks";
|
||||
import { logConsoleAndDb } from "../db/databaseUtil";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
const route = useRoute();
|
||||
@@ -105,8 +106,9 @@ const reportIssue = () => {
|
||||
|
||||
// Log the error for analytics
|
||||
onMounted(() => {
|
||||
logger.error(
|
||||
logConsoleAndDb(
|
||||
`[DeepLinkError] Error page displayed for path: ${originalPath.value}, code: ${errorCode.value}, params: ${JSON.stringify(route.params)}, query: ${JSON.stringify(route.query)}`,
|
||||
true,
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -314,7 +314,6 @@ import {
|
||||
} from "@/constants/notifications";
|
||||
import * as Package from "../../package.json";
|
||||
|
||||
// consolidate this with GiveActionClaim in src/interfaces/claims.ts
|
||||
interface Claim {
|
||||
claim?: Claim; // For nested claims in Verifiable Credentials
|
||||
agent?: {
|
||||
|
||||
@@ -310,9 +310,10 @@ export default class SearchAreaView extends Vue {
|
||||
},
|
||||
};
|
||||
|
||||
const searchBoxes = JSON.stringify([newSearchBox]);
|
||||
|
||||
// Store search box configuration using platform service
|
||||
// searchBoxes will be automatically converted to JSON string by $updateSettings
|
||||
await this.$updateSettings({ searchBoxes: [newSearchBox] });
|
||||
await this.$updateSettings({ searchBoxes: searchBoxes as any });
|
||||
|
||||
this.searchBox = newSearchBox;
|
||||
this.isChoosingSearchBox = false;
|
||||
@@ -346,7 +347,7 @@ export default class SearchAreaView extends Vue {
|
||||
try {
|
||||
// Clear search box settings and disable nearby filtering
|
||||
await this.$updateSettings({
|
||||
searchBoxes: [],
|
||||
searchBoxes: "[]" as any,
|
||||
filterFeedByNearby: false,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user