fix: resolve build failures, security issues, and architectural improvements
Critical Fixes: - Remove missing sw_combine.js from prebuild script and all documentation - Remove missing test-safety-check.sh from test:all script - Add build:web:build alias to fix docker commands - Fix syntax errors in validate-critical-files.sh script Security: - Fix Electron path traversal vulnerability in export-data-to-downloads handler - Sanitize file names using basename() to prevent directory traversal - Enforce allowed file extensions (.json, .txt, .csv, .md, .log) - Add validation for empty names, path separators, and length limits Architecture Improvements: - Add queue size guard to CapacitorPlatformService (max 1000 operations) - Fail-fast when queue is full to prevent memory exhaustion - Add warning at 80% capacity - Add getQueueTelemetry() method for monitoring queue health - Track peak queue size for diagnostics - Standardize environment variable usage in PlatformServiceFactory - Prefer import.meta.env.VITE_PLATFORM (standard Vite pattern) - Maintain backward compatibility with process.env fallback Documentation: - Clarify PWA status: remove misleading VitePWA comments - Update BUILDING.md to reflect removed sw_combine.js step - Update build-arch-guard.sh to remove sw_combine.js from protected files All changes maintain backward compatibility and improve code quality.
This commit is contained in:
20
BUILDING.md
20
BUILDING.md
@@ -1724,14 +1724,12 @@ npx prettier --write ./sw_scripts/
|
||||
The `prebuild` script automatically runs before any build:
|
||||
|
||||
```json
|
||||
"prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js && node scripts/copy-wasm.js"
|
||||
"prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node scripts/copy-wasm.js"
|
||||
```
|
||||
|
||||
**What happens automatically:**
|
||||
|
||||
- **ESLint**: Checks and fixes code formatting in `src/`
|
||||
- **Script Combination**: `sw_combine.js` combines all `sw_scripts/*.js` files
|
||||
into `sw_scripts-combined.js`
|
||||
- **WASM Copy**: `copy-wasm.js` copies SQLite WASM files to `public/wasm/`
|
||||
|
||||
#### Build Process Architecture
|
||||
@@ -1739,10 +1737,10 @@ The `prebuild` script automatically runs before any build:
|
||||
**Web Build Process:**
|
||||
|
||||
```text
|
||||
1. Pre-Build: ESLint + Script Combination + WASM Copy
|
||||
1. Pre-Build: ESLint + WASM Copy
|
||||
2. Environment Setup: Load .env files, set NODE_ENV
|
||||
3. Vite Build: Bundle web assets with PWA support
|
||||
4. Service Worker: Inject combined scripts into PWA
|
||||
4. Service Worker: Inject service worker scripts into PWA
|
||||
5. Output: Production-ready files in dist/
|
||||
```
|
||||
|
||||
@@ -1770,10 +1768,8 @@ The `prebuild` script automatically runs before any build:
|
||||
|
||||
**Script Organization:**
|
||||
|
||||
- `sw_scripts/` - Individual third-party scripts
|
||||
- `sw_combine.js` - Combines scripts into single file
|
||||
- `sw_scripts-combined.js` - Combined service worker (317KB, 10K+ lines)
|
||||
- `vite.config.utils.mts` - PWA configuration using combined script
|
||||
- `sw_scripts/` - Individual third-party scripts for service worker
|
||||
- `vite.config.utils.mts` - PWA configuration
|
||||
|
||||
**PWA Integration:**
|
||||
|
||||
@@ -1781,18 +1777,16 @@ The `prebuild` script automatically runs before any build:
|
||||
// vite.config.utils.mts
|
||||
pwaConfig: {
|
||||
strategies: "injectManifest",
|
||||
filename: "sw_scripts-combined.js", // Uses our combined script
|
||||
filename: "sw_scripts-combined.js", // Service worker file
|
||||
// ... manifest configuration
|
||||
}
|
||||
```
|
||||
|
||||
**What Gets Combined:**
|
||||
**Service Worker Scripts:**
|
||||
|
||||
- `nacl.js` - NaCl cryptographic library
|
||||
- `noble-curves.js` - Elliptic curve cryptography (177KB)
|
||||
- `noble-hashes.js` - Cryptographic hash functions (91KB)
|
||||
- `safari-notifications.js` - Safari-specific notifications
|
||||
- `additional-scripts.js` - Additional service worker functionality
|
||||
|
||||
#### Process Environment Configuration
|
||||
|
||||
|
||||
@@ -105,8 +105,7 @@ Build Scripts:
|
||||
├── electron/** # Electron build files
|
||||
├── android/** # Android build configuration
|
||||
├── ios/** # iOS build configuration
|
||||
├── sw_scripts/** # Service worker scripts
|
||||
└── sw_combine.js # Service worker combination
|
||||
└── sw_scripts/** # Service worker scripts
|
||||
|
||||
Deployment:
|
||||
├── Dockerfile # Docker configuration
|
||||
|
||||
@@ -6,7 +6,7 @@ import electronIsDev from 'electron-is-dev';
|
||||
import unhandled from 'electron-unhandled';
|
||||
// import { autoUpdater } from 'electron-updater';
|
||||
import { promises as fs } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { join, basename } from 'path';
|
||||
|
||||
import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher } from './setup';
|
||||
|
||||
@@ -151,15 +151,47 @@ app.on('activate', async function () {
|
||||
* This provides a secure, native way to save files directly to the Downloads
|
||||
* directory using the main process's file system access.
|
||||
*
|
||||
* Security: File names are sanitized to prevent path traversal attacks.
|
||||
* Only safe file extensions are allowed (.json, .txt, .csv, .md).
|
||||
*
|
||||
* @param fileName - The name of the file to save (including extension)
|
||||
* @param data - The data to write to the file (string or buffer)
|
||||
* @returns Promise<{success: boolean, path?: string, error?: string}>
|
||||
*/
|
||||
ipcMain.handle('export-data-to-downloads', async (_event, fileName: string, data: string) => {
|
||||
try {
|
||||
// Security: Sanitize file name to prevent path traversal
|
||||
// 1. Extract only the basename (removes any directory components)
|
||||
const sanitizedBaseName = basename(fileName);
|
||||
|
||||
// 2. Reject if still contains path separators (shouldn't happen after basename, but double-check)
|
||||
if (sanitizedBaseName.includes('/') || sanitizedBaseName.includes('\\')) {
|
||||
throw new Error('Invalid file name: path separators not allowed');
|
||||
}
|
||||
|
||||
// 3. Enforce allowed file extensions for security
|
||||
const allowedExtensions = ['.json', '.txt', '.csv', '.md', '.log'];
|
||||
const hasAllowedExtension = allowedExtensions.some(ext =>
|
||||
sanitizedBaseName.toLowerCase().endsWith(ext.toLowerCase())
|
||||
);
|
||||
|
||||
if (!hasAllowedExtension) {
|
||||
throw new Error(`Invalid file extension. Allowed: ${allowedExtensions.join(', ')}`);
|
||||
}
|
||||
|
||||
// 4. Additional validation: reject empty or suspicious names
|
||||
if (!sanitizedBaseName || sanitizedBaseName.trim().length === 0) {
|
||||
throw new Error('File name cannot be empty');
|
||||
}
|
||||
|
||||
// 5. Reject names that are too long (prevent potential filesystem issues)
|
||||
if (sanitizedBaseName.length > 255) {
|
||||
throw new Error('File name too long (max 255 characters)');
|
||||
}
|
||||
|
||||
// Get the user's Downloads directory path
|
||||
const downloadsDir = app.getPath('downloads');
|
||||
const filePath = join(downloadsDir, fileName);
|
||||
const filePath = join(downloadsDir, sanitizedBaseName);
|
||||
|
||||
// Write the file to the Downloads directory
|
||||
await fs.writeFile(filePath, data, 'utf-8');
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
"lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src",
|
||||
"type-safety-check": "./scripts/type-safety-check.sh",
|
||||
"type-check": "tsc --noEmit",
|
||||
"prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js && node scripts/copy-wasm.js",
|
||||
"prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node scripts/copy-wasm.js",
|
||||
"test:prerequisites": "node scripts/check-prerequisites.js",
|
||||
"check:dependencies": "./scripts/check-dependencies.sh",
|
||||
"test:all": "npm run lint && tsc && npm run test:web && npm run test:mobile && ./scripts/test-safety-check.sh && echo '\n\n\nGotta add the performance tests'",
|
||||
"test:all": "npm run lint && tsc && npm run test:web && npm run test:mobile && echo '\n\n\nGotta add the performance tests'",
|
||||
"test:web": "npx playwright test -c playwright.config-local.ts --trace on",
|
||||
"test:mobile": "./scripts/test-mobile.sh",
|
||||
"test:android": "node scripts/test-android.js",
|
||||
@@ -64,6 +64,7 @@
|
||||
"build:web:serve:test": "./scripts/build-web.sh --serve --test",
|
||||
"build:web:serve:prod": "./scripts/build-web.sh --serve --prod",
|
||||
"docker:up": "docker-compose up",
|
||||
"build:web:build": "./scripts/build-web.sh",
|
||||
"docker:up:test": "npm run build:web:build -- --mode test && docker-compose up test",
|
||||
"docker:up:prod": "npm run build:web:build -- --mode production && docker-compose up production",
|
||||
"docker:down": "docker-compose down",
|
||||
|
||||
@@ -24,7 +24,6 @@ SENSITIVE=(
|
||||
"android/**"
|
||||
"ios/**"
|
||||
"sw_scripts/**"
|
||||
"sw_combine.js"
|
||||
"Dockerfile"
|
||||
"docker/**"
|
||||
"capacitor.config.ts"
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Critical Files Migration Validator
|
||||
# Author: Matthew Raymer
|
||||
# Description: Validates migration status of critical files
|
||||
#
|
||||
|
||||
echo 🔍 Critical Files Migration Validator"
|
||||
echo "🔍 Critical Files Migration Validator"
|
||||
echo "====================================="
|
||||
|
||||
# Function to check actual usage (not comments)
|
||||
@@ -10,77 +15,87 @@ check_actual_usage() {
|
||||
local description="$3"
|
||||
|
||||
# Remove comments and check for actual usage
|
||||
local count=$(grep -v ^[[:space:]]*//\|^[[:space:]]*\*\|^[[:space:]]*<!--" "$file" | \
|
||||
grep -v TODO.*migration\|FIXME.*migration" | \
|
||||
local count=$(grep -v "^[[:space:]]*//\|^[[:space:]]*\*\|^[[:space:]]*<!--" "$file" | \
|
||||
grep -v "TODO.*migration\|FIXME.*migration" | \
|
||||
grep -v "Migration.*replaced\|migrated.*from" | \
|
||||
grep -c $pattern" || echo 0)
|
||||
grep -c "$pattern" || echo 0)
|
||||
|
||||
if [$count" -gt0 then
|
||||
echo ❌ $description: $count instances
|
||||
return 1 else
|
||||
echo ✅$description: None found
|
||||
if [ "$count" -gt 0 ]; then
|
||||
echo " ❌ $description: $count instances"
|
||||
return 1
|
||||
else
|
||||
echo " ✅ $description: None found"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to check notification migration
|
||||
check_notifications() {
|
||||
local file="$1
|
||||
local file="$1"
|
||||
# Check for notification helpers
|
||||
local has_helpers=$(grep -c "createNotifyHelpers" $file" || echo "0")
|
||||
local has_helpers=$(grep -c "createNotifyHelpers" "$file" || echo "0")
|
||||
|
||||
# Check for direct $notify calls (excluding helper setup)
|
||||
local direct_notify=$(grep -v "createNotifyHelpers" "$file" | \
|
||||
grep -v this\.notify\." | \
|
||||
grep -v "this\.notify\." | \
|
||||
grep -c "this\.\$notify" || echo 0)
|
||||
|
||||
if $has_helpers" -gt0 && $direct_notify" -eq0 then
|
||||
echo " ✅ Complete notification migration
|
||||
if [ "$has_helpers" -gt 0 ] && [ "$direct_notify" -eq 0 ]; then
|
||||
echo " ✅ Complete notification migration"
|
||||
return 0
|
||||
elif $has_helpers" -gt0 && $direct_notify" -gt0 then
|
||||
echo " ⚠️ Mixed pattern: $direct_notify direct calls
|
||||
return 1 else
|
||||
echo " ❌ No notification migration
|
||||
elif [ "$has_helpers" -gt 0 ] && [ "$direct_notify" -gt 0 ]; then
|
||||
echo " ⚠️ Mixed pattern: $direct_notify direct calls"
|
||||
return 1
|
||||
else
|
||||
echo " ❌ No notification migration"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to analyze a file
|
||||
analyze_file() {
|
||||
local file="$1 echo ""
|
||||
local file="$1"
|
||||
echo ""
|
||||
echo "📄 Analyzing: $file"
|
||||
echo "----------------------------------------"
|
||||
|
||||
local issues=0 # Check legacy patterns
|
||||
echo "🔍 Legacy Patterns:
|
||||
check_actual_usage$file aseUtil" "databaseUtil usage || ((issues++))
|
||||
check_actual_usage "$filelogConsoleAndDb ConsoleAndDb usage || ((issues++))
|
||||
check_actual_usage$file formServiceFactory\.getInstance ct PlatformService usage ||((issues++))
|
||||
local issues=0
|
||||
# Check legacy patterns
|
||||
echo "🔍 Legacy Patterns:"
|
||||
check_actual_usage "$file" "databaseUtil" "databaseUtil usage" || ((issues++))
|
||||
check_actual_usage "$file" "logConsoleAndDb" "ConsoleAndDb usage" || ((issues++))
|
||||
check_actual_usage "$file" "formServiceFactory\.getInstance" "PlatformService usage" || ((issues++))
|
||||
|
||||
# Check notifications
|
||||
echo 🔔 Notifications:"
|
||||
check_notifications "$file ||((issues++))
|
||||
echo "🔔 Notifications:"
|
||||
check_notifications "$file" || ((issues++))
|
||||
|
||||
# Check PlatformServiceMixin
|
||||
echo "🔧 PlatformServiceMixin:"
|
||||
local has_mixin=$(grep -cPlatformServiceMixin" $file || echo 0)
|
||||
local has_mixins=$(grep -cmixins.*PlatformServiceMixin\|mixins.*\[PlatformServiceMixin" $file" || echo 0)
|
||||
local has_mixin=$(grep -c "PlatformServiceMixin" "$file" || echo 0)
|
||||
local has_mixins=$(grep -c "mixins.*PlatformServiceMixin\|mixins.*\[PlatformServiceMixin" "$file" || echo 0)
|
||||
|
||||
if $has_mixin" -gt 0 && $has_mixins" -gt0 then
|
||||
echo " ✅ PlatformServiceMixin properly integrated elif $has_mixin" -gt 0 && $has_mixins" -eq0 then
|
||||
echo " ⚠️ Imported but not used as mixin ((issues++))
|
||||
if [ "$has_mixin" -gt 0 ] && [ "$has_mixins" -gt 0 ]; then
|
||||
echo " ✅ PlatformServiceMixin properly integrated"
|
||||
elif [ "$has_mixin" -gt 0 ] && [ "$has_mixins" -eq 0 ]; then
|
||||
echo " ⚠️ Imported but not used as mixin"
|
||||
((issues++))
|
||||
else
|
||||
echo " ❌ No PlatformServiceMixin usage ((issues++))
|
||||
echo " ❌ No PlatformServiceMixin usage"
|
||||
((issues++))
|
||||
fi
|
||||
|
||||
# Check TODO comments
|
||||
local todo_count=$(grep -c TODO.*migration\|FIXME.*migration" $file || echo "0) if $todo_count" -gt0 then
|
||||
echo ⚠️ TODO/FIXME comments: $todo_count ((issues++))
|
||||
local todo_count=$(grep -c "TODO.*migration\|FIXME.*migration" "$file" || echo "0")
|
||||
if [ "$todo_count" -gt 0 ]; then
|
||||
echo " ⚠️ TODO/FIXME comments: $todo_count"
|
||||
((issues++))
|
||||
fi
|
||||
|
||||
if$issues" -eq0 then
|
||||
echo "✅ File is fully migrated else
|
||||
echo❌ $issues issues found"
|
||||
if [ "$issues" -eq 0 ]; then
|
||||
echo "✅ File is fully migrated"
|
||||
else
|
||||
echo "❌ $issues issues found"
|
||||
fi
|
||||
|
||||
return $issues
|
||||
@@ -88,35 +103,39 @@ analyze_file() {
|
||||
|
||||
# Main analysis
|
||||
echo ""
|
||||
echo 📊 Critical Files Analysis"
|
||||
echo "📊 Critical Files Analysis"
|
||||
echo "=========================="
|
||||
|
||||
# Critical files from our assessment
|
||||
files=(
|
||||
src/components/MembersList.vue"
|
||||
"src/components/MembersList.vue"
|
||||
"src/views/ContactsView.vue"
|
||||
src/views/OnboardMeetingSetupView.vue"
|
||||
src/db/databaseUtil.ts"
|
||||
src/db/index.ts
|
||||
"src/views/OnboardMeetingSetupView.vue"
|
||||
"src/db/databaseUtil.ts"
|
||||
"src/db/index.ts"
|
||||
)
|
||||
|
||||
total_issues=0
|
||||
for file in ${files[@]}"; do
|
||||
for file in "${files[@]}"; do
|
||||
if [ -f "$file" ]; then
|
||||
analyze_file "$file"
|
||||
total_issues=$((total_issues + $?))
|
||||
else
|
||||
echo ❌ File not found: $file"
|
||||
echo "❌ File not found: $file"
|
||||
fi
|
||||
done
|
||||
|
||||
# Summary
|
||||
echo "echo📋 Summary"
|
||||
echo=========="
|
||||
echo ""
|
||||
echo "📋 Summary"
|
||||
echo "=========="
|
||||
echo "Files analyzed: ${#files[@]}"
|
||||
echo "Total issues found: $total_issues"
|
||||
|
||||
if$total_issues" -eq 0]; then
|
||||
echo "✅ All critical files are properly migrated exit 0 echo "❌ Migration issues require attention"
|
||||
if [ "$total_issues" -eq 0 ]; then
|
||||
echo "✅ All critical files are properly migrated"
|
||||
exit 0
|
||||
else
|
||||
echo "❌ Migration issues require attention"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -3,7 +3,8 @@ import { logger } from "./utils/logger";
|
||||
|
||||
const platform = process.env.VITE_PLATFORM;
|
||||
|
||||
// PWA service worker is automatically registered by VitePWA plugin
|
||||
// Note: PWA functionality is currently not implemented.
|
||||
// Service worker registration would be handled here when PWA support is added.
|
||||
|
||||
const app = initializeApp();
|
||||
|
||||
|
||||
@@ -39,7 +39,9 @@ export class PlatformServiceFactory {
|
||||
}
|
||||
|
||||
// Only log when actually creating the instance
|
||||
const platform = process.env.VITE_PLATFORM || "web";
|
||||
// Use import.meta.env for Vite environment variables (standard Vite pattern)
|
||||
// process.env.VITE_PLATFORM is defined via Vite's define config, but import.meta.env is preferred
|
||||
const platform = (import.meta.env?.VITE_PLATFORM || process.env.VITE_PLATFORM || "web") as string;
|
||||
|
||||
if (!PlatformServiceFactory.creationLogged) {
|
||||
// Use console for critical startup message to avoid circular dependency
|
||||
|
||||
@@ -54,6 +54,8 @@ export class CapacitorPlatformService
|
||||
private initializationPromise: Promise<void> | null = null;
|
||||
private operationQueue: Array<QueuedOperation> = [];
|
||||
private isProcessingQueue: boolean = false;
|
||||
private readonly MAX_QUEUE_SIZE = 1000; // Maximum queue size before failing
|
||||
private maxQueueSizeReached = 0; // Track peak queue size for telemetry
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -371,6 +373,31 @@ export class CapacitorPlatformService
|
||||
});
|
||||
|
||||
return new Promise<R>((resolve, reject) => {
|
||||
// Queue size guard: prevent memory exhaustion from unbounded queue growth
|
||||
if (this.operationQueue.length >= this.MAX_QUEUE_SIZE) {
|
||||
const error = new Error(
|
||||
`Database operation queue is full (${this.MAX_QUEUE_SIZE} operations). ` +
|
||||
`This usually indicates the database is not initializing properly or operations are being queued too quickly.`
|
||||
);
|
||||
logger.error(
|
||||
`[CapacitorPlatformService] Queue size limit reached: ${this.operationQueue.length}/${this.MAX_QUEUE_SIZE}`,
|
||||
);
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Track peak queue size for telemetry
|
||||
if (this.operationQueue.length > this.maxQueueSizeReached) {
|
||||
this.maxQueueSizeReached = this.operationQueue.length;
|
||||
}
|
||||
|
||||
// Log warning if queue is getting large (but not at limit yet)
|
||||
if (this.operationQueue.length > this.MAX_QUEUE_SIZE * 0.8) {
|
||||
logger.warn(
|
||||
`[CapacitorPlatformService] Queue size is high: ${this.operationQueue.length}/${this.MAX_QUEUE_SIZE}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Create completely plain objects that Vue cannot make reactive
|
||||
// Step 1: Deep clone the converted params to ensure they're plain objects
|
||||
const plainParams = JSON.parse(JSON.stringify(convertedParams));
|
||||
@@ -865,6 +892,27 @@ export class CapacitorPlatformService
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets telemetry information about the database operation queue.
|
||||
* Useful for debugging and monitoring queue health.
|
||||
* @returns Queue telemetry data
|
||||
*/
|
||||
getQueueTelemetry(): {
|
||||
currentSize: number;
|
||||
maxSize: number;
|
||||
peakSize: number;
|
||||
isProcessing: boolean;
|
||||
initialized: boolean;
|
||||
} {
|
||||
return {
|
||||
currentSize: this.operationQueue.length,
|
||||
maxSize: this.MAX_QUEUE_SIZE,
|
||||
peakSize: this.maxQueueSizeReached,
|
||||
isProcessing: this.isProcessingQueue,
|
||||
initialized: this.initialized,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and requests storage permissions if needed
|
||||
* @returns Promise that resolves when permissions are granted
|
||||
|
||||
Reference in New Issue
Block a user