Browse Source

Merge branch 'build-improvement' into performance-optimizations-testing

pull/159/head
Matthew Raymer 2 weeks ago
parent
commit
4140f348c0
  1. 8
      android/app/build.gradle
  2. 5
      android/app/src/main/assets/capacitor.config.json
  3. 2
      android/build.gradle
  4. 2
      android/gradle.properties
  5. 4
      android/variables.gradle
  6. 5
      capacitor.config.json
  7. 338
      docs/build-system/environment-variable-precedence.md
  8. 322
      docs/build-system/platforms/android-custom-api-ip.md
  9. 10
      package.json
  10. 37
      scripts/build-android.sh
  11. 32
      scripts/build-ios.sh
  12. 28
      scripts/common.sh
  13. 15
      src/libs/util.ts
  14. 6
      src/test/PlatformServiceMixinTest.vue
  15. 14
      src/views/AccountViewView.vue
  16. 22
      src/views/ContactQRScanShowView.vue
  17. 3
      src/views/GiftedDetailsView.vue
  18. 11
      src/views/HomeView.vue
  19. 10
      src/views/ProjectsView.vue

8
android/app/build.gradle

@ -64,6 +64,14 @@ android {
} }
} }
} }
packagingOptions {
jniLibs {
pickFirsts += ['**/lib/x86_64/libbarhopper_v3.so', '**/lib/x86_64/libimage_processing_util_jni.so', '**/lib/x86_64/libsqlcipher.so']
}
}
// Configure for 16 KB page size compatibility
// Enable bundle builds (without which it doesn't work right for bundleDebug vs bundleRelease) // Enable bundle builds (without which it doesn't work right for bundleDebug vs bundleRelease)
bundle { bundle {

5
android/app/src/main/assets/capacitor.config.json

@ -57,13 +57,14 @@
] ]
}, },
"android": { "android": {
"allowMixedContent": false, "allowMixedContent": true,
"captureInput": true, "captureInput": true,
"webContentsDebuggingEnabled": false, "webContentsDebuggingEnabled": false,
"allowNavigation": [ "allowNavigation": [
"*.timesafari.app", "*.timesafari.app",
"*.jsdelivr.net", "*.jsdelivr.net",
"api.endorser.ch" "api.endorser.ch",
"10.0.2.2:3000"
] ]
}, },
"electron": { "electron": {

2
android/build.gradle

@ -7,7 +7,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.11.1' classpath 'com.android.tools.build:gradle:8.12.0'
classpath 'com.google.gms:google-services:4.4.0' classpath 'com.google.gms:google-services:4.4.0'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong

2
android/gradle.properties

@ -20,4 +20,4 @@ org.gradle.jvmargs=-Xmx1536m
# Android operating system, and which are packaged with your app's APK # Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn # https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true android.useAndroidX=true
android.suppressUnsupportedCompileSdk=34 android.suppressUnsupportedCompileSdk=36

4
android/variables.gradle

@ -1,7 +1,7 @@
ext { ext {
minSdkVersion = 22 minSdkVersion = 22
compileSdkVersion = 34 compileSdkVersion = 36
targetSdkVersion = 34 targetSdkVersion = 36
androidxActivityVersion = '1.8.0' androidxActivityVersion = '1.8.0'
androidxAppCompatVersion = '1.6.1' androidxAppCompatVersion = '1.6.1'
androidxCoordinatorLayoutVersion = '1.2.0' androidxCoordinatorLayoutVersion = '1.2.0'

5
capacitor.config.json

@ -57,13 +57,14 @@
] ]
}, },
"android": { "android": {
"allowMixedContent": false, "allowMixedContent": true,
"captureInput": true, "captureInput": true,
"webContentsDebuggingEnabled": false, "webContentsDebuggingEnabled": false,
"allowNavigation": [ "allowNavigation": [
"*.timesafari.app", "*.timesafari.app",
"*.jsdelivr.net", "*.jsdelivr.net",
"api.endorser.ch" "api.endorser.ch",
"10.0.2.2:3000"
] ]
}, },
"electron": { "electron": {

338
docs/build-system/environment-variable-precedence.md

@ -0,0 +1,338 @@
# Environment Variable Precedence and API Configuration
**Date:** August 4, 2025
**Author:** Matthew Raymer
## Overview
This document explains the order of precedence for environment variables in the
TimeSafari project, how `.env` files are used, and the API configuration scheme
for different environments.
## Order of Precedence (Highest to Lowest)
### 1. Shell Script Overrides (Highest Priority)
Shell scripts can override environment variables for platform-specific needs:
```bash
# scripts/common.sh - setup_build_env()
if [ "$BUILD_MODE" = "development" ]; then
export VITE_DEFAULT_ENDORSER_API_SERVER="http://localhost:3000"
export VITE_DEFAULT_PARTNER_API_SERVER="http://localhost:3000"
fi
```
### 2. Platform-Specific Overrides (High Priority)
Platform-specific build scripts can override for mobile development:
```bash
# scripts/build-android.sh
if [ "$BUILD_MODE" = "development" ]; then
export VITE_DEFAULT_ENDORSER_API_SERVER="http://10.0.2.2:3000"
export VITE_DEFAULT_PARTNER_API_SERVER="http://10.0.2.2:3000"
fi
```
### 3. Environment-Specific .env Files (Medium Priority)
Environment-specific `.env` files provide environment-specific defaults:
```bash
# .env.development, .env.test, .env.production
VITE_DEFAULT_ENDORSER_API_SERVER=http://localhost:3000
VITE_DEFAULT_PARTNER_API_SERVER=http://localhost:3000
```
### 4. Fallback .env File (Low Priority)
General `.env` file provides project-wide defaults:
```bash
# .env (if exists)
VITE_DEFAULT_ENDORSER_API_SERVER=http://localhost:3000
```
### 5. app.ts Constants (Lowest Priority - Fallback)
Hardcoded constants in `src/constants/app.ts` provide safety nets:
```typescript
export const DEFAULT_ENDORSER_API_SERVER =
import.meta.env.VITE_DEFAULT_ENDORSER_API_SERVER ||
AppString.PROD_ENDORSER_API_SERVER;
```
## Build Process Flow
### 1. Shell Scripts Set Base Values
```bash
# scripts/common.sh
setup_build_env() {
if [ "$BUILD_MODE" = "development" ]; then
export VITE_DEFAULT_ENDORSER_API_SERVER="http://localhost:3000"
export VITE_DEFAULT_PARTNER_API_SERVER="http://localhost:3000"
fi
}
```
### 2. Platform-Specific Overrides
```bash
# scripts/build-android.sh
if [ "$BUILD_MODE" = "development" ]; then
export VITE_DEFAULT_ENDORSER_API_SERVER="http://10.0.2.2:3000"
export VITE_DEFAULT_PARTNER_API_SERVER="http://10.0.2.2:3000"
fi
```
### 3. Load .env Files
```bash
# scripts/build-web.sh
local env_file=".env.$BUILD_MODE" # .env.development, .env.test, .env.production
if [ -f "$env_file" ]; then
load_env_file "$env_file"
fi
# Fallback to .env
if [ -f ".env" ]; then
load_env_file ".env"
fi
```
### 4. Vite Processes Environment
```typescript
// vite.config.common.mts
dotenv.config(); // Loads .env files
```
### 5. Application Uses Values
```typescript
// src/constants/app.ts
export const DEFAULT_ENDORSER_API_SERVER =
import.meta.env.VITE_DEFAULT_ENDORSER_API_SERVER ||
AppString.PROD_ENDORSER_API_SERVER;
```
## API Configuration Scheme
### Environment Configuration Summary
| Environment | Endorser API (Claims) | Partner API | Image API |
|-------------|----------------------|-------------|-----------|
| **Development** | `http://localhost:3000` | `http://localhost:3000` | `https://image-api.timesafari.app` |
| **Test** | `https://test-api.endorser.ch` | `https://test-partner-api.endorser.ch` | `https://image-api.timesafari.app` |
| **Production** | `https://api.endorser.ch` | `https://partner-api.endorser.ch` | `https://image-api.timesafari.app` |
### Mobile Development Overrides
#### Android Development
- **Emulator**: `http://10.0.2.2:3000` (Android emulator default)
- **Physical Device**: `http://{CUSTOM_IP}:3000` (Custom IP for physical device)
#### iOS Development
- **Simulator**: `http://localhost:3000` (iOS simulator default)
- **Physical Device**: `http://{CUSTOM_IP}:3000` (Custom IP for physical device)
## .env File Structure
### .env.development
```bash
# ==========================================
# DEVELOPMENT ENVIRONMENT CONFIGURATION
# ==========================================
# API Server Configuration:
# - Endorser API (Claims): Local development server
# - Partner API: Local development server (aligned with claims)
# - Image API: Test server (shared for development)
# ==========================================
# Only the variables that start with VITE_ are seen in the application import.meta.env in Vue.
# iOS doesn't like spaces in the app title.
TIME_SAFARI_APP_TITLE="TimeSafari_Dev"
VITE_APP_SERVER=http://localhost:8080
# This is the claim ID for actions in the BVC project, with the JWT ID on this environment (not production).
VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F
# API Servers (Development - Local)
VITE_DEFAULT_ENDORSER_API_SERVER=http://localhost:3000
VITE_DEFAULT_PARTNER_API_SERVER=http://localhost:3000
# Image API (Test server for development)
VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app
# Push Server (disabled for localhost)
#VITE_DEFAULT_PUSH_SERVER... can't be set up with localhost domain
# Feature Flags
VITE_PASSKEYS_ENABLED=true
```
### .env.test
```bash
# ==========================================
# TEST ENVIRONMENT CONFIGURATION
# ==========================================
# API Server Configuration:
# - Endorser API (Claims): Test server
# - Partner API: Test server (aligned with claims)
# - Image API: Test server
# ==========================================
# Only the variables that start with VITE_ are seen in the application import.meta.env in Vue.
# iOS doesn't like spaces in the app title.
TIME_SAFARI_APP_TITLE="TimeSafari_Test"
VITE_APP_SERVER=https://test.timesafari.app
# This is the claim ID for actions in the BVC project, with the JWT ID on this environment (not production).
VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F
# API Servers (Test Environment)
VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch
VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch
# Image API (Test server)
VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app
# Push Server (Test)
VITE_DEFAULT_PUSH_SERVER=https://test.timesafari.app
# Feature Flags
VITE_PASSKEYS_ENABLED=true
```
### .env.production
```bash
# ==========================================
# PRODUCTION ENVIRONMENT CONFIGURATION
# ==========================================
# API Server Configuration:
# - Endorser API (Claims): Production server
# - Partner API: Production server (aligned with claims)
# - Image API: Production server
# ==========================================
# Only the variables that start with VITE_ are seen in the application import.meta.env in Vue.
# App Server
VITE_APP_SERVER=https://timesafari.app
# This is the claim ID for actions in the BVC project.
VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01GXYPFF7FA03NXKPYY142PY4H
# API Servers (Production Environment)
VITE_DEFAULT_ENDORSER_API_SERVER=https://api.endorser.ch
VITE_DEFAULT_PARTNER_API_SERVER=https://partner-api.endorser.ch
# Image API (Production server)
VITE_DEFAULT_IMAGE_API_SERVER=https://image-api.timesafari.app
# Push Server (Production)
VITE_DEFAULT_PUSH_SERVER=https://timesafari.app
```
## Key Principles
### 1. API Alignment
- **Partner API** values follow the same pattern as **Claim API** (Endorser API)
- Both APIs use the same environment-specific endpoints
- This ensures consistency across the application
### 2. Platform Flexibility
- Shell scripts can override for platform-specific needs
- Android emulator uses `10.0.2.2:3000`
- iOS simulator uses `localhost:3000`
- Physical devices use custom IP addresses
### 3. Environment Isolation
- Each environment has its own `.env` file
- Test environment uses test APIs
- Development environment uses local APIs
- Production environment uses production APIs
### 4. Safety Nets
- Hardcoded constants in `app.ts` provide fallbacks
- Multiple layers of configuration prevent failures
- Clear precedence order ensures predictable behavior
## Usage Examples
### Development Build
```bash
# Uses .env.development + shell script overrides
npm run build:web -- --mode development
```
### Test Build
```bash
# Uses .env.test + shell script overrides
npm run build:web -- --mode test
```
### Production Build
```bash
# Uses .env.production + shell script overrides
npm run build:web -- --mode production
```
### Android Development
```bash
# Uses .env.development + Android-specific overrides
./scripts/build-android.sh --dev
```
### iOS Development
```bash
# Uses .env.development + iOS-specific overrides
./scripts/build-ios.sh --dev
```
## Troubleshooting
### Environment Variable Debugging
```bash
# Show current environment variables
./scripts/build-web.sh --env
# Check specific variable
echo $VITE_DEFAULT_ENDORSER_API_SERVER
```
### Common Issues
1. **Wrong API Server**: Check if shell script overrides are correct
2. **Missing .env File**: Ensure environment-specific .env file exists
3. **Platform-Specific Issues**: Verify platform overrides in build scripts
4. **Vite Not Loading**: Check if `dotenv.config()` is called
### Validation
```bash
# Validate environment configuration
npm run test-env
```
## Best Practices
1. **Always use environment-specific .env files** for different environments
2. **Keep shell script overrides minimal** and platform-specific
3. **Document API alignment** in .env file headers
4. **Use hardcoded fallbacks** in `app.ts` for safety
5. **Test all environments** before deployment
6. **Validate configuration** with test scripts
## Related Documentation
- [Build System Overview](../build-system/README.md)
- [Android Custom API IP](../platforms/android-custom-api-ip.md)
- [API Configuration](../api-configuration.md)
- [Environment Setup](../environment-setup.md)

322
docs/build-system/platforms/android-custom-api-ip.md

@ -0,0 +1,322 @@
# Mobile Custom API IP Configuration
**Author**: Matthew Raymer
**Date**: 2025-01-27
**Status**: ✅ **COMPLETE** - Custom API IP support for physical device development
## Overview
When deploying TimeSafari to physical Android devices during development, you may need to specify a custom IP address for the claim API server. This is necessary because physical devices cannot access `localhost` or `10.0.2.2` (Android emulator IP) to reach your local development server.
## Problem
During mobile development:
- **Android Emulator**: Uses `10.0.2.2:3000` to access host machine's localhost (Android emulator default)
- **iOS Simulator**: Uses `localhost:3000` to access host machine's localhost (iOS simulator default)
- **Physical Devices**: Cannot access `localhost` or `10.0.2.2` - needs actual IP address for network access
## Solution
The mobile build system uses platform-appropriate defaults and supports specifying a custom IP address for the claim API server when building for physical devices:
- **Android**: Defaults to `10.0.2.2:3000` for emulator development
- **iOS**: Uses Capacitor default (`localhost:3000`) for simulator development
## Usage
### Command Line Usage
```bash
# Android - Default behavior (uses 10.0.2.2 for emulator)
./scripts/build-android.sh --dev
# Android - Custom IP for physical device
./scripts/build-android.sh --dev --api-ip 192.168.1.100
# iOS - Default behavior (uses localhost for simulator)
./scripts/build-ios.sh --dev
# iOS - Custom IP for physical device
./scripts/build-ios.sh --dev --api-ip 192.168.1.100
# Test environment with custom IP
./scripts/build-android.sh --test --api-ip 192.168.1.100
./scripts/build-ios.sh --test --api-ip 192.168.1.100
# Build and auto-run with custom IP
./scripts/build-android.sh --dev --api-ip 192.168.1.100 --auto-run
./scripts/build-ios.sh --dev --api-ip 192.168.1.100 --auto-run
```
### NPM Scripts
```bash
# Android - Default development build (uses 10.0.2.2 for emulator)
npm run build:android:dev
# Android - Development build with custom IP (requires IP parameter)
npm run build:android:dev:custom 192.168.1.100
# iOS - Default development build (uses localhost for simulator)
npm run build:ios:dev
# iOS - Development build with custom IP (requires IP parameter)
npm run build:ios:dev:custom 192.168.1.100
# Test builds with custom IP (requires IP parameter)
npm run build:android:test:custom 192.168.1.100
npm run build:ios:test:custom 192.168.1.100
# Development build + auto-run with custom IP
npm run build:android:dev:run:custom 192.168.1.100
npm run build:ios:dev:run:custom 192.168.1.100
# Test build + auto-run with custom IP
npm run build:android:test:run:custom 192.168.1.100
npm run build:ios:test:run:custom 192.168.1.100
```
## Examples
### Scenario 1: Development on Simulator/Emulator (Default)
```bash
# Android - Default behavior - uses 10.0.2.2 for emulator
npm run build:android:dev
# iOS - Default behavior - uses localhost for simulator
npm run build:ios:dev
# Build and immediately run on simulator/emulator
npm run build:android:dev:run
npm run build:ios:dev:run
```
### Scenario 2: Development on Physical Device
```bash
# Your development server is running on 192.168.1.50:3000
npm run build:android:dev:custom 192.168.1.50
npm run build:ios:dev:custom 192.168.1.50
# Build and immediately run on device
npm run build:android:dev:run:custom 192.168.1.50
npm run build:ios:dev:run:custom 192.168.1.50
```
### Scenario 3: Testing on Physical Device
```bash
# Your test server is running on 192.168.1.75:3000
npm run build:android:test:custom 192.168.1.75
npm run build:ios:test:custom 192.168.1.75
# Build and immediately run on device
npm run build:android:test:run:custom 192.168.1.75
npm run build:ios:test:run:custom 192.168.1.75
```
### Scenario 4: Direct Script Usage
```bash
# Default behavior (uses platform-appropriate defaults)
./scripts/build-android.sh --dev --studio
./scripts/build-ios.sh --dev --studio
# Custom IP for physical device
./scripts/build-android.sh --dev --api-ip 192.168.1.100 --studio
./scripts/build-ios.sh --dev --api-ip 192.168.1.100 --studio
```
## How It Works
### Environment Variable Override
The build system handles API server configuration as follows:
1. **Android default**: Uses Android emulator default (`http://10.0.2.2:3000`)
2. **iOS default**: Uses Capacitor default (`http://localhost:3000`)
3. **Custom IP specified**: Overrides with `http://<custom-ip>:3000` for physical device development
4. **Maintains other APIs**: Image and Partner APIs remain at production URLs
5. **Logs the configuration**: Shows which IP is being used in build logs
### Build Process
```bash
# Development mode with Android emulator default (10.0.2.2)
export VITE_DEFAULT_ENDORSER_API_SERVER="http://10.0.2.2:3000"
export VITE_DEFAULT_PARTNER_API_SERVER="http://10.0.2.2:3000"
npm run build:capacitor -- --mode development
# Development mode with iOS simulator default (localhost)
export VITE_DEFAULT_ENDORSER_API_SERVER="http://localhost:3000"
export VITE_DEFAULT_PARTNER_API_SERVER="http://localhost:3000"
npm run build:capacitor -- --mode development
# Development mode with custom IP
export VITE_DEFAULT_ENDORSER_API_SERVER="http://192.168.1.100:3000"
export VITE_DEFAULT_PARTNER_API_SERVER="http://192.168.1.100:3000"
npm run build:capacitor -- --mode development
```
### Default Behavior
- **Android (no `--api-ip`)**: Uses Android emulator default (`10.0.2.2:3000`)
- **iOS (no `--api-ip`)**: Uses Capacitor default (`localhost:3000`)
- **Custom IP specified**: Uses provided IP address for physical device development
- **Invalid IP format**: Build will fail with clear error message
- **Network unreachable**: App will show connection errors at runtime
## Finding Your IP Address
### On Linux/macOS
```bash
# Find your local IP address
ifconfig | grep "inet " | grep -v 127.0.0.1
# or
ip addr show | grep "inet " | grep -v 127.0.0.1
```
### On Windows
```bash
# Find your local IP address
ipconfig | findstr "IPv4"
```
### Common Network Patterns
- **Home WiFi**: Usually `192.168.1.x` or `192.168.0.x`
- **Office Network**: May be `10.x.x.x` or `172.16.x.x`
- **Mobile Hotspot**: Often `192.168.43.x`
## Troubleshooting
### Common Issues
#### 1. Device Cannot Connect to API
```bash
# Check if your IP is accessible
ping 192.168.1.100
# Check if port 3000 is open
telnet 192.168.1.100 3000
```
#### 2. Build Fails with Invalid IP
```bash
# Ensure IP format is correct
./scripts/build-android.sh --dev --api-ip 192.168.1.100 # ✅ Correct
./scripts/build-android.sh --dev --api-ip localhost # ❌ Wrong
```
#### 3. Firewall Blocking Connection
```bash
# Check firewall settings
sudo ufw status # Ubuntu/Debian
sudo firewall-cmd --list-all # CentOS/RHEL
```
### Debug Mode
```bash
# Enable verbose logging
./scripts/build-android.sh --dev --api-ip 192.168.1.100 --verbose
```
## Best Practices
### 1. Use Consistent IP Addresses
```bash
# Create aliases for common development scenarios
alias build-dev="npm run build:android:dev:custom 192.168.1.100"
alias build-test="npm run build:android:test:custom 192.168.1.100"
```
### 2. Document Your Setup
```bash
# Create a development setup file
echo "DEV_API_IP=192.168.1.100" > .env.development
echo "TEST_API_IP=192.168.1.100" >> .env.development
```
### 3. Network Security
- Ensure your development server is only accessible on your local network
- Use HTTPS in production environments
- Consider VPN for remote development scenarios
### 4. Team Development
```bash
# Share IP configuration with team
# Add to .env.example
DEV_API_IP=192.168.1.100
TEST_API_IP=192.168.1.100
```
## Integration with CI/CD
### Environment Variables
```yaml
# Example CI/CD configuration
variables:
DEV_API_IP: "192.168.1.100"
TEST_API_IP: "192.168.1.100"
build:
script:
- npm run build:android:dev:custom $DEV_API_IP
```
### Automated Testing
```bash
# Test with different IP configurations
npm run build:android:test:custom 192.168.1.100
npm run build:android:test:custom 10.0.0.100
```
## Migration from Legacy
### Previous Workarounds
Before this feature, developers had to:
1. Manually edit environment files
2. Use different build configurations
3. Modify source code for IP addresses
### New Approach
```bash
# Simple one-liner
npm run build:android:dev:custom 192.168.1.100
```
## Future Enhancements
### Planned Features
1. **IP Validation**: Automatic IP format validation
2. **Network Discovery**: Auto-detect available IP addresses
3. **Port Configuration**: Support for custom ports
4. **Multiple APIs**: Support for custom IPs for all API endpoints
### Integration Opportunities
1. **Docker Integration**: Automatic IP detection in containerized environments
2. **Network Profiles**: Save and reuse common network configurations
3. **Hot Reload**: Automatic rebuild when IP changes
---
**Status**: Complete and ready for production use
**Last Updated**: 2025-01-27
**Version**: 1.0
**Maintainer**: Matthew Raymer

10
package.json

@ -41,6 +41,10 @@
"build:ios:sync": "./scripts/build-ios.sh --sync", "build:ios:sync": "./scripts/build-ios.sh --sync",
"build:ios:assets": "./scripts/build-ios.sh --assets", "build:ios:assets": "./scripts/build-ios.sh --assets",
"build:ios:deploy": "./scripts/build-ios.sh --deploy", "build:ios:deploy": "./scripts/build-ios.sh --deploy",
"build:ios:dev:custom": "./scripts/build-ios.sh --dev --api-ip",
"build:ios:test:custom": "./scripts/build-ios.sh --test --api-ip",
"build:ios:dev:run:custom": "./scripts/build-ios.sh --dev --api-ip --auto-run",
"build:ios:test:run:custom": "./scripts/build-ios.sh --test --api-ip --auto-run",
"build:web": "./scripts/build-web.sh", "build:web": "./scripts/build-web.sh",
"build:web:dev": "./scripts/build-web.sh --dev", "build:web:dev": "./scripts/build-web.sh --dev",
"build:web:test": "./scripts/build-web.sh --test", "build:web:test": "./scripts/build-web.sh --test",
@ -104,7 +108,11 @@
"build:android:clean": "./scripts/build-android.sh --clean", "build:android:clean": "./scripts/build-android.sh --clean",
"build:android:sync": "./scripts/build-android.sh --sync", "build:android:sync": "./scripts/build-android.sh --sync",
"build:android:assets": "./scripts/build-android.sh --assets", "build:android:assets": "./scripts/build-android.sh --assets",
"build:android:deploy": "./scripts/build-android.sh --deploy" "build:android:deploy": "./scripts/build-android.sh --deploy",
"build:android:dev:custom": "./scripts/build-android.sh --dev --api-ip",
"build:android:test:custom": "./scripts/build-android.sh --test --api-ip",
"build:android:dev:run:custom": "./scripts/build-android.sh --dev --api-ip --auto-run",
"build:android:test:run:custom": "./scripts/build-android.sh --test --api-ip --auto-run"
}, },
"dependencies": { "dependencies": {
"@capacitor-community/electron": "^5.0.1", "@capacitor-community/electron": "^5.0.1",

37
scripts/build-android.sh

@ -60,12 +60,16 @@ SYNC_ONLY=false
ASSETS_ONLY=false ASSETS_ONLY=false
DEPLOY_APP=false DEPLOY_APP=false
AUTO_RUN=false AUTO_RUN=false
CUSTOM_API_IP=""
# Function to parse Android-specific arguments # Function to parse Android-specific arguments
parse_android_args() { parse_android_args() {
local args=("$@") local args=("$@")
local i=0
while [ $i -lt ${#args[@]} ]; do
local arg="${args[$i]}"
for arg in "${args[@]}"; do
case $arg in case $arg in
--dev|--development) --dev|--development)
BUILD_MODE="development" BUILD_MODE="development"
@ -106,6 +110,18 @@ parse_android_args() {
--auto-run) --auto-run)
AUTO_RUN=true AUTO_RUN=true
;; ;;
--api-ip)
if [ $((i + 1)) -lt ${#args[@]} ]; then
CUSTOM_API_IP="${args[$((i + 1))]}"
i=$((i + 1)) # Skip the next argument
else
log_error "Error: --api-ip requires an IP address"
exit 1
fi
;;
--api-ip=*)
CUSTOM_API_IP="${arg#*=}"
;;
-h|--help) -h|--help)
print_android_usage print_android_usage
exit 0 exit 0
@ -117,6 +133,7 @@ parse_android_args() {
log_warn "Unknown argument: $arg" log_warn "Unknown argument: $arg"
;; ;;
esac esac
i=$((i + 1))
done done
} }
@ -138,6 +155,7 @@ print_android_usage() {
echo " --assets Generate assets only" echo " --assets Generate assets only"
echo " --deploy Deploy APK to connected device" echo " --deploy Deploy APK to connected device"
echo " --auto-run Auto-run app after build" echo " --auto-run Auto-run app after build"
echo " --api-ip <ip> Custom IP address for claim API (defaults to 10.0.2.2)"
echo "" echo ""
echo "Common Options:" echo "Common Options:"
echo " -h, --help Show this help message" echo " -h, --help Show this help message"
@ -151,6 +169,8 @@ print_android_usage() {
echo " $0 --clean # Clean only" echo " $0 --clean # Clean only"
echo " $0 --sync # Sync only" echo " $0 --sync # Sync only"
echo " $0 --deploy # Build and deploy to device" echo " $0 --deploy # Build and deploy to device"
echo " $0 --dev # Dev build with default 10.0.2.2"
echo " $0 --dev --api-ip 192.168.1.100 # Dev build with custom API IP"
echo "" echo ""
} }
@ -166,6 +186,21 @@ log_info "Build type: $BUILD_TYPE"
# Setup environment for Capacitor build # Setup environment for Capacitor build
setup_build_env "capacitor" setup_build_env "capacitor"
# Override API servers for Android development
if [ "$BUILD_MODE" = "development" ]; then
if [ -n "$CUSTOM_API_IP" ]; then
# Use custom IP for physical device development
export VITE_DEFAULT_ENDORSER_API_SERVER="http://${CUSTOM_API_IP}:3000"
export VITE_DEFAULT_PARTNER_API_SERVER="http://${CUSTOM_API_IP}:3000"
log_info "Android development mode: Using custom IP ${CUSTOM_API_IP} for physical device"
else
# Use Android emulator IP (10.0.2.2) for Android development
export VITE_DEFAULT_ENDORSER_API_SERVER="http://10.0.2.2:3000"
export VITE_DEFAULT_PARTNER_API_SERVER="http://10.0.2.2:3000"
log_debug "Android development mode: Using 10.0.2.2 for emulator"
fi
fi
# Setup application directories # Setup application directories
setup_app_directories setup_app_directories

32
scripts/build-ios.sh

@ -22,6 +22,7 @@ SYNC_ONLY=false
ASSETS_ONLY=false ASSETS_ONLY=false
DEPLOY_APP=false DEPLOY_APP=false
AUTO_RUN=false AUTO_RUN=false
CUSTOM_API_IP=""
# Function to print iOS-specific usage # Function to print iOS-specific usage
print_ios_usage() { print_ios_usage() {
@ -41,6 +42,7 @@ print_ios_usage() {
echo " --assets Generate assets only" echo " --assets Generate assets only"
echo " --deploy Deploy app to connected device" echo " --deploy Deploy app to connected device"
echo " --auto-run Auto-run app after build" echo " --auto-run Auto-run app after build"
echo " --api-ip <ip> Custom IP address for claim API (uses Capacitor default)"
echo "" echo ""
echo "Common Options:" echo "Common Options:"
echo " -h, --help Show this help message" echo " -h, --help Show this help message"
@ -54,12 +56,19 @@ print_ios_usage() {
echo " $0 --clean # Clean only" echo " $0 --clean # Clean only"
echo " $0 --sync # Sync only" echo " $0 --sync # Sync only"
echo " $0 --deploy # Build and deploy to device" echo " $0 --deploy # Build and deploy to device"
echo " $0 --dev # Dev build with Capacitor default"
echo " $0 --dev --api-ip 192.168.1.100 # Dev build with custom API IP"
echo "" echo ""
} }
# Function to parse iOS-specific arguments # Function to parse iOS-specific arguments
parse_ios_args() { parse_ios_args() {
for arg in "$@"; do local args=("$@")
local i=0
while [ $i -lt ${#args[@]} ]; do
local arg="${args[$i]}"
case $arg in case $arg in
--dev|--development) --dev|--development)
BUILD_MODE="development" BUILD_MODE="development"
@ -100,6 +109,18 @@ parse_ios_args() {
--auto-run) --auto-run)
AUTO_RUN=true AUTO_RUN=true
;; ;;
--api-ip)
if [ $((i + 1)) -lt ${#args[@]} ]; then
CUSTOM_API_IP="${args[$((i + 1))]}"
i=$((i + 1)) # Skip the next argument
else
log_error "Error: --api-ip requires an IP address"
exit 1
fi
;;
--api-ip=*)
CUSTOM_API_IP="${arg#*=}"
;;
-h|--help) -h|--help)
print_ios_usage print_ios_usage
exit 0 exit 0
@ -111,6 +132,7 @@ parse_ios_args() {
log_warn "Unknown argument: $arg" log_warn "Unknown argument: $arg"
;; ;;
esac esac
i=$((i + 1))
done done
} }
@ -291,6 +313,14 @@ log_info "Build type: $BUILD_TYPE"
# Setup environment for Capacitor build # Setup environment for Capacitor build
setup_build_env "capacitor" setup_build_env "capacitor"
# Override API servers for iOS development when custom IP is specified
if [ "$BUILD_MODE" = "development" ] && [ -n "$CUSTOM_API_IP" ]; then
# Use custom IP for physical device development
export VITE_DEFAULT_ENDORSER_API_SERVER="http://${CUSTOM_API_IP}:3000"
export VITE_DEFAULT_PARTNER_API_SERVER="http://${CUSTOM_API_IP}:3000"
log_info "iOS development mode: Using custom IP ${CUSTOM_API_IP} for physical device"
fi
# Setup application directories # Setup application directories
setup_app_directories setup_app_directories

28
scripts/common.sh

@ -51,10 +51,18 @@ log_step() {
# Function to measure and log execution time # Function to measure and log execution time
measure_time() { measure_time() {
local start_time=$(date +%s) local start_time=$(date +%s)
"$@" if "$@"; then
local end_time=$(date +%s) local end_time=$(date +%s)
local duration=$((end_time - start_time)) local duration=$((end_time - start_time))
log_success "Completed in ${duration} seconds" log_success "Completed in ${duration} seconds"
return 0
else
local exit_code=$?
local end_time=$(date +%s)
local duration=$((end_time - start_time))
log_error "Failed after ${duration} seconds (exit code: ${exit_code})"
return $exit_code
fi
} }
# Function to print section headers # Function to print section headers
@ -197,20 +205,22 @@ setup_build_env() {
# Set API server environment variables based on build mode # Set API server environment variables based on build mode
if [ "$BUILD_MODE" = "development" ]; then if [ "$BUILD_MODE" = "development" ]; then
# For Capacitor development, use localhost by default
# Android builds will override this in build-android.sh
export VITE_DEFAULT_ENDORSER_API_SERVER="http://localhost:3000" export VITE_DEFAULT_ENDORSER_API_SERVER="http://localhost:3000"
export VITE_DEFAULT_PARTNER_API_SERVER="http://localhost:3000"
log_debug "Development mode: Using localhost for Endorser and Partner APIs"
export VITE_DEFAULT_IMAGE_API_SERVER="https://image-api.timesafari.app" export VITE_DEFAULT_IMAGE_API_SERVER="https://image-api.timesafari.app"
export VITE_DEFAULT_PARTNER_API_SERVER="https://partner-api.endorser.ch"
log_debug "Development mode: Using localhost for Endorser API, production for Image/Partner APIs"
elif [ "$BUILD_MODE" = "test" ]; then elif [ "$BUILD_MODE" = "test" ]; then
export VITE_DEFAULT_ENDORSER_API_SERVER="https://test-api.endorser.ch" export VITE_DEFAULT_ENDORSER_API_SERVER="https://test-api.endorser.ch"
export VITE_DEFAULT_PARTNER_API_SERVER="https://test-partner-api.endorser.ch"
log_debug "Test mode: Using test Endorser and Partner APIs"
export VITE_DEFAULT_IMAGE_API_SERVER="https://image-api.timesafari.app" export VITE_DEFAULT_IMAGE_API_SERVER="https://image-api.timesafari.app"
export VITE_DEFAULT_PARTNER_API_SERVER="https://partner-api.endorser.ch"
log_debug "Test mode: Using test Endorser API, production for Image/Partner APIs"
elif [ "$BUILD_MODE" = "production" ]; then elif [ "$BUILD_MODE" = "production" ]; then
export VITE_DEFAULT_ENDORSER_API_SERVER="https://api.endorser.ch" export VITE_DEFAULT_ENDORSER_API_SERVER="https://api.endorser.ch"
export VITE_DEFAULT_IMAGE_API_SERVER="https://image-api.timesafari.app"
export VITE_DEFAULT_PARTNER_API_SERVER="https://partner-api.endorser.ch" export VITE_DEFAULT_PARTNER_API_SERVER="https://partner-api.endorser.ch"
log_debug "Production mode: Using production API servers" log_debug "Production mode: Using production API servers"
export VITE_DEFAULT_IMAGE_API_SERVER="https://image-api.timesafari.app"
fi fi
# Log environment setup # Log environment setup

15
src/libs/util.ts

@ -35,17 +35,6 @@ import { IIdentifier } from "@veramo/core";
import { DEFAULT_ROOT_DERIVATION_PATH } from "./crypto"; import { DEFAULT_ROOT_DERIVATION_PATH } from "./crypto";
// Consolidate this with src/utils/PlatformServiceMixin._parseJsonField // Consolidate this with src/utils/PlatformServiceMixin._parseJsonField
function parseJsonField<T>(value: unknown, defaultValue: T): T {
if (typeof value === "string") {
try {
return JSON.parse(value);
} catch {
return defaultValue;
}
}
return (value as T) || defaultValue;
}
function mapQueryResultToValues( function mapQueryResultToValues(
record: { columns: string[]; values: unknown[][] } | undefined, record: { columns: string[]; values: unknown[][] } | undefined,
): Array<Record<string, unknown>> { ): Array<Record<string, unknown>> {
@ -806,7 +795,7 @@ export const contactToCsvLine = (contact: Contact): string => {
// Handle contactMethods array by stringifying it // Handle contactMethods array by stringifying it
const contactMethodsStr = contact.contactMethods const contactMethodsStr = contact.contactMethods
? escapeField(JSON.stringify(parseJsonField(contact.contactMethods, []))) ? escapeField(JSON.stringify(contact.contactMethods))
: ""; : "";
const fields = [ const fields = [
@ -918,7 +907,7 @@ export const contactsToExportJson = (contacts: Contact[]): DatabaseExport => {
contact, contact,
); );
exContact.contactMethods = contact.contactMethods exContact.contactMethods = contact.contactMethods
? JSON.stringify(parseJsonField(contact.contactMethods, [])) ? JSON.stringify(contact.contactMethods)
: undefined; : undefined;
return exContact; return exContact;
}); });

6
src/test/PlatformServiceMixinTest.vue

@ -11,7 +11,7 @@
class="px-4 py-2 rounded mr-2 transition-colors" class="px-4 py-2 rounded mr-2 transition-colors"
@click="testInsert" @click="testInsert"
> >
Test Insert Test Contact Insert
</button> </button>
<button <button
:class=" :class="
@ -22,7 +22,7 @@
class="px-4 py-2 rounded mr-2 transition-colors" class="px-4 py-2 rounded mr-2 transition-colors"
@click="testUpdate" @click="testUpdate"
> >
Test Update Test Contact Update
</button> </button>
<button <button
:class=" :class="
@ -44,7 +44,7 @@
class="px-4 py-2 rounded mr-2 transition-colors" class="px-4 py-2 rounded mr-2 transition-colors"
@click="testDatabaseStorage" @click="testDatabaseStorage"
> >
Test Database Storage Format Test SearchBox Database Storage -- Beware: Changes Your Search Box
</button> </button>
<button <button
:class=" :class="

14
src/views/AccountViewView.vue

@ -1661,14 +1661,12 @@ export default class AccountViewView extends Vue {
} }
onShareInfo() { onShareInfo() {
// Call the existing logic for sharing info, e.g., open the share dialog // Navigate to QR code sharing page - mobile uses full scan, web uses basic
this.openShareDialog(); if (Capacitor.isNativePlatform()) {
} this.$router.push({ name: "contact-qr-scan-full" });
} else {
// Placeholder for share dialog logic this.$router.push({ name: "contact-qr" });
openShareDialog() { }
// TODO: Implement share dialog logic
this.notify.info("Share dialog not yet implemented.");
} }
onRecheckLimits() { onRecheckLimits() {

22
src/views/ContactQRScanShowView.vue

@ -151,7 +151,6 @@ import { getContactJwtFromJwtUrl } from "../libs/crypto";
import { import {
CONTACT_CSV_HEADER, CONTACT_CSV_HEADER,
CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI, CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI,
generateEndorserJwtUrlForAccount,
register, register,
setVisibilityUtil, setVisibilityUtil,
} from "../libs/endorserServer"; } from "../libs/endorserServer";
@ -162,7 +161,6 @@ import { logger } from "../utils/logger";
import { QRScannerFactory } from "@/services/QRScanner/QRScannerFactory"; import { QRScannerFactory } from "@/services/QRScanner/QRScannerFactory";
import { CameraState } from "@/services/QRScanner/types"; import { CameraState } from "@/services/QRScanner/types";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { Account } from "@/db/tables/accounts";
import { createNotifyHelpers } from "@/utils/notify"; import { createNotifyHelpers } from "@/utils/notify";
import { import {
NOTIFY_QR_INITIALIZATION_ERROR, NOTIFY_QR_INITIALIZATION_ERROR,
@ -547,6 +545,7 @@ export default class ContactQRScanShow extends Vue {
name: contact.name, name: contact.name,
}); });
this.notify.toast( this.notify.toast(
"Submitted",
NOTIFY_QR_REGISTRATION_SUBMITTED.message, NOTIFY_QR_REGISTRATION_SUBMITTED.message,
QR_TIMEOUT_SHORT, QR_TIMEOUT_SHORT,
); );
@ -611,20 +610,15 @@ export default class ContactQRScanShow extends Vue {
} }
async onCopyUrlToClipboard() { async onCopyUrlToClipboard() {
const account = (await libsUtil.retrieveFullyDecryptedAccount( // Copy the CSV format QR code value instead of generating a deep link
this.activeDid,
)) as Account;
const jwtUrl = await generateEndorserJwtUrlForAccount(
account,
this.isRegistered,
this.givenName,
this.profileImageUrl,
true,
);
useClipboard() useClipboard()
.copy(jwtUrl) .copy(this.qrValue)
.then(() => { .then(() => {
this.notify.toast(NOTIFY_QR_URL_COPIED.message, QR_TIMEOUT_MEDIUM); this.notify.toast(
"Copied",
NOTIFY_QR_URL_COPIED.message,
QR_TIMEOUT_MEDIUM,
);
}); });
} }

3
src/views/GiftedDetailsView.vue

@ -189,7 +189,7 @@
{{ {{
recipientDid recipientDid
? "This was given to " + recipientName + "." ? "This was given to " + recipientName + "."
: "No individual benefitted." : "No named individual benefitted."
}} }}
</label> </label>
<font-awesome <font-awesome
@ -626,6 +626,7 @@ export default class GiftedDetails extends Vue {
this.notify.toast( this.notify.toast(
NOTIFY_GIFTED_DETAILS_RECORDING_GIVE.message, NOTIFY_GIFTED_DETAILS_RECORDING_GIVE.message,
undefined,
TIMEOUTS.SHORT, TIMEOUTS.SHORT,
); );

11
src/views/HomeView.vue

@ -1971,17 +1971,6 @@ export default class HomeView extends Vue {
(this.$refs.feedFilters as FeedFilters).open(this.reloadFeedOnChange); (this.$refs.feedFilters as FeedFilters).open(this.reloadFeedOnChange);
} }
/**
* Shows toast notification to user
*
* @internal
* Used for various user notifications
* @param message Message to display
*/
toastUser(message: string) {
this.notify.toast("FYI", message, TIMEOUTS.SHORT);
}
/** /**
* Computes CSS classes for known person icons * Computes CSS classes for known person icons
* *

10
src/views/ProjectsView.vue

@ -638,16 +638,18 @@ export default class ProjectsView extends Vue {
* - Alternative sharing methods for remote users * - Alternative sharing methods for remote users
*/ */
promptForShareMethod() { promptForShareMethod() {
this.notify.confirm( this.$notify(
NOTIFY_CAMERA_SHARE_METHOD.title,
NOTIFY_CAMERA_SHARE_METHOD.text,
{ {
group: "modal",
type: "confirm",
title: NOTIFY_CAMERA_SHARE_METHOD.title,
text: NOTIFY_CAMERA_SHARE_METHOD.text,
onYes: () => this.handleQRCodeClick(), onYes: () => this.handleQRCodeClick(),
onNo: () => this.$router.push({ name: "share-my-contact-info" }), onNo: () => this.$router.push({ name: "share-my-contact-info" }),
yesText: NOTIFY_CAMERA_SHARE_METHOD.yesText, yesText: NOTIFY_CAMERA_SHARE_METHOD.yesText,
noText: NOTIFY_CAMERA_SHARE_METHOD.noText, noText: NOTIFY_CAMERA_SHARE_METHOD.noText,
timeout: TIMEOUTS.MODAL,
}, },
TIMEOUTS.MODAL,
); );
} }

Loading…
Cancel
Save