Compare commits

..

1 Commits

Author SHA1 Message Date
Matthew Raymer
cd32c37cfb fix: standardize app package name to app.timesafari.app
Update package name consistently across all configurations to fix deep linking and app identification issues.

Changes:
- Update appId in package.json build config from "app.timesafari" to "app.timesafari.app"
- Update force-stop commands in test scripts to use correct package name
- Update URL scheme registration in iOS Info.plist to use consistent bundle ID
- Update app identifier fallback values in iOS test script
- Update serve script to use production mode with host flag

Technical Details:
- Standardized package name to "app.timesafari.app" across all configurations
- Fixed ADB commands in test scripts to use correct package identifier
- Updated iOS URL scheme registration to match Android package name
- Added proper NODE_ENV and host configuration for serve command

Benefits:
- Fixes deep linking functionality by using consistent package identifiers
- Ensures correct app identification across Android and iOS platforms
- Improves test reliability with correct package references
- Enables proper cross-platform URL scheme handling
2025-03-25 08:53:24 +00:00
24 changed files with 283 additions and 748 deletions

View File

@@ -442,13 +442,13 @@ mv time-safari/dist time-safari-dist-prev.0 && mv crowd-funder-for-time-pwa/dist
adb shell pm list packages | grep timesafari
# Force stop the app (if it's running)
adb shell am force-stop app.timesafari
adb shell am force-stop app.timesafari.app
# Clear app data (if you don't want to fully uninstall)
adb shell pm clear app.timesafari
adb shell pm clear app.timesafari.app
# Uninstall for all users
adb shell pm uninstall -k --user 0 app.timesafari
adb shell pm uninstall -k --user 0 app.timesafari.app
# Check if app is installed
adb shell pm path app.timesafari
adb shell pm path app.timesafari.app

Binary file not shown.

View File

@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="/favicon.ico">
<title>TimeSafari</title>
<script type="module" crossorigin src="/assets/index-CZMUlUNO.js"></script>
<script type="module" crossorigin src="/assets/index-DHTxVcAY.js"></script>
</head>
<body>
<noscript>

View File

@@ -1,36 +0,0 @@
{
"appId": "app.timesafari.app",
"productName": "TimeSafari",
"directories": {
"output": "dist-electron-packages",
"buildResources": "build"
},
"files": [
"dist-electron/**/*",
"node_modules/**/*",
"package.json",
"src/electron/electron-logger.js"
],
"extraResources": [
{
"from": "src/utils",
"to": "utils",
"filter": ["**/*"]
}
],
"extraMetadata": {
"main": "src/electron/main.js"
},
"linux": {
"target": ["AppImage"],
"category": "Utility",
"maintainer": "TimeSafari Team"
},
"mac": {
"target": ["dmg"],
"category": "public.app-category.productivity"
},
"win": {
"target": ["nsis"]
}
}

27
package-lock.json generated
View File

@@ -7,7 +7,6 @@
"": {
"name": "timesafari",
"version": "0.4.4",
"hasInstallScript": true,
"dependencies": {
"@capacitor/android": "^6.2.0",
"@capacitor/app": "^6.0.0",
@@ -9781,9 +9780,9 @@
}
},
"node_modules/@types/babel__traverse": {
"version": "7.20.7",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
"integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
"version": "7.20.6",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
"integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
"license": "MIT",
"optional": true,
"peer": true,
@@ -15596,9 +15595,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.124",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.124.tgz",
"integrity": "sha512-riELkpDUqBi00gqreV3RIGoowxGrfueEKBd6zPdOk/I8lvuFpBGNkYoHof3zUHbiTBsIU8oxdIIL/WNrAG1/7A==",
"version": "1.5.123",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.123.tgz",
"integrity": "sha512-refir3NlutEZqlKaBLK0tzlVLe5P2wDKS7UQt/3SpibizgsRAPOsqQC3ffw1nlv3ze5gjRQZYHoPymgVZkplFA==",
"devOptional": true,
"license": "ISC"
},
@@ -16022,9 +16021,9 @@
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.2.5",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.5.tgz",
"integrity": "sha512-IKKP8R87pJyMl7WWamLgPkloB16dagPIdd2FjBDbyRYPKo93wS/NbCOPh6gH+ieNLC+XZrhJt/kWj0PS/DFdmg==",
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.4.tgz",
"integrity": "sha512-SFtuYmnhwYCtuCDTKPoK+CEzCnEgKTU2qTLwoCxvrC0MFBTIXo1i6hDYOI4cwHaE5GZtlWmTN3YfucYi7KJwPw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -16040,7 +16039,7 @@
"peerDependencies": {
"@types/eslint": ">=8.0.0",
"eslint": ">=8.0.0",
"eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
"eslint-config-prettier": "*",
"prettier": ">=3.0.0"
},
"peerDependenciesMeta": {
@@ -20867,9 +20866,9 @@
"license": "ISC"
},
"node_modules/luxon": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.0.tgz",
"integrity": "sha512-WE7p0p7W1xji9qxkLYsvcIxZyfP48GuFrWIBQZIsbjCyf65dG1rv4n83HcOyEyhvzxJCrUoObCRNFgRNIQ5KNA==",
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz",
"integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==",
"license": "MIT",
"engines": {
"node": ">=12"

View File

@@ -22,14 +22,14 @@
"check:ios-device": "xcrun xctrace list devices 2>&1 | grep -w 'Booted' || (echo 'No iOS simulator running' && exit 1)",
"clean:electron": "rimraf dist-electron",
"build:pywebview": "vite build --config vite.config.pywebview.mts",
"build:electron": "npm run check:electron && npm run clean:electron && vite build --config vite.config.electron.mts && node scripts/build-electron.js",
"build:electron": "npm run clean:electron && vite build --config vite.config.electron.mts && node scripts/build-electron.js",
"build:capacitor": "vite build --config vite.config.capacitor.mts",
"build:web": "vite build --config vite.config.web.mts",
"electron:dev": "concurrently \"vite --config vite.config.electron.mts\" \"electron .\"",
"electron:dev": "npm run build && electron dist-electron",
"electron:start": "electron dist-electron",
"electron:build-linux": "npm run check:electron && npm run build:electron && electron-builder --linux AppImage",
"electron:build-linux-deb": "npm run check:electron && npm run build:electron && electron-builder --linux deb",
"electron:build-linux-prod": "NODE_ENV=production npm run check:electron &&npm run build:electron && electron-builder --linux AppImage",
"electron:build-linux": "npm run build:electron && electron-builder --linux AppImage",
"electron:build-linux-deb": "npm run build:electron && electron-builder --linux deb",
"electron:build-linux-prod": "NODE_ENV=production npm run build:electron && electron-builder --linux AppImage",
"build:electron-prod": "NODE_ENV=production npm run build:electron",
"pywebview:dev": "vite build --config vite.config.pywebview.mts && .venv/bin/python src/pywebview/main.py",
"pywebview:build": "vite build --config vite.config.pywebview.mts && .venv/bin/python src/pywebview/main.py",
@@ -39,10 +39,7 @@
"fastlane:ios:beta": "cd ios && fastlane beta",
"fastlane:ios:release": "cd ios && fastlane release",
"fastlane:android:beta": "cd android && fastlane beta",
"fastlane:android:release": "cd android && fastlane release",
"check:electron": "node scripts/check-electron-prerequisites.js",
"electron:build": "npm run check:electron && vite build --config vite.config.electron.mts && node scripts/fix-electron-paths.js && electron-builder",
"postinstall": "electron-builder install-app-deps"
"fastlane:android:release": "cd android && fastlane release"
},
"dependencies": {
"@capacitor/android": "^6.2.0",
@@ -158,33 +155,30 @@
},
"main": "./dist-electron/main.js",
"build": {
"appId": "app.timesafari",
"appId": "app.timesafari.app",
"productName": "TimeSafari",
"directories": {
"output": "dist-electron-packages"
},
"files": [
"dist-electron/**/*",
"!dist-electron/node_modules/**/*"
"src/electron/**/*",
"main.js"
],
"directories": {
"output": "dist-electron-packages",
"buildResources": "build-resources"
},
"extraResources": [],
"asar": true,
"asarUnpack": [
"dist-electron/www/assets/**/*"
"extraResources": [
{
"from": "dist-electron",
"to": "."
}
],
"linux": {
"target": ["AppImage"],
"category": "Utility",
"executableName": "TimeSafari"
"target": [
"AppImage",
"deb"
],
"category": "Office",
"icon": "build/icon.png"
},
"mac": {
"category": "public.app-category.productivity"
},
"win": {
"target": ["nsis"]
},
"artifactName": "TimeSafari-${version}-${arch}.${ext}",
"publish": null
"asar": true
}
}

View File

@@ -88,12 +88,6 @@ async function main() {
throw new Error('package.json not found in build directory');
}
// Copy the electron-logger.js file
const loggerSrc = path.join(__dirname, '../src/electron/electron-logger.js');
const loggerDest = path.join(distElectronDir, 'electron-logger.js');
fs.copyFileSync(loggerSrc, loggerDest);
console.log(`Copying src/electron/electron-logger.js to ${loggerDest}`);
console.log('Build completed successfully!');
} catch (error) {
console.error('Build failed:', error);

View File

@@ -1,177 +0,0 @@
#!/usr/bin/env node
/**
* @file check-electron-prerequisites.js
* @description Verifies and installs required dependencies for Electron builds
*
* This script checks if Python's distutils module is available, which is required
* by node-gyp when compiling native Node.js modules during Electron packaging.
* Without distutils, builds will fail with "ModuleNotFoundError: No module named 'distutils'".
*
* The script performs the following actions:
* 1. Checks if Python's distutils module is available
* 2. If missing, offers to install setuptools package which provides distutils
* 3. Attempts installation through pip or pip3
* 4. Provides manual installation instructions if automated installation fails
*
* Usage:
* - Direct execution: node scripts/check-electron-prerequisites.js
* - As npm script: npm run check:electron
* - Before builds: npm run check:electron && electron-builder
*
* Exit codes:
* - 0: All prerequisites are met or were successfully installed
* - 1: Prerequisites are missing and weren't installed
*
* @author [YOUR_NAME]
* @version 1.0.0
* @license MIT
*/
const { execSync } = require('child_process');
const readline = require('readline');
const chalk = require('chalk'); // You might need to add this to your dependencies
console.log(chalk.blue('🔍 Checking Electron build prerequisites...'));
/**
* Checks if Python's distutils module is available
*
* This function attempts to import the distutils module in Python.
* If successful, it means node-gyp will be able to compile native modules.
* If unsuccessful, the Electron build will likely fail when compiling native dependencies.
*
* @returns {boolean} True if distutils is available, false otherwise
*
* @example
* if (checkDistutils()) {
* console.log('Ready to build Electron app');
* }
*/
function checkDistutils() {
try {
// Attempt to import distutils using Python
// We use stdio: 'ignore' to suppress any Python output
execSync('python -c "import distutils"', { stdio: 'ignore' });
console.log(chalk.green('✅ Python distutils is available'));
return true;
} catch (e) {
// This error occurs if either Python is not found or if distutils is missing
console.log(chalk.red('❌ Python distutils is missing'));
return false;
}
}
/**
* Installs the setuptools package which provides distutils
*
* This function attempts to install setuptools using pip or pip3.
* Setuptools is a package that provides the distutils module needed by node-gyp.
* In Python 3.12+, distutils was moved out of the standard library into setuptools.
*
* The function tries multiple installation methods:
* 1. First attempts with pip
* 2. If that fails, tries with pip3
* 3. If both fail, provides instructions for manual installation
*
* @returns {Promise<boolean>} True if installation succeeded, false otherwise
*
* @example
* const success = await installSetuptools();
* if (success) {
* console.log('Ready to proceed with build');
* } else {
* console.log('Please fix prerequisites manually');
* }
*/
async function installSetuptools() {
console.log(chalk.yellow('📦 Attempting to install setuptools...'));
try {
// First try with pip, commonly used on all platforms
execSync('pip install setuptools', { stdio: 'inherit' });
console.log(chalk.green('✅ Successfully installed setuptools'));
return true;
} catch (pipError) {
try {
// If pip fails, try with pip3 (common on Linux distributions)
console.log(chalk.yellow('⚠️ Trying with pip3...'));
execSync('pip3 install setuptools', { stdio: 'inherit' });
console.log(chalk.green('✅ Successfully installed setuptools using pip3'));
return true;
} catch (pip3Error) {
// If both methods fail, provide manual installation guidance
console.log(chalk.red('❌ Failed to install setuptools automatically'));
console.log(chalk.yellow('📝 Please install it manually with:'));
console.log(' pip install setuptools');
console.log(' or');
console.log(' sudo apt install python3-setuptools (on Debian/Ubuntu)');
console.log(' sudo pacman -S python-setuptools (on Arch Linux)');
console.log(' sudo dnf install python3-setuptools (on Fedora)');
console.log(' brew install python-setuptools (on macOS with Homebrew)');
return false;
}
}
}
/**
* Main execution function
*
* This function orchestrates the checking and installation process:
* 1. Checks if distutils is already available
* 2. If not, informs the user and prompts for installation
* 3. Based on user input, attempts to install or exits
*
* The function handles interactive user prompts and orchestrates
* the overall flow of the script.
*
* @returns {Promise<void>}
* @throws Will exit process with code 1 if prerequisites aren't met
*/
async function main() {
// First check if distutils is already available
if (checkDistutils()) {
// All prerequisites are met, exit successfully
process.exit(0);
}
// Inform the user about the missing prerequisite
console.log(chalk.yellow('⚠️ Python distutils is required for Electron builds'));
console.log(chalk.yellow('⚠️ This is needed to compile native modules during the build process'));
// Set up readline interface for user interaction
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// Prompt the user for installation permission
const answer = await new Promise(resolve => {
rl.question(chalk.blue('Would you like to install setuptools now? (y/n) '), resolve);
});
// Clean up readline interface
rl.close();
if (answer.toLowerCase() === 'y') {
// User agreed to installation
const success = await installSetuptools();
if (success) {
// Installation succeeded, exit successfully
process.exit(0);
} else {
// Installation failed, exit with error
process.exit(1);
}
} else {
// User declined installation
console.log(chalk.yellow('⚠️ Build may fail without distutils'));
process.exit(1);
}
}
// Execute the main function and handle any uncaught errors
main().catch(error => {
console.error(chalk.red('Error during prerequisites check:'), error);
process.exit(1);
});

View File

@@ -1,61 +0,0 @@
/**
* Fix path resolution issues in the Electron build
*/
const fs = require('fs');
const path = require('path');
const glob = require('glob');
// Fix asset paths in HTML file
function fixHtmlPaths() {
const htmlFile = path.join(__dirname, '../dist-electron/index.html');
if (fs.existsSync(htmlFile)) {
let html = fs.readFileSync(htmlFile, 'utf8');
// Convert absolute paths to relative
html = html.replace(/src="\//g, 'src="./');
html = html.replace(/href="\//g, 'href="./');
fs.writeFileSync(htmlFile, html);
console.log('✅ Fixed paths in index.html');
}
}
// Fix asset imports in JS files
function fixJsPaths() {
const jsFiles = glob.sync('dist-electron/assets/*.js');
jsFiles.forEach(file => {
let content = fs.readFileSync(file, 'utf8');
// Replace absolute imports with relative ones
const originalContent = content;
content = content.replace(/["']\/assets\//g, '"./assets/');
if (content !== originalContent) {
fs.writeFileSync(file, content);
console.log(`✅ Fixed paths in ${path.basename(file)}`);
}
});
}
// Add base href to HTML
function addBaseHref() {
const htmlFile = path.join(__dirname, '../dist-electron/index.html');
if (fs.existsSync(htmlFile)) {
let html = fs.readFileSync(htmlFile, 'utf8');
// Add base href if not present
if (!html.includes('<base href=')) {
html = html.replace('</head>', '<base href="./">\n</head>');
fs.writeFileSync(htmlFile, html);
console.log('✅ Added base href to index.html');
}
}
}
// Run all fixes
fixHtmlPaths();
fixJsPaths();
addBaseHref();
console.log('🎉 Electron path fixes completed');

View File

@@ -1,14 +0,0 @@
// This is a placeholder notarize script that does nothing for non-macOS platforms
// Only necessary for macOS app store submissions
exports.default = async function notarizing(context) {
// Only notarize macOS builds
if (context.electronPlatformName !== 'darwin') {
console.log('Skipping notarization for non-macOS platform');
return;
}
// For macOS, we would implement actual notarization here
console.log('This is where macOS notarization would happen');
// We're just returning with no action for non-macOS builds
};

View File

@@ -170,7 +170,7 @@ const executeDeeplink = async (url, description, log) => {
try {
// Stop the app before executing the deep link
execSync('adb shell am force-stop app.timesafari');
execSync('adb shell am force-stop app.timesafari.app');
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1s
execSync(`adb shell am start -W -a android.intent.action.VIEW -d "${url}" -c android.intent.category.BROWSABLE`);
@@ -207,10 +207,6 @@ const runDeeplinkTests = async (log) => {
// Test URLs
const deeplinkTests = [
{
url: `timesafari://claim/${claimDetails.claim_id}`,
description: 'Claim view'
},
{
url: `timesafari://claim-cert/${claimDetails.claim_id}`,
description: 'Claim certificate view'

View File

@@ -800,7 +800,7 @@ const checkAndRegisterUrlScheme = (log) => {
<array>
<dict>
<key>CFBundleURLName</key>
<string>app.timesafari</string>
<string>app.timesafari.app</string>
<key>CFBundleURLSchemes</key>
<array>
<string>timesafari</string>
@@ -855,10 +855,10 @@ const getAppIdentifier = () => {
}
// Default fallback
return 'app.timesafari';
return 'app.timesafari.app';
} catch (error) {
console.error('Error getting app identifier:', error);
return 'app.timesafari'; // Default fallback
return 'app.timesafari.app'; // Default fallback
}
};

View File

@@ -12,22 +12,22 @@
<div class="bg-slate-100 rounded-t-md border border-slate-300 p-3 sm:p-4">
<div class="flex items-center gap-2 mb-6">
<div v-if="record.issuerDid">
<EntityIcon
:entity-id="record.issuerDid"
class="rounded-full bg-white overflow-hidden !size-[2rem] object-cover"
class="rounded size-[3rem] sm:size-[4rem] object-cover"
/>
</div>
<div v-else>
<font-awesome
icon="person-circle-question"
class="text-slate-300 text-[2rem]"
/>
<font-awesome icon="person-circle-question" class="text-slate-300 text-[3rem] sm:text-[4rem]" />
</div>
<div>
<h3 class="font-semibold">
{{ record.issuer.known ? record.issuer.displayName : "" }}
{{
record.issuer.known ? record.issuer.displayName : ""
}}
</h3>
<p class="ms-auto text-xs text-slate-500 italic">
{{ friendlyDate }}
@@ -74,7 +74,7 @@
<EntityIcon
:entity-id="record.agentDid"
:profile-image-url="record.issuer.profileImageUrl"
class="rounded-full bg-slate-100 overflow-hidden !size-[3rem] sm:!size-[4rem]"
class="rounded size-[3rem] sm:size-[4rem]"
/>
</div>
<!-- Unknown Person -->
@@ -135,7 +135,7 @@
<EntityIcon
:entity-id="record.recipientDid"
:profile-image-url="record.receiver.profileImageUrl"
class="rounded-full bg-slate-100 overflow-hidden !size-[3rem] sm:!size-[4rem]"
class="rounded size-[3rem] sm:size-[4rem]"
/>
</div>
<!-- Unknown Person -->

View File

@@ -1,37 +0,0 @@
/**
* Electron-specific logger implementation
*/
const fs = require("fs");
const path = require("path");
const { app } = require("electron");
// Create logs directory if it doesn't exist
const logsDir = path.join(app.getPath("userData"), "logs");
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir, { recursive: true });
}
const logFile = path.join(
logsDir,
`electron-${new Date().toISOString().split("T")[0]}.log`,
);
function log(level, message) {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] [${level}] ${message}\n`;
// Write to log file
fs.appendFileSync(logFile, logMessage);
// Also output to console
// eslint-disable-next-line no-console
console[level.toLowerCase()](message);
}
module.exports = {
info: (message) => log("INFO", message),
warn: (message) => log("WARN", message),
error: (message) => log("ERROR", message),
debug: (message) => log("DEBUG", message),
getLogPath: () => logFile,
};

View File

@@ -1,236 +1,174 @@
const { app, BrowserWindow, session, protocol, dialog } = require("electron");
const { app, BrowserWindow } = require("electron");
const path = require("path");
const fs = require("fs");
const logger = require("../utils/logger");
// Global window reference
let mainWindow = null;
// Check if running in dev mode
const isDev = process.argv.includes("--inspect");
// Debug flags
const isDev = !app.isPackaged;
// Helper for logging
function logDebug(...args) {
// eslint-disable-next-line no-console
console.log("[DEBUG]", ...args);
}
function logError(...args) {
// eslint-disable-next-line no-console
console.error("[ERROR]", ...args);
if (!isDev && mainWindow) {
dialog.showErrorBox("TimeSafari Error", args.join(" "));
}
}
// Get the most appropriate app path
function getAppPath() {
if (app.isPackaged) {
const possiblePaths = [
path.join(process.resourcesPath, "app.asar", "dist-electron"),
path.join(process.resourcesPath, "app.asar"),
path.join(process.resourcesPath, "app"),
app.getAppPath(),
];
for (const testPath of possiblePaths) {
const testFile = path.join(testPath, "www", "index.html");
if (fs.existsSync(testFile)) {
logDebug(`Found valid app path: ${testPath}`);
return testPath;
}
}
logError("Could not find valid app path");
return path.join(process.resourcesPath, "app.asar"); // Default fallback
} else {
return __dirname;
}
}
// Create the browser window
function createWindow() {
logDebug("Creating window with paths:");
logDebug("- process.resourcesPath:", process.resourcesPath);
logDebug("- app.getAppPath():", app.getAppPath());
logDebug("- __dirname:", __dirname);
// Add before createWindow function
const preloadPath = path.join(__dirname, "preload.js");
logger.log("Checking preload path:", preloadPath);
logger.log("Preload exists:", fs.existsSync(preloadPath));
// Create the browser window
mainWindow = new BrowserWindow({
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
contextIsolation: true,
nodeIntegration: false,
contextIsolation: true,
webSecurity: true,
allowRunningInsecureContent: false,
preload: path.join(__dirname, "preload.js"),
},
});
// Fix root file paths - replaces all protocol handling
protocol.interceptFileProtocol("file", (request, callback) => {
let urlPath = request.url.substr(7); // Remove 'file://' prefix
urlPath = decodeURIComponent(urlPath); // Handle special characters
// Always open DevTools for now
mainWindow.webContents.openDevTools();
// Debug all asset requests
if (
urlPath.includes("assets/") ||
urlPath.endsWith(".js") ||
urlPath.endsWith(".css") ||
urlPath.endsWith(".html")
) {
logDebug(`Intercepted request for: ${urlPath}`);
}
// Intercept requests to fix asset paths
mainWindow.webContents.session.webRequest.onBeforeRequest(
{
urls: [
"file://*/*/assets/*",
"file://*/assets/*",
"file:///assets/*", // Catch absolute paths
"<all_urls>", // Catch all URLs as a fallback
],
},
(details, callback) => {
let url = details.url;
// Fix paths for files at root like registerSW.js or manifest.webmanifest
if (
urlPath.endsWith("registerSW.js") ||
urlPath.endsWith("manifest.webmanifest") ||
urlPath.endsWith("sw.js")
) {
const appBasePath = getAppPath();
const filePath = path.join(appBasePath, "www", path.basename(urlPath));
if (fs.existsSync(filePath)) {
logDebug(`Serving ${urlPath} from ${filePath}`);
return callback({ path: filePath });
} else {
// For service worker, provide empty content to avoid errors
if (urlPath.endsWith("registerSW.js") || urlPath.endsWith("sw.js")) {
logDebug(`Providing empty SW file for ${urlPath}`);
// Create an empty JS file content that does nothing
const tempFile = path.join(
app.getPath("temp"),
path.basename(urlPath),
);
fs.writeFileSync(
tempFile,
"// Service workers disabled in Electron\n",
);
return callback({ path: tempFile });
}
}
}
// Handle assets paths that might be requested from root
if (urlPath.startsWith("/assets/") || urlPath === "/assets") {
const appBasePath = getAppPath();
const filePath = path.join(appBasePath, "www", urlPath);
logDebug(`Redirecting ${urlPath} to ${filePath}`);
return callback({ path: filePath });
}
// Handle assets paths that are missing the www folder
if (urlPath.includes("/assets/")) {
const appBasePath = getAppPath();
const relativePath = urlPath.substring(urlPath.indexOf("/assets/"));
const filePath = path.join(appBasePath, "www", relativePath);
if (fs.existsSync(filePath)) {
logDebug(`Fixing asset path ${urlPath} to ${filePath}`);
return callback({ path: filePath });
}
}
// For all other paths, just pass them through
callback({ path: urlPath });
});
// Set up CSP headers - more permissive in dev mode
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
"Content-Security-Policy": [
isDev
? "default-src 'self' file:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob: https://*; connect-src 'self' https://*"
: "default-src 'self' file:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob: https://image.timesafari.app https://*.americancloud.com; connect-src 'self' https://api.timesafari.app https://api.endorser.ch https://test-api.endorser.ch https://fonts.googleapis.com",
],
},
});
});
// Load the index.html with modifications
try {
const appPath = getAppPath();
const wwwFolder = path.join(appPath, "www");
const indexPath = path.join(wwwFolder, "index.html");
logDebug("Loading app from:", indexPath);
// Check if the file exists
if (fs.existsSync(indexPath)) {
// Read and modify index.html to disable service worker
let indexContent = fs.readFileSync(indexPath, "utf8");
// 1. Add base tag for proper path resolution
indexContent = indexContent.replace(
"<head>",
`<head>\n <base href="file://${wwwFolder}/">`,
);
// 2. Disable service worker registration by replacing the script
if (indexContent.includes("registerSW.js")) {
indexContent = indexContent.replace(
/<script src="registerSW\.js"><\/script>/,
"<script>/* Service worker disabled in Electron */</script>",
);
// Handle paths that don't start with file://
if (!url.startsWith("file://") && url.includes("/assets/")) {
url = `file://${path.join(__dirname, "www", url)}`;
}
// Create a temp file with modified content
const tempDir = app.getPath("temp");
const tempIndexPath = path.join(tempDir, "timesafari-index.html");
fs.writeFileSync(tempIndexPath, indexContent);
// Handle absolute paths starting with /assets/
if (url.includes("/assets/") && !url.includes("/www/assets/")) {
const baseDir = url.includes("dist-electron")
? url.substring(
0,
url.indexOf("/dist-electron") + "/dist-electron".length,
)
: `file://${__dirname}`;
const assetPath = url.split("/assets/")[1];
const newUrl = `${baseDir}/www/assets/${assetPath}`;
callback({ redirectURL: newUrl });
return;
}
// Load the modified index.html
mainWindow.loadFile(tempIndexPath).catch((err) => {
logError("Failed to load via loadFile:", err);
callback({}); // No redirect for other URLs
},
);
// Fallback to direct URL loading
mainWindow.loadURL(`file://${tempIndexPath}`).catch((err2) => {
logError("Both loading methods failed:", err2);
mainWindow.loadURL(
"data:text/html,<h1>Error: Failed to load TimeSafari</h1><p>Please contact support.</p>",
);
});
});
} else {
logError(`Index file not found at: ${indexPath}`);
mainWindow.loadURL(
"data:text/html,<h1>Error: Cannot find application</h1><p>index.html not found</p>",
);
}
} catch (err) {
logError("Failed to load app:", err);
if (isDev) {
// Debug info
logger.log("Debug Info:");
logger.log("Running in dev mode:", isDev);
logger.log("App is packaged:", app.isPackaged);
logger.log("Process resource path:", process.resourcesPath);
logger.log("App path:", app.getAppPath());
logger.log("__dirname:", __dirname);
logger.log("process.cwd():", process.cwd());
}
// Open DevTools in development
const indexPath = path.join(__dirname, "www", "index.html");
if (isDev) {
logger.log("Loading index from:", indexPath);
logger.log("www path:", path.join(__dirname, "www"));
logger.log("www assets path:", path.join(__dirname, "www", "assets"));
}
if (!fs.existsSync(indexPath)) {
logger.error(`Index file not found at: ${indexPath}`);
throw new Error("Index file not found");
}
// Add CSP headers to allow API connections
mainWindow.webContents.session.webRequest.onHeadersReceived(
(details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
"Content-Security-Policy": [
"default-src 'self';" +
"connect-src 'self' https://api.endorser.ch https://*.timesafari.app;" +
"img-src 'self' data: https: blob:;" +
"script-src 'self' 'unsafe-inline' 'unsafe-eval';" +
"style-src 'self' 'unsafe-inline';" +
"font-src 'self' data:;",
],
},
});
},
);
// Load the index.html
mainWindow
.loadFile(indexPath)
.then(() => {
logger.log("Successfully loaded index.html");
if (isDev) {
mainWindow.webContents.openDevTools();
logger.log("DevTools opened - running in dev mode");
}
})
.catch((err) => {
logger.error("Failed to load index.html:", err);
logger.error("Attempted path:", indexPath);
});
// Listen for console messages from the renderer
mainWindow.webContents.on("console-message", (_event, level, message) => {
logger.log("Renderer Console:", message);
});
// Add right after creating the BrowserWindow
mainWindow.webContents.on(
"did-fail-load",
(event, errorCode, errorDescription) => {
logger.error("Page failed to load:", errorCode, errorDescription);
},
);
mainWindow.webContents.on("preload-error", (event, preloadPath, error) => {
logger.error("Preload script error:", preloadPath, error);
});
mainWindow.webContents.on(
"console-message",
(event, level, message, line, sourceId) => {
logger.log("Renderer Console:", line, sourceId, message);
},
);
// Enable remote debugging when in dev mode
if (isDev) {
mainWindow.webContents.openDevTools();
}
mainWindow.on("closed", () => {
mainWindow = null;
});
}
// App lifecycle events
app.whenReady().then(() => {
logDebug(`Starting TimeSafari v${app.getVersion()}`);
// Skip the service worker registration for file:// protocol
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true";
createWindow();
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
// Handle app ready
app.whenReady().then(createWindow);
// Handle all windows closed
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
if (process.platform !== "darwin") {
app.quit();
}
});
// Handle uncaught exceptions
process.on("uncaughtException", (error) => {
logError("Uncaught Exception:", error);
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// Handle any errors
process.on("uncaughtException", (error) => {
logger.error("Uncaught Exception:", error);
});

View File

@@ -1,95 +1,78 @@
const { contextBridge, ipcRenderer } = require("electron");
// Safety wrapper for logging
function safeLog(message) {
try {
// eslint-disable-next-line no-console
console.log("[Preload]", message);
} catch (e) {
// Silent fail for logging
}
}
const logger = {
log: (message, ...args) => {
if (process.env.NODE_ENV !== "production") {
/* eslint-disable no-console */
console.log(message, ...args);
/* eslint-enable no-console */
}
},
warn: (message, ...args) => {
if (process.env.NODE_ENV !== "production") {
/* eslint-disable no-console */
console.warn(message, ...args);
/* eslint-enable no-console */
}
},
error: (message, ...args) => {
/* eslint-disable no-console */
console.error(message, ...args); // Errors should always be logged
/* eslint-enable no-console */
},
};
// Initialize
safeLog("Preload script starting...");
// Use a more direct path resolution approach
const getPath = (pathType) => {
switch (pathType) {
case "userData":
return (
process.env.APPDATA ||
(process.platform === "darwin"
? `${process.env.HOME}/Library/Application Support`
: `${process.env.HOME}/.local/share`)
);
case "home":
return process.env.HOME;
case "appPath":
return process.resourcesPath;
default:
return "";
}
};
logger.log("Preload script starting...");
try {
// Mock service worker registration to prevent errors
if (window.navigator) {
// Override the service worker registration to return a fake promise that resolves with nothing
window.navigator.serviceWorker = {
register: () => Promise.resolve({}),
getRegistration: () => Promise.resolve(null),
ready: Promise.resolve({}),
};
}
// Safely expose specific APIs to the renderer process
contextBridge.exposeInMainWorld("electronAPI", {
// Basic flags/info
isElectron: true,
// Path utilities
getPath,
// Disable service worker in Electron
disableServiceWorker: true,
// Logging
log: (message) => {
try {
// eslint-disable-next-line no-console
console.log("[Renderer]", message);
} catch (e) {
// Silence any errors from logging
}
},
// Report errors to main process
reportError: (error) => {
try {
ipcRenderer.send("app-error", error.toString());
} catch (e) {
// eslint-disable-next-line no-console
console.error("Failed to report error to main process", e);
}
},
// Safe path handling helper (no Node modules needed)
joinPath: (...parts) => {
return parts.join("/").replace(/\/\//g, "/");
},
// Fix asset URLs
resolveAssetUrl: (assetPath) => {
if (assetPath.startsWith("/assets/")) {
return assetPath; // Already properly formed
}
if (assetPath.startsWith("assets/")) {
return "/" + assetPath; // Add leading slash
}
return assetPath;
},
// Send messages to main process
// IPC functions
send: (channel, data) => {
// Whitelist channels for security
const validChannels = ["app-event", "log-event", "app-error"];
const validChannels = ["toMain"];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
// Receive messages from main process
receive: (channel, func) => {
const validChannels = ["app-notification", "log-response"];
const validChannels = ["fromMain"];
if (validChannels.includes(channel)) {
// Remove old listeners to avoid memory leaks
ipcRenderer.removeAllListeners(channel);
// Add the new listener
ipcRenderer.on(channel, (_, ...args) => func(...args));
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
},
// Environment info
env: {
isElectron: true,
isDev: process.env.NODE_ENV === "development",
},
// Path utilities
getBasePath: () => {
return process.env.NODE_ENV === "development" ? "/" : "./";
},
});
safeLog("Preload script completed successfully");
} catch (err) {
safeLog("Error in preload script: " + err.toString());
logger.log("Preload script completed successfully");
} catch (error) {
logger.error("Error in preload script:", error);
}

View File

@@ -2,66 +2,38 @@
import { register } from "register-service-worker";
// Add debug logging for environment variables
console.log('[ServiceWorker] Environment variables:', {
VITE_PWA_ENABLED: process.env.VITE_PWA_ENABLED,
NODE_ENV: process.env.NODE_ENV,
BASE_URL: process.env.BASE_URL,
CAN_REGISTER: process.env.VITE_PWA_ENABLED === "true" && process.env.NODE_ENV === "production"
});
// Modified condition to handle both string and boolean true
// Only register service worker if explicitly enabled and in production
if (
process.env.VITE_PWA_ENABLED === "true" &&
process.env.NODE_ENV === "production"
) {
console.log('[ServiceWorker] Attempting to register service worker...');
// Use '/' as fallback if BASE_URL is undefined
const baseUrl = process.env.BASE_URL || '/';
register(`${baseUrl}sw.js`, {
register(`${process.env.BASE_URL}sw.js`, {
ready() {
console.log("[ServiceWorker] Service worker is active.");
console.log("Service worker is active.");
},
registered(registration) {
console.log("[ServiceWorker] Service worker has been registered:", registration);
registered() {
console.log("Service worker has been registered.");
},
cached(registration) {
console.log("[ServiceWorker] Content has been cached for offline use:", registration);
cached() {
console.log("Content has been cached for offline use.");
},
updatefound(registration) {
console.log("[ServiceWorker] New content is downloading:", registration);
updatefound() {
console.log("New content is downloading.");
},
updated(registration) {
console.log("[ServiceWorker] New content is available:", registration);
updated() {
console.log("New content is available; please refresh.");
},
offline() {
console.log("[ServiceWorker] No internet connection found. App is running in offline mode.");
console.log(
"No internet connection found. App is running in offline mode.",
);
},
error(error) {
console.error("[ServiceWorker] Error during service worker registration:", error);
console.error("Error during service worker registration:", error);
},
});
} else {
console.log(
"[ServiceWorker] Registration skipped:",
{
enabled: process.env.VITE_PWA_ENABLED === "true",
production: process.env.NODE_ENV === "production",
value: process.env.VITE_PWA_ENABLED,
type: typeof process.env.VITE_PWA_ENABLED
}
"Service worker registration skipped - not enabled or not in production",
);
}
export function registerServiceWorker() {
// Skip service worker registration in Electron
if (window.electronAPI?.isElectron) {
console.log("Running in Electron - skipping service worker registration");
return;
}
// Regular service worker registration for web
if ("serviceWorker" in navigator) {
// ... existing code ...
}
}

View File

@@ -344,6 +344,7 @@ import * as serverUtil from "../libs/endorserServer";
import { logger } from "../utils/logger";
import { GiveRecordWithContactInfo } from "types";
/**
* HomeView - Main view component for the application's home page
*

View File

@@ -8,5 +8,4 @@ web3>=6.0.0 # For Ethereum interaction
eth-utils>=2.1.0 # For Ethereum utilities
pyjwt>=2.8.0 # For JWT operations
cryptography>=42.0.0 # For key format conversion
jwcrypto
setuptools
jwcrypto

View File

@@ -74,7 +74,7 @@ execute_deeplink() {
echo "---"
else
# Stop the app before executing the deep link
adb shell am force-stop app.timesafari
adb shell am force-stop app.timesafari.app
sleep 1 # Give it a moment to fully stop
if adb shell am start -W -a android.intent.action.VIEW \

View File

@@ -16,14 +16,11 @@ export async function createBuildConfig(mode: string) {
const isElectron = mode === "electron";
const isCapacitor = mode === "capacitor";
const isPyWebView = mode === "pywebview";
const isWeb = mode === "web";
// Explicitly set platform
process.env.VITE_PLATFORM = mode;
if (isWeb) {
process.env.VITE_PWA_ENABLED = 'true';
} else {
if (isElectron || isPyWebView || isCapacitor) {
process.env.VITE_PWA_ENABLED = 'false';
}
@@ -45,7 +42,7 @@ export async function createBuildConfig(mode: string) {
define: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.VITE_PLATFORM': JSON.stringify(mode),
'process.env.VITE_PWA_ENABLED': isWeb ? "true" : "false",
'process.env.VITE_PWA_ENABLED': JSON.stringify(!(isElectron || isPyWebView || isCapacitor)),
__dirname: isElectron ? JSON.stringify(process.cwd()) : '""',
},
resolve: {

View File

@@ -24,25 +24,6 @@ export default defineConfig(async () => {
};
}
}
}],
build: {
outDir: 'dist-electron',
emptyOutDir: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'vue-router', 'pinia']
}
}
},
assetsDir: 'assets',
minify: 'terser',
terserOptions: {
compress: {
drop_console: false,
},
},
},
base: './',
}]
});
});

6
vite.config.js Normal file
View File

@@ -0,0 +1,6 @@
export default {
preview: {
host: true,
port: 4173
}
}