diff --git a/.cursor/rules/development/type_safety_guide.mdc b/.cursor/rules/development/type_safety_guide.mdc index 54fd8b57..9c23938a 100644 --- a/.cursor/rules/development/type_safety_guide.mdc +++ b/.cursor/rules/development/type_safety_guide.mdc @@ -46,27 +46,18 @@ Practical rules to keep TypeScript strict and predictable. Minimize exceptions. ### Core Type Safety Rules - **No `any` Types**: Use explicit types or `unknown` with proper type guards -- **Error Handling Uses Guards**: Implement and reuse type guards from - `src/interfaces/**` -- **Dynamic Property Access**: Use `keyof` + `in` checks for type-safe - property access +- **Error Handling Uses Guards**: Implement and reuse type guards from `src/interfaces/**` +- **Dynamic Property Access**: Use `keyof` + `in` checks for type-safe property access ### Type Guard Patterns - - **API Errors**: Use `isApiError(error)` guards for API error handling -- **Database Errors**: Use `isDatabaseError(error)` guards for database - operations -- **Axios Errors**: Implement `isAxiosError(error)` guards for HTTP error - handling +- **Database Errors**: Use `isDatabaseError(error)` guards for database operations +- **Axios Errors**: Implement `isAxiosError(error)` guards for HTTP error handling ### Implementation Guidelines - -- **Avoid Type Assertions**: Replace `as any` with proper type guards and - interfaces -- **Narrow Types Properly**: Use type guards to narrow `unknown` types - safely -- **Document Type Decisions**: Explain complex type structures and their - purpose +- **Avoid Type Assertions**: Replace `as any` with proper type guards and interfaces +- **Narrow Types Properly**: Use type guards to narrow `unknown` types safely +- **Document Type Decisions**: Explain complex type structures and their purpose ## Minimal Special Cases (document in PR when used) diff --git a/.cursor/rules/software_development.mdc b/.cursor/rules/software_development.mdc index ed60ff4e..2f7c2c71 100644 --- a/.cursor/rules/software_development.mdc +++ b/.cursor/rules/software_development.mdc @@ -171,10 +171,55 @@ debugging, architecture decisions, and testing. - [ ] Environment impact assessed for team members - [ ] Pre-build validation implemented where appropriate ---- +## Additional Core Principles -**Status**: Active development guidelines -**Priority**: High -**Estimated Effort**: Ongoing -**Dependencies**: base_context.mdc, research_diagnostic.mdc -**Stakeholders**: Development team +### 4. Dependency Management & Environment Validation +- **Pre-build Validation**: Always validate critical dependencies before executing build scripts +- **Environment Consistency**: Ensure team members have identical development environments +- **Dependency Verification**: Check that required packages are installed and accessible +- **Path Resolution**: Use `npx` for local dependencies to avoid PATH issues + +## Additional Required Workflows + +### Dependency Validation (Before Proposing Changes) +- [ ] **Dependency Validation**: Verify all required dependencies are available and accessible + +### Environment Impact Assessment (During Solution Design) +- [ ] **Environment Impact**: Assess how changes affect team member setups + +## Additional Competence Hooks + +### Dependency & Environment Management +- **"What dependencies does this feature require and are they properly declared?"** +- **"How will this change affect team member development environments?"** +- **"What validation can we add to catch dependency issues early?"** + +## Dependency Management Best Practices + +### Pre-build Validation +- **Check Critical Dependencies**: Validate essential tools before executing build scripts +- **Use npx for Local Dependencies**: Prefer `npx tsx` over direct `tsx` to avoid PATH issues +- **Environment Consistency**: Ensure all team members have identical dependency versions + +### Common Pitfalls +- **Missing npm install**: Team members cloning without running `npm install` +- **PATH Issues**: Direct command execution vs. npm script execution differences +- **Version Mismatches**: Different Node.js/npm versions across team members + +### Validation Strategies +- **Dependency Check Scripts**: Implement pre-build validation for critical dependencies +- **Environment Requirements**: Document and enforce minimum Node.js/npm versions +- **Onboarding Checklist**: Standardize team member setup procedures + +### Error Messages and Guidance +- **Specific Error Context**: Provide clear guidance when dependency issues occur +- **Actionable Solutions**: Direct users to specific commands (`npm install`, `npm run check:dependencies`) +- **Environment Diagnostics**: Implement comprehensive environment validation tools + +### Build Script Enhancements +- **Early Validation**: Check dependencies before starting build processes +- **Graceful Degradation**: Continue builds when possible but warn about issues +- **Helpful Tips**: Remind users about dependency management best practices + +- **Narrow Types Properly**: Use type guards to narrow `unknown` types safely +- **Document Type Decisions**: Explain complex type structures and their purpose diff --git a/BUILDING.md b/BUILDING.md index e1e94fcd..e5abf069 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -1017,47 +1017,27 @@ If you need to build manually or want to understand the individual steps: export GEM_PATH=$shortened_path ``` -1. Build the web assets & update ios: +1. Bump the version in package.json, then here. - ```bash - rm -rf dist - npm run build:web - npm run build:capacitor - npx cap sync ios - ``` - - - If that fails with "Could not find..." then look at the "gem_path" instructions above. - -3. Copy the assets: - - ```bash - # It makes no sense why capacitor-assets will not run without these but it actually changes the contents. - mkdir -p ios/App/App/Assets.xcassets/AppIcon.appiconset - echo '{"images":[]}' > ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json - mkdir -p ios/App/App/Assets.xcassets/Splash.imageset - echo '{"images":[]}' > ios/App/App/Assets.xcassets/Splash.imageset/Contents.json - npx capacitor-assets generate --ios ``` - -4. Bump the version to match Android & package.json: - - ``` - cd ios/App && xcrun agvtool new-version 39 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.0.6;/g" App.xcodeproj/project.pbxproj && cd - + cd ios/App && xcrun agvtool new-version 40 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.0.7;/g" App.xcodeproj/project.pbxproj && cd - # Unfortunately this edits Info.plist directly. #xcrun agvtool new-marketing-version 0.4.5 ``` -5. Open the project in Xcode: +2. Build. - ```bash - npx cap open ios - ``` + Here's prod. Also available: test, dev + + ```bash + npm run build:ios:prod + ``` -6. Use Xcode to build and run on simulator or device. +3.1. Use Xcode to build and run on simulator or device. * Select Product -> Destination with some Simulator version. Then click the run arrow. -7. Release +3.2. Use Xcode to release. * Someday: Under "General" we want to rename a bunch of things to "Time Safari" * Choose Product -> Destination -> Any iOS Device @@ -1125,35 +1105,28 @@ The recommended way to build for Android is using the automated build script: #### Manual Build Process -1. Build the web assets: - - ```bash - rm -rf dist - npm run build:web - npm run build:capacitor - ``` - -2. Update Android project with latest build: +1. Bump the version in package.json, then here: android/app/build.gradle - ```bash - npx cap sync android - ``` + ```bash + perl -p -i -e 's/versionCode .*/versionCode 40/g' android/app/build.gradle + perl -p -i -e 's/versionName .*/versionName "1.0.7"/g' android/app/build.gradle + ``` -3. Copy the assets +2. Build. - ```bash - npx capacitor-assets generate --android - ``` + Here's prod. Also available: test, dev -4. Bump version to match iOS & package.json: android/app/build.gradle + ```bash + npm run build:android:prod + ``` -5. Open the project in Android Studio: +3. Open the project in Android Studio: ```bash npx cap open android ``` -6. Use Android Studio to build and run on emulator or device. +4. Use Android Studio to build and run on emulator or device. ## Android Build from the console @@ -1186,9 +1159,9 @@ cd - At play.google.com/console: -- Go to the Testing Track (eg. Closed). +- Go to Production or the Closed Testing and either Create Track or Manage Track. - Click "Create new release". -- Upload the `aab` file. +- Upload the `aab` file from: app/build/outputs/bundle/release/app-release.aab - Hit "Next". - Save, go to "Publishing Overview" as prompted, and click "Send changes for review". diff --git a/CHANGELOG.md b/CHANGELOG.md index cf28e788..19209fb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.0.3] - 2025.07.12 -### Changed -- Photo is pinned to profile mode +## [1.0.7] - 2025.08.18 ### Fixed -- Deep link URLs (and other prod settings) -- Error in BVC begin view +- Deep link for onboard-meeting-members + ## [1.0.6] - 2025.08.09 ### Fixed diff --git a/README.md b/README.md index efc9b1ad..fc954fd5 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,33 @@ npm run assets:clean npm run build:native ``` +### Environment Setup & Dependencies + +Before building the application, ensure your development environment is properly +configured: + +```bash +# Install all dependencies (required first time and after updates) +npm install + +# Validate your development environment +npm run check:dependencies + +# Check prerequisites for testing +npm run test:prerequisites +``` + +**Common Issues & Solutions**: + +- **"tsx: command not found"**: Run `npm install` to install devDependencies +- **"capacitor-assets: command not found"**: Ensure `@capacitor/assets` is installed +- **Build failures**: Run `npm run check:dependencies` to diagnose environment issues + +**Required Versions**: +- Node.js: 18+ (LTS recommended) +- npm: 8+ (comes with Node.js) +- Platform-specific tools: Android Studio, Xcode (for mobile builds) + ### Platform Support - **Android**: Adaptive icons with foreground/background, monochrome support diff --git a/android/app/build.gradle b/android/app/build.gradle index a92af2db..57c34006 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -31,8 +31,8 @@ android { applicationId "app.timesafari.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 39 - versionName "1.0.6" + versionCode 40 + versionName "1.0.7" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index 381f4bab..5b57160c 100644 --- a/ios/App/App.xcodeproj/project.pbxproj +++ b/ios/App/App.xcodeproj/project.pbxproj @@ -403,7 +403,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 39; + CURRENT_PROJECT_VERSION = 40; DEVELOPMENT_TEAM = GM3FS5JQPH; ENABLE_APP_SANDBOX = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -413,7 +413,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.6; + MARKETING_VERSION = 1.0.7; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = app.timesafari; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -430,7 +430,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 39; + CURRENT_PROJECT_VERSION = 40; DEVELOPMENT_TEAM = GM3FS5JQPH; ENABLE_APP_SANDBOX = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -440,7 +440,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.6; + MARKETING_VERSION = 1.0.7; PRODUCT_BUNDLE_IDENTIFIER = app.timesafari; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; diff --git a/package-lock.json b/package-lock.json index d6914554..f8c11390 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "timesafari", - "version": "1.0.7-beta", + "version": "1.0.8-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "timesafari", - "version": "1.0.7-beta", + "version": "1.0.8-beta", "dependencies": { "@capacitor-community/electron": "^5.0.1", "@capacitor-community/sqlite": "6.0.2", diff --git a/package.json b/package.json index eb68f859..cfe759b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "timesafari", - "version": "1.0.7-beta", + "version": "1.0.8-beta", "description": "Time Safari Application", "author": { "name": "Time Safari Team" @@ -12,6 +12,8 @@ "type-check": "tsc --noEmit", "prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js && 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:web": "npx playwright test -c playwright.config-local.ts --trace on", "test:mobile": "./scripts/test-mobile.sh", "test:android": "node scripts/test-android.js", @@ -27,8 +29,8 @@ "build:capacitor": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --mode capacitor --config vite.config.capacitor.mts", "build:capacitor:sync": "npm run build:capacitor && npx cap sync", "build:native": "vite build && npx cap sync && npx capacitor-assets generate", - "assets:config": "tsx scripts/assets-config.ts", - "assets:validate": "tsx scripts/assets-validator.ts", + "assets:config": "npx tsx scripts/assets-config.ts", + "assets:validate": "npx tsx scripts/assets-validator.ts", "assets:clean": "rimraf android/app/src/main/res/mipmap-* ios/App/App/Assets.xcassets/**/AppIcon*.png ios/App/App/Assets.xcassets/**/Splash*.png || true", "build:ios": "./scripts/build-ios.sh", "build:ios:dev": "./scripts/build-ios.sh --dev", @@ -96,7 +98,7 @@ "build:electron:dmg:dev": "./scripts/build-electron.sh --dev --dmg", "build:electron:dmg:test": "./scripts/build-electron.sh --test --dmg", "build:electron:dmg:prod": "./scripts/build-electron.sh --prod --dmg", - "clean:android": "adb uninstall app.timesafari.app || true", + "clean:android": "./scripts/clean-android.sh", "clean:ios": "rm -rf ios/App/build ios/App/Pods ios/App/output ios/App/App/public ios/DerivedData ios/capacitor-cordova-ios-plugins ios/App/App/capacitor.config.json ios/App/App/config.xml || true", "clean:electron": "./scripts/build-electron.sh --clean", "clean:all": "npm run clean:ios && npm run clean:android && npm run clean:electron", diff --git a/scripts/build-android.sh b/scripts/build-android.sh index c6c8ae88..c795a4b0 100755 --- a/scripts/build-android.sh +++ b/scripts/build-android.sh @@ -49,6 +49,31 @@ set -e # Source common utilities source "$(dirname "$0")/common.sh" +# Function to validate critical dependencies +validate_dependencies() { + log_info "Validating critical dependencies..." + + # Check if node_modules exists + if [ ! -d "node_modules" ]; then + log_error "node_modules directory not found. Please run 'npm install' first." + exit 1 + fi + + # Check if tsx is available + if [ ! -f "node_modules/.bin/tsx" ]; then + log_error "tsx dependency not found. Please run 'npm install' first." + exit 1 + fi + + # Check if capacitor-assets is available + if [ ! -f "node_modules/.bin/capacitor-assets" ]; then + log_error "capacitor-assets dependency not found. Please run 'npm install' first." + exit 1 + fi + + log_success "All critical dependencies validated successfully" +} + # Default values BUILD_MODE="development" BUILD_TYPE="debug" @@ -179,6 +204,11 @@ parse_android_args "$@" # Print build header print_header "TimeSafari Android Build Process" + +# Validate dependencies before proceeding +validate_dependencies + +# Log build start log_info "Starting Android build process at $(date)" log_info "Build mode: $BUILD_MODE" log_info "Build type: $BUILD_TYPE" @@ -257,6 +287,7 @@ fi # Step 1: Validate asset configuration safe_execute "Validating asset configuration" "npm run assets:validate" || { log_warn "Asset validation found issues, but continuing with build..." + log_info "If you encounter build failures, please run 'npm install' first to ensure all dependencies are available." } # Step 2: Clean Android app @@ -337,6 +368,9 @@ if [ "$OPEN_STUDIO" = true ]; then log_info "Android Studio: opened" fi +# Reminder about dependency management +log_info "💡 Tip: If you encounter dependency issues, run 'npm install' to ensure all packages are up to date." + print_footer "Android Build" # Exit with success diff --git a/scripts/build-ios.sh b/scripts/build-ios.sh index e9009715..511358d5 100755 --- a/scripts/build-ios.sh +++ b/scripts/build-ios.sh @@ -173,20 +173,20 @@ check_ios_resources() { # Check for required assets if [ ! -f "assets/icon.png" ]; then - log_warning "App icon not found at assets/icon.png" + log_warn "App icon not found at assets/icon.png" fi if [ ! -f "assets/splash.png" ]; then - log_warning "Splash screen not found at assets/splash.png" + log_warn "Splash screen not found at assets/splash.png" fi # Check for iOS-specific files if [ ! -f "ios/App/App/Info.plist" ]; then - log_warning "Info.plist not found" + log_warn "Info.plist not found" fi if [ ! -f "ios/App/App/AppDelegate.swift" ]; then - log_warning "AppDelegate.swift not found" + log_warn "AppDelegate.swift not found" fi log_success "iOS resource check completed" diff --git a/scripts/check-dependencies.sh b/scripts/check-dependencies.sh new file mode 100755 index 00000000..c8e14e8b --- /dev/null +++ b/scripts/check-dependencies.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# check-dependencies.sh +# Author: Matthew Raymer +# Date: 2025-08-19 +# Description: Dependency validation script for TimeSafari development environment +# This script checks for critical dependencies required for building the application. + +# Exit on any error +set -e + +# Source common utilities +source "$(dirname "$0")/common.sh" + +print_header "TimeSafari Dependency Validation" + +log_info "Checking development environment dependencies..." + +# Check Node.js version +if command -v node &> /dev/null; then + NODE_VERSION=$(node --version) + log_info "Node.js version: $NODE_VERSION" + + # Extract major version number + MAJOR_VERSION=$(echo $NODE_VERSION | sed 's/v\([0-9]*\)\..*/\1/') + if [ "$MAJOR_VERSION" -lt 18 ]; then + log_error "Node.js version $NODE_VERSION is too old. Please upgrade to Node.js 18 or later." + exit 1 + fi +else + log_error "Node.js is not installed. Please install Node.js 18 or later." + exit 1 +fi + +# Check npm version +if command -v npm &> /dev/null; then + NPM_VERSION=$(npm --version) + log_info "npm version: $NPM_VERSION" +else + log_error "npm is not installed. Please install npm." + exit 1 +fi + +# Check if node_modules exists +if [ ! -d "node_modules" ]; then + log_error "node_modules directory not found." + log_info "Please run: npm install" + exit 1 +fi + +# Check critical dependencies +log_info "Validating critical packages..." + +CRITICAL_DEPS=("tsx" "capacitor-assets" "vite") + +for dep in "${CRITICAL_DEPS[@]}"; do + if [ -f "node_modules/.bin/$dep" ]; then + log_success "✓ $dep found" + else + log_error "✗ $dep not found in node_modules/.bin" + log_info "This usually means the package wasn't installed properly." + log_info "Try running: npm install" + exit 1 + fi +done + +# Check TypeScript via npx +if npx tsc --version &> /dev/null; then + TSC_VERSION=$(npx tsc --version) + log_success "✓ TypeScript found: $TSC_VERSION" +else + log_error "✗ TypeScript not accessible via npx" + log_info "Try running: npm install" + exit 1 +fi + +# Check Capacitor CLI +if command -v npx &> /dev/null; then + if npx cap --version &> /dev/null; then + CAP_VERSION=$(npx cap --version) + log_success "✓ Capacitor CLI version: $CAP_VERSION" + else + log_error "✗ Capacitor CLI not accessible via npx" + log_info "Try running: npm install @capacitor/cli" + exit 1 + fi +else + log_error "npx is not available. Please ensure npm is properly installed." + exit 1 +fi + +# Check Android development tools +if command -v adb &> /dev/null; then + log_success "✓ Android Debug Bridge (adb) found" +else + log_warn "⚠ Android Debug Bridge (adb) not found" + log_info "This is only needed for Android development and testing." +fi + +if command -v gradle &> /dev/null; then + GRADLE_VERSION=$(gradle --version | head -n 1) + log_success "✓ Gradle found: $GRADLE_VERSION" +else + log_warn "⚠ Gradle not found in PATH" + log_info "This is only needed if building outside of Android Studio." +fi + +log_success "Dependency validation completed successfully!" +log_info "Your development environment is ready for TimeSafari development." + +print_footer "Dependency Validation" diff --git a/scripts/clean-android.sh b/scripts/clean-android.sh new file mode 100755 index 00000000..4fa354af --- /dev/null +++ b/scripts/clean-android.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# clean-android.sh +# Author: Matthew Raymer +# Date: 2025-08-19 +# Description: Clean Android app with timeout protection to prevent hanging +# This script safely uninstalls the TimeSafari app from connected Android devices +# with a 30-second timeout to prevent indefinite hanging. + +# Exit on any error +set -e + +# Source common utilities +source "$(dirname "$0")/common.sh" + +# Function to implement timeout for systems without timeout command +timeout_command() { + local timeout_seconds="$1" + shift + + # Check if timeout command exists + if command -v timeout &> /dev/null; then + timeout "$timeout_seconds" "$@" + else + # Fallback for systems without timeout (like macOS) + # Use perl to implement timeout + perl -e ' + eval { + local $SIG{ALRM} = sub { die "timeout" }; + alarm shift; + system @ARGV; + alarm 0; + }; + if ($@) { exit 1; } + ' "$timeout_seconds" "$@" + fi +} + +log_info "Starting Android cleanup process..." + +# Check if adb is available +if ! command -v adb &> /dev/null; then + log_error "adb command not found. Please install Android SDK Platform Tools." + exit 1 +fi + +# Check for connected devices +log_info "Checking for connected Android devices..." +if adb devices | grep -q 'device$'; then + log_info "Android device(s) found. Attempting to uninstall app..." + + # Try to uninstall with timeout + if timeout_command 30 adb uninstall app.timesafari.app; then + log_success "Successfully uninstalled TimeSafari app" + else + log_warn "Uninstall failed or timed out after 30 seconds" + log_info "This is normal if the app wasn't installed or device is unresponsive" + fi +else + log_info "No Android devices connected. Skipping uninstall." +fi + +log_success "Android cleanup process completed" diff --git a/src/interfaces/deepLinks.ts b/src/interfaces/deepLinks.ts index d5266c7a..0fe5c68d 100644 --- a/src/interfaces/deepLinks.ts +++ b/src/interfaces/deepLinks.ts @@ -28,7 +28,7 @@ import { z } from "zod"; // Parameter validation schemas for each route type -export const deepLinkSchemas = { +export const deepLinkPathSchemas = { claim: z.object({ id: z.string(), }), @@ -60,7 +60,7 @@ export const deepLinkSchemas = { jwt: z.string().optional(), }), "onboard-meeting-members": z.object({ - id: z.string(), + groupId: z.string(), }), project: z.object({ id: z.string(), @@ -70,6 +70,17 @@ export const deepLinkSchemas = { }), }; +export const deepLinkQuerySchemas = { + "onboard-meeting-members": z.object({ + password: z.string(), + }), +}; + +// Add a union type of all valid route paths +export const VALID_DEEP_LINK_ROUTES = Object.keys( + deepLinkPathSchemas, +) as readonly (keyof typeof deepLinkPathSchemas)[]; + // Create a type from the array export type DeepLinkRoute = (typeof VALID_DEEP_LINK_ROUTES)[number]; @@ -80,14 +91,13 @@ export const baseUrlSchema = z.object({ queryParams: z.record(z.string()).optional(), }); -// Add a union type of all valid route paths -export const VALID_DEEP_LINK_ROUTES = Object.keys( - deepLinkSchemas, -) as readonly (keyof typeof deepLinkSchemas)[]; +// export type DeepLinkPathParams = { +// [K in keyof typeof deepLinkPathSchemas]: z.infer<(typeof deepLinkPathSchemas)[K]>; +// }; -export type DeepLinkParams = { - [K in keyof typeof deepLinkSchemas]: z.infer<(typeof deepLinkSchemas)[K]>; -}; +// export type DeepLinkQueryParams = { +// [K in keyof typeof deepLinkQuerySchemas]: z.infer<(typeof deepLinkQuerySchemas)[K]>; +// }; export interface DeepLinkError extends Error { code: string; diff --git a/src/services/ProfileService.ts b/src/services/ProfileService.ts index 26dbe9f3..62a85970 100644 --- a/src/services/ProfileService.ts +++ b/src/services/ProfileService.ts @@ -10,7 +10,6 @@ import { getHeaders, errorStringForLog } from "@/libs/endorserServer"; import { handleApiError } from "./api"; import { logger } from "@/utils/logger"; import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView"; -import { AxiosErrorResponse } from "@/interfaces/common"; /** * Profile data interface @@ -125,15 +124,18 @@ export class ProfileService { async deleteProfile(activeDid: string): Promise { try { const headers = await getHeaders(activeDid); - const response = await this.axios.delete( - `${this.partnerApiServer}/api/partner/userProfile`, - { headers }, - ); + const url = `${this.partnerApiServer}/api/partner/userProfile`; + const response = await this.axios.delete(url, { headers }); if (response.status === 204 || response.status === 200) { logger.info("Profile deleted successfully"); return true; } else { + logger.error("Unexpected response status when deleting profile:", { + status: response.status, + statusText: response.statusText, + data: response.data, + }); throw new Error( `Profile not deleted - HTTP ${response.status}: ${response.statusText}`, ); @@ -157,9 +159,11 @@ export class ProfileService { return true; // Consider this a success if profile doesn't exist } else if (response.status === 400) { logger.error("Bad request when deleting profile:", response.data); - throw new Error( - `Profile deletion failed: ${response.data?.error?.message || "Bad request"}`, - ); + const errorMessage = + typeof response.data === "string" + ? response.data + : response.data?.message || "Bad request"; + throw new Error(`Profile deletion failed: ${errorMessage}`); } else if (response.status === 401) { logger.error("Unauthorized to delete profile"); throw new Error("You are not authorized to delete this profile"); @@ -240,10 +244,33 @@ export class ProfileService { /** * Type guard for API errors with proper typing */ - private isApiError(error: unknown): error is AxiosErrorResponse { + private isApiError(error: unknown): error is { + response?: { + status?: number; + statusText?: string; + data?: { message?: string } | string; + }; + } { return typeof error === "object" && error !== null && "response" in error; } + /** + * Extract URL from AxiosError without type casting + */ + private getErrorUrl(error: unknown): string | undefined { + if (this.isAxiosError(error)) { + return error.config?.url; + } + return undefined; + } + + /** + * Type guard for AxiosError + */ + private isAxiosError(error: unknown): error is AxiosError { + return error instanceof AxiosError; + } + /** * Extract error URL safely from error object */ diff --git a/src/services/deepLinks.ts b/src/services/deepLinks.ts index d8445607..ee6095bb 100644 --- a/src/services/deepLinks.ts +++ b/src/services/deepLinks.ts @@ -47,10 +47,11 @@ import { Router } from "vue-router"; import { z } from "zod"; import { - deepLinkSchemas, + deepLinkPathSchemas, baseUrlSchema, routeSchema, DeepLinkRoute, + deepLinkQuerySchemas, } from "../interfaces/deepLinks"; import type { DeepLinkError } from "../interfaces/deepLinks"; import { logger } from "../utils/logger"; @@ -74,7 +75,7 @@ function getFirstKeyFromZodObject( * because "router.replace" expects the right parameter name for the route. */ export const ROUTE_MAP: Record = - Object.entries(deepLinkSchemas).reduce( + Object.entries(deepLinkPathSchemas).reduce( (acc, [routeName, schema]) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const paramKey = getFirstKeyFromZodObject(schema as z.ZodObject); @@ -198,15 +199,24 @@ export class DeepLinkHandler { } // Continue with parameter validation as before... - const schema = deepLinkSchemas[path as keyof typeof deepLinkSchemas]; + const pathSchema = + deepLinkPathSchemas[path as keyof typeof deepLinkPathSchemas]; + const querySchema = + deepLinkQuerySchemas[path as keyof typeof deepLinkQuerySchemas]; - let validatedParams; + let validatedPathParams: Record = {}; + let validatedQueryParams: Record = {}; try { - validatedParams = await schema.parseAsync(params); + if (pathSchema) { + validatedPathParams = await pathSchema.parseAsync(params); + } + if (querySchema) { + validatedQueryParams = await querySchema.parseAsync(query); + } } catch (error) { // For parameter validation errors, provide specific error feedback logger.error( - `[DeepLink] Invalid parameters for route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with params: ${JSON.stringify(params)} ... and query: ${JSON.stringify(query)}`, + `[DeepLink] Invalid parameters for route name ${routeName} for path: ${path} ... with error: ${JSON.stringify(error)} ... with params: ${JSON.stringify(params)} ... and query: ${JSON.stringify(query)}`, ); await this.router.replace({ name: "deep-link-error", @@ -226,20 +236,22 @@ export class DeepLinkHandler { try { await this.router.replace({ name: routeName, - params: validatedParams, + params: validatedPathParams, + query: validatedQueryParams, }); } catch (error) { logger.error( - `[DeepLink] Error routing to route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with validated params: ${JSON.stringify(validatedParams)}`, + `[DeepLink] Error routing to route name ${routeName} for path: ${path}: ${JSON.stringify(error)} ... with validated params: ${JSON.stringify(validatedPathParams)} ... and query: ${JSON.stringify(validatedQueryParams)}`, ); // For parameter validation errors, provide specific error feedback await this.router.replace({ name: "deep-link-error", - params: validatedParams, + params: validatedPathParams, query: { originalPath: path, errorCode: "ROUTING_ERROR", errorMessage: `Error routing to ${routeName}: ${JSON.stringify(error)}`, + ...validatedQueryParams, }, }); } diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index f635df32..eb99665c 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -182,7 +182,6 @@ @change="onLocationCheckboxChange" /> -

diff --git a/src/views/DeepLinkErrorView.vue b/src/views/DeepLinkErrorView.vue index 6decd859..a3b53b09 100644 --- a/src/views/DeepLinkErrorView.vue +++ b/src/views/DeepLinkErrorView.vue @@ -47,7 +47,7 @@ import { computed, onMounted } from "vue"; import { useRoute, useRouter } from "vue-router"; import { VALID_DEEP_LINK_ROUTES, - deepLinkSchemas, + deepLinkPathSchemas, } from "../interfaces/deepLinks"; import { logConsoleAndDb } from "../db/databaseUtil"; import { logger } from "../utils/logger"; @@ -56,7 +56,7 @@ const route = useRoute(); const router = useRouter(); // an object with the route as the key and the first param name as the value const deepLinkSchemaKeys = Object.fromEntries( - Object.entries(deepLinkSchemas).map(([route, schema]) => { + Object.entries(deepLinkPathSchemas).map(([route, schema]) => { const param = Object.keys(schema.shape)[0]; return [route, param]; }), diff --git a/src/views/OnboardMeetingMembersView.vue b/src/views/OnboardMeetingMembersView.vue index a1280011..7e42718b 100644 --- a/src/views/OnboardMeetingMembersView.vue +++ b/src/views/OnboardMeetingMembersView.vue @@ -113,7 +113,7 @@ export default class OnboardMeetingMembersView extends Vue { try { // Identity creation should be handled by router guard, but keep as fallback for meeting setup if (!this.activeDid) { - logger.info( + this.$logAndConsole( "[OnboardMeetingMembersView] No active DID found, creating identity as fallback for meeting setup", ); this.activeDid = await generateSaveAndActivateIdentity();