Browse Source

WIP: add Electron platform configuration to Capacitor

- Add electron platform section to capacitor.config.json
- Configure deep linking with timesafari:// scheme
- Set up build options for macOS, Windows, and Linux
- Configure output directory and file inclusion
- Add platform-specific build targets (DMG, NSIS, AppImage)
- Support both x64 and arm64 architectures for macOS
- Set appropriate app categories for each platform

This enables building TimeSafari as a native desktop application
using Capacitor's Electron platform while maintaining existing
mobile and web functionality.
streamline-attempt
Matthew Raymer 1 week ago
parent
commit
54e3800037
  1. 15
      -1750838135249.log
  2. 4
      -1750838461020.log
  3. 212
      BUILDING.md
  4. 43
      capacitor.config.json
  5. 3
      index.html
  6. 443
      package-lock.json
  7. 70
      package.json
  8. 19
      scripts/README.md
  9. 82
      scripts/build-electron-linux.sh
  10. 74
      scripts/build-electron-mac.sh
  11. 148
      scripts/build-electron.js
  12. 53
      scripts/build-electron.sh
  13. 11
      scripts/common.sh
  14. 44
      scripts/electron-dev.sh
  15. 35
      scripts/test-env.sh
  16. 174
      src/electron/main.js
  17. 221
      src/electron/main.ts
  18. 91
      src/electron/preload.js
  19. 17
      src/main.electron.ts
  20. 2
      src/main.web.ts
  21. 16
      src/registerServiceWorker.ts
  22. 9
      src/router/index.ts
  23. 5
      src/services/PlatformServiceFactory.ts
  24. 10
      src/services/platforms/CapacitorPlatformService.ts
  25. 358
      src/services/platforms/ElectronPlatformService.ts
  26. 28
      tsconfig.electron.json
  27. 23
      vite.config.common.mts
  28. 154
      vite.config.electron.mts
  29. 31
      vite.config.electron.renderer.mts

15
-1750838135249.log

@ -1,15 +0,0 @@
VM4 sandbox_bundle:2 Unable to load preload script: /home/noone/projects/timesafari/crowd-master/preload.js
(anonymous) @ VM4 sandbox_bundle:2
VM4 sandbox_bundle:2 Error: ENOENT: no such file or directory, open '/home/noone/projects/timesafari/crowd-master/preload.js'
at async open (node:internal/fs/promises:639:25)
at async Object.readFile (node:internal/fs/promises:1246:14)
at async node:electron/js2c/browser_init:2:108714
at async Promise.all (index 0)
at async node:electron/js2c/browser_init:2:108650
at async IpcMainImpl.<anonymous> (node:electron/js2c/browser_init:2:105615)
(anonymous) @ VM4 sandbox_bundle:2
main.electron.ts:1
Failed to load resource: net::ERR_FILE_NOT_FOUND
index.html:1 Uncaught (in promise) TypeError: Failed to fetch dynamically imported module: file:///tmp/.mount_TimeSaGVOt4a/resources/app.asar/dist-electron/www/src/main.electron.ts

4
-1750838461020.log

@ -1,4 +0,0 @@
main.electron.js:1
Failed to load resource: net::ERR_FILE_NOT_FOUND

212
BUILDING.md

@ -9,7 +9,8 @@ For a quick dev environment setup, use [pkgx](https://pkgx.dev).
- Node.js (LTS version recommended)
- npm (comes with Node.js)
- Git
- For desktop builds: Additional build tools based on your OS
- For mobile builds: Android Studio (Android) or Xcode (iOS)
- For desktop builds: Capacitor Electron platform
## Unified Build Scripts
@ -28,8 +29,6 @@ TimeSafari now uses unified build scripts that automatically handle environment
| Script | Purpose | Command |
|--------|---------|---------|
| `electron-dev.sh` | Electron development | `./scripts/electron-dev.sh` |
| `electron-build.sh` | Electron build | `./scripts/build-electron.sh` |
| `capacitor-dev.sh` | Capacitor development | `./scripts/capacitor-dev.sh` |
| `capacitor-build.sh` | Capacitor build | `./scripts/build-capacitor.sh` |
| `web-dev.sh` | Web development | `./scripts/web-dev.sh` |
@ -41,25 +40,22 @@ All scripts automatically set the correct environment variables for their build
| Build Type | VITE_PLATFORM | VITE_PWA_ENABLED | VITE_DISABLE_PWA | NODE_ENV |
|------------|---------------|------------------|------------------|----------|
| `electron` | electron | false | true | production* |
| `capacitor` | capacitor | false | true | - |
| `web` | web | true | false | - |
*NODE_ENV=production only set when production mode is enabled
### CLI Options
All scripts support these options:
```bash
# Show help
./scripts/build-electron.sh --help
./scripts/build-capacitor.sh --help
# Enable verbose logging
./scripts/build-electron.sh --verbose
./scripts/build-capacitor.sh --verbose
# Show environment variables
./scripts/build-electron.sh --env
./scripts/build-capacitor.sh --env
```
## Forks
@ -102,12 +98,14 @@ npx jsr add @nostr/tools
**Reason**: Resolved Vite/Rollup build issues with deep imports
**Before** (npm):
```typescript
import { finalizeEvent } from "nostr-tools/lib/cjs/index.js";
import { accountFromExtendedKey } from "nostr-tools/lib/cjs/nip06.js";
```
**After** (JSR):
```typescript
import { finalizeEvent } from "@nostr/tools";
import { accountFromExtendedKey } from "@nostr/tools/nip06";
@ -210,102 +208,92 @@ VITE_DEFAULT_PUSH_SERVER=https://timesafari.app
VITE_PASSKEYS_ENABLED=true
```
## Desktop Build (Electron)
## Desktop Build (Capacitor Electron)
### Development
### Prerequisites
For development with automatic environment setup:
1. Install Capacitor CLI:
```bash
./scripts/electron-dev.sh
```
```bash
npm install -g @capacitor/cli
```
### Production Build
2. Add Electron platform:
For production builds with automatic environment setup:
```bash
npx cap add electron
```
```bash
./scripts/build-electron.sh
```
### Development
### Linux Packaging
For development with automatic environment setup:
```bash
# Build AppImage (recommended)
./scripts/build-electron-linux.sh
# Build web assets
npm run build:capacitor
# Build .deb package
./scripts/build-electron-linux.sh deb
# Sync with Capacitor
npx cap sync electron
# Build production AppImage
./scripts/build-electron-linux.sh prod
# Open in Electron
npx cap open electron
```
The packaged applications will be in `dist-electron-packages/`:
- AppImage: `dist-electron-packages/TimeSafari-x.x.x.AppImage`
- DEB: `dist-electron-packages/timesafari_x.x.x_amd64.deb`
### Production Build
### macOS Packaging
For production builds:
```bash
# Build standard Mac package
./scripts/build-electron-mac.sh
# Build web assets
npm run build:capacitor
# Build universal package (Intel + Apple Silicon)
./scripts/build-electron-mac.sh universal
```
The packaged applications will be in `dist-electron-packages/`:
- `.app` bundle: `TimeSafari.app`
- `.dmg` installer: `TimeSafari-x.x.x.dmg`
- `.zip` archive: `TimeSafari-x.x.x-mac.zip`
### Code Signing and Notarization (macOS)
# Sync with Capacitor
npx cap sync electron
For public distribution on macOS, you need to code sign and notarize your app:
1. Set up environment variables in `.env` file:
```bash
CSC_LINK=/path/to/your/certificate.p12
CSC_KEY_PASSWORD=your_certificate_password
APPLE_ID=your_apple_id
APPLE_ID_PASSWORD=your_app_specific_password
```
# Build Electron app
npx cap build electron
```
2. Build with signing:
```bash
./scripts/build-electron-mac.sh
```
### Packaging
Capacitor Electron uses electron-builder for packaging. Configure the build in `capacitor.config.json`:
```json
{
"plugins": {
"ElectronBuilder": {
"buildOptions": {
"appId": "app.timesafari.app",
"productName": "TimeSafari",
"directories": {
"output": "dist-electron-packages"
},
"files": [
"dist/**/*",
"electron/**/*"
],
"linux": {
"target": ["AppImage", "deb"],
"category": "Office"
},
"mac": {
"target": ["dmg", "zip"],
"category": "public.app-category.productivity"
},
"win": {
"target": ["nsis", "portable"]
}
}
}
}
}
```
### Running the Packaged App
- **Linux**:
- AppImage: Make executable and run
```bash
chmod +x dist-electron-packages/TimeSafari-*.AppImage
./dist-electron-packages/TimeSafari-*.AppImage
```
- DEB: Install and run
```bash
sudo dpkg -i dist-electron-packages/timesafari_*_amd64.deb
timesafari
```
- **macOS**:
- `.app` bundle: Double-click `TimeSafari.app` in Finder
- `.dmg` installer:
1. Double-click the `.dmg` file
2. Drag the app to your Applications folder
3. Launch from Applications
- `.zip` archive:
1. Extract the `.zip` file
2. Move `TimeSafari.app` to your Applications folder
3. Launch from Applications
Note: If you get a security warning when running the app:
1. Right-click the app
2. Select "Open"
3. Click "Open" in the security dialog
- **Linux**: AppImage files are self-contained executables
- **macOS**: `.app` bundles can be dragged to Applications folder
- **Windows**: `.exe` installers or portable executables
## Mobile Builds (Capacitor)
@ -601,12 +589,12 @@ For iOS deep links, configure the URL scheme in Xcode:
### Common Issues
1. **Environment Variables Not Set**
- Use `--env` flag to check current environment: `./scripts/build-electron.sh --env`
- Use `--env` flag to check current environment: `./scripts/build-capacitor.sh --env`
- Verify `.env` file exists and is properly formatted
- Check script output for environment setup messages
2. **Build Failures**
- Use `--verbose` flag for detailed logging: `./scripts/build-electron.sh --verbose`
- Use `--verbose` flag for detailed logging: `./scripts/build-capacitor.sh --verbose`
- Check prerequisites are installed
- Verify all dependencies are installed: `npm install`
@ -615,14 +603,13 @@ For iOS deep links, configure the URL scheme in Xcode:
- Check file permissions on build directories
4. **Platform-Specific Issues**
- **Linux**: Ensure AppImage dependencies are installed
- **macOS**: Check code signing certificates and entitlements
- **Android**: Verify Android Studio and SDK are properly configured
- **iOS**: Ensure Xcode and certificates are set up correctly
- **Electron**: Check Capacitor Electron platform installation
### Getting Help
- Check script help: `./scripts/build-electron.sh --help`
- Check script help: `./scripts/build-capacitor.sh --help`
- Review script documentation in `scripts/README.md`
- Test environment setup: `./scripts/test-env.sh`
- Test common utilities: `./scripts/test-common.sh`
@ -633,20 +620,47 @@ For iOS deep links, configure the URL scheme in Xcode:
|----------|------|-------------|-----------------|-------|
| `web` | web | true | false | Standard web browser |
| `capacitor` | capacitor | false | true | Mobile app (iOS/Android) |
| `electron` | electron | false | true | Desktop app (Windows/macOS/Linux) |
| `electron` | capacitor | false | true | Desktop app (via Capacitor Electron) |
## Platform Service Architecture
TimeSafari uses a unified platform service architecture that works across all platforms:
### Platform Detection
The `CapacitorPlatformService` automatically detects the platform and adjusts capabilities:
```typescript
getCapabilities(): PlatformCapabilities {
const platform = Capacitor.getPlatform();
const isElectron = platform === "electron";
return {
hasFileSystem: true,
hasCamera: true,
isMobile: !isElectron, // false for Electron, true for mobile
isIOS: platform === "ios",
hasFileDownload: isElectron, // Electron can download files directly
needsFileHandlingInstructions: !isElectron, // Mobile needs instructions
isNativeApp: true,
};
}
```
## Electron Build: CSS Injection
### Unified Database Layer
The Electron build now uses Vite's built-in CSS handling with a custom plugin (`electron-css-injection`) that automatically injects CSS links into the generated `index.html` file. This replaces the previous manual CSS injection script.
All platforms use the same SQLite database through Capacitor plugins:
**Plugin:** `vite.config.electron.mts` - `electron-css-injection` plugin
- **Mobile**: `@capacitor-community/sqlite` plugin
- **Desktop**: Same plugin via Capacitor Electron
- **Web**: IndexedDB fallback with absurd-sql
**Features:**
- Automatically detects and injects CSS files generated by Vite
- Ensures proper relative paths for Electron builds
- Handles multiple CSS files if present
- Provides detailed logging during build process
### Feature Parity
**No manual intervention required** - CSS injection is handled automatically during the Vite build process.
The same Capacitor plugins work across all platforms:
**Author:** Matthew Raymer
- File system operations
- Camera access
- SQLite database
- Deep linking
- Sharing functionality

43
capacitor.config.json

@ -2,7 +2,6 @@
"appId": "app.timesafari",
"appName": "TimeSafari",
"webDir": "dist",
"bundledWebRuntime": false,
"server": {
"cleartext": true
},
@ -52,5 +51,47 @@
"*.jsdelivr.net",
"api.endorser.ch"
]
},
"electron": {
"deepLinking": {
"schemes": ["timesafari"]
},
"buildOptions": {
"appId": "app.timesafari",
"productName": "TimeSafari",
"directories": {
"output": "dist-electron-packages"
},
"files": [
"dist/**/*",
"electron/**/*"
],
"mac": {
"category": "public.app-category.productivity",
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
}
]
},
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64"]
}
]
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": ["x64"]
}
],
"category": "Utility"
}
}
}
}

3
index.html

@ -18,9 +18,6 @@
case 'capacitor':
import('./src/main.capacitor.ts');
break;
case 'electron':
import('./src/main.electron.ts');
break;
case 'web':
default:
import('./src/main.web.ts');

443
package-lock.json

@ -7549,24 +7549,33 @@
"license": "MIT"
},
"node_modules/@noble/ciphers": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.1.tgz",
"integrity": "sha512-QCOA9cgf3Rc33owG0AYBB9wszz+Ul2kramWN8tXG44Gyciud/tbkEqvxRF/IpqQaBpRBNi9f4jdNxqB2CQCIXg==",
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz",
"integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==",
"license": "MIT",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/curves": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.2.tgz",
"integrity": "sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.8.0"
"@noble/hashes": "1.3.2"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/curves/node_modules/@noble/hashes": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
@ -7586,12 +7595,12 @@
"optional": true
},
"node_modules/@noble/hashes": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
@ -7650,102 +7659,6 @@
"nostr-wasm": "0.1.0"
}
},
"node_modules/@nostr/tools/node_modules/@noble/ciphers": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz",
"integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==",
"license": "MIT",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@nostr/tools/node_modules/@noble/curves": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.3.2"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@nostr/tools/node_modules/@noble/curves/node_modules/@noble/hashes": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@nostr/tools/node_modules/@noble/hashes": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@nostr/tools/node_modules/@scure/base": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
"integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"license": "MIT"
},
"node_modules/@nostr/tools/node_modules/@scure/bip32": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz",
"integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==",
"license": "MIT",
"dependencies": {
"@noble/curves": "~1.1.0",
"@noble/hashes": "~1.3.1",
"@scure/base": "~1.1.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@nostr/tools/node_modules/@scure/bip32/node_modules/@noble/curves": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz",
"integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.3.1"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@nostr/tools/node_modules/@scure/bip39": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz",
"integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@npmcli/fs": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz",
@ -8724,95 +8637,56 @@
]
},
"node_modules/@scure/base": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz",
"integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==",
"license": "MIT",
"funding": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
"integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"license": "MIT"
},
"node_modules/@scure/bip32": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz",
"integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz",
"integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==",
"license": "MIT",
"dependencies": {
"@noble/curves": "~1.4.0",
"@noble/hashes": "~1.4.0",
"@scure/base": "~1.1.6"
"@noble/curves": "~1.1.0",
"@noble/hashes": "~1.3.1",
"@scure/base": "~1.1.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip32/node_modules/@noble/curves": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz",
"integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz",
"integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.4.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip32/node_modules/@noble/hashes": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
"@noble/hashes": "1.3.1"
},
"node_modules/@scure/bip32/node_modules/@scure/base": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz",
"integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==",
"license": "MIT",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip39": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz",
"integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz",
"integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "~1.4.0",
"@scure/base": "~1.1.6"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip39/node_modules/@noble/hashes": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
"license": "MIT",
"engines": {
"node": ">= 16"
"@noble/hashes": "~1.3.0",
"@scure/base": "~1.1.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@scure/bip39/node_modules/@scure/base": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz",
"integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==",
"license": "MIT",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@simplewebauthn/browser": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-10.0.0.tgz",
@ -9704,9 +9578,9 @@
}
},
"node_modules/@types/leaflet": {
"version": "1.9.18",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.18.tgz",
"integrity": "sha512-ht2vsoPjezor5Pmzi5hdsA7F++v5UGq9OlUduWHmMZiuQGIpJ2WS5+Gg9HaAA79gNh1AIPtCqhzejcIZ3lPzXQ==",
"version": "1.9.19",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.19.tgz",
"integrity": "sha512-pB+n2daHcZPF2FDaWa+6B0a0mSDf4dPU35y5iTXsx7x/PzzshiX5atYiS1jlBn43X7XvM8AP+AB26lnSk0J4GA==",
"devOptional": true,
"license": "MIT",
"dependencies": {
@ -10409,6 +10283,15 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@veramo/did-provider-peer/node_modules/@scure/base": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz",
"integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==",
"license": "MIT",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@veramo/did-provider-peer/node_modules/@veramo/core-types": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@veramo/core-types/-/core-types-6.0.0.tgz",
@ -11834,7 +11717,6 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"possible-typed-array-names": "^1.0.0"
@ -12602,9 +12484,9 @@
"license": "MIT"
},
"node_modules/browserslist": {
"version": "4.25.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz",
"integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==",
"version": "4.25.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
"integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
"devOptional": true,
"funding": [
{
@ -12622,8 +12504,8 @@
],
"license": "MIT",
"dependencies": {
"caniuse-lite": "^1.0.30001718",
"electron-to-chromium": "^1.5.160",
"caniuse-lite": "^1.0.30001726",
"electron-to-chromium": "^1.5.173",
"node-releases": "^2.0.19",
"update-browserslist-db": "^1.1.3"
},
@ -12961,7 +12843,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.0",
@ -12993,7 +12874,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@ -13103,9 +12983,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001723",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz",
"integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==",
"version": "1.0.30001726",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz",
"integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==",
"devOptional": true,
"funding": [
{
@ -14860,7 +14740,6 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0",
@ -15091,6 +14970,24 @@
"node": ">=18"
}
},
"node_modules/did-jwt/node_modules/@noble/ciphers": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.4.1.tgz",
"integrity": "sha512-QCOA9cgf3Rc33owG0AYBB9wszz+Ul2kramWN8tXG44Gyciud/tbkEqvxRF/IpqQaBpRBNi9f4jdNxqB2CQCIXg==",
"license": "MIT",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/did-jwt/node_modules/@scure/base": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz",
"integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==",
"license": "MIT",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/did-jwt/node_modules/multiformats": {
"version": "9.9.0",
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz",
@ -15582,9 +15479,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.170",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.170.tgz",
"integrity": "sha512-GP+M7aeluQo9uAyiTCxgIj/j+PrWhMlY7LFVj8prlsPljd0Fdg9AprlfUi+OCSFWy9Y5/2D/Jrj9HS8Z4rpKWA==",
"version": "1.5.173",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.173.tgz",
"integrity": "sha512-2bFhXP2zqSfQHugjqJIDFVwa+qIxyNApenmXTp9EjaKtdPrES5Qcn9/aSFy/NaP2E+fWG/zxKu/LBvY36p5VNQ==",
"devOptional": true,
"license": "ISC"
},
@ -16016,9 +15913,9 @@
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.0.tgz",
"integrity": "sha512-8qsOYwkkGrahrgoUv76NZi23koqXOGiiEzXMrT8Q7VcYaUISR+5MorIUxfWqYXN0fN/31WbSrxCxFkVQ43wwrA==",
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz",
"integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -16238,6 +16135,18 @@
"@noble/hashes": "^1.4.0"
}
},
"node_modules/ethereum-bloom-filters/node_modules/@noble/hashes": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/ethereum-cryptography": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz",
@ -16274,6 +16183,42 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/ethereum-cryptography/node_modules/@scure/base": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz",
"integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==",
"license": "MIT",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/ethereum-cryptography/node_modules/@scure/bip32": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz",
"integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==",
"license": "MIT",
"dependencies": {
"@noble/curves": "~1.4.0",
"@noble/hashes": "~1.4.0",
"@scure/base": "~1.1.6"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/ethereum-cryptography/node_modules/@scure/bip39": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz",
"integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "~1.4.0",
"@scure/base": "~1.1.6"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/ethereumjs-util": {
"version": "7.1.5",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz",
@ -16341,18 +16286,6 @@
"node": ">=14.0.0"
}
},
"node_modules/ethers/node_modules/@noble/curves": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.3.2"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/ethers/node_modules/@noble/hashes": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
@ -16446,6 +16379,15 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/ethr-did/node_modules/@scure/base": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz",
"integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==",
"license": "MIT",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/ethr-did/node_modules/did-jwt": {
"version": "8.0.17",
"resolved": "https://registry.npmjs.org/did-jwt/-/did-jwt-8.0.17.tgz",
@ -17222,7 +17164,6 @@
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
"integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-callable": "^1.2.7"
@ -18102,7 +18043,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0"
@ -18705,7 +18645,6 @@
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -19149,7 +19088,6 @@
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
"integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"which-typed-array": "^1.1.16"
@ -19236,7 +19174,6 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
"dev": true,
"license": "MIT"
},
"node_modules/isbinaryfile": {
@ -23979,21 +23916,53 @@
}
},
"node_modules/pbkdf2": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
"integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.3.tgz",
"integrity": "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==",
"license": "MIT",
"dependencies": {
"create-hash": "^1.1.2",
"create-hmac": "^1.1.4",
"ripemd160": "^2.0.1",
"safe-buffer": "^5.0.1",
"sha.js": "^2.4.8"
"create-hash": "~1.1.3",
"create-hmac": "^1.1.7",
"ripemd160": "=2.0.1",
"safe-buffer": "^5.2.1",
"sha.js": "^2.4.11",
"to-buffer": "^1.2.0"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/pbkdf2/node_modules/create-hash": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz",
"integrity": "sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==",
"license": "MIT",
"dependencies": {
"cipher-base": "^1.0.1",
"inherits": "^2.0.1",
"ripemd160": "^2.0.0",
"sha.js": "^2.4.0"
}
},
"node_modules/pbkdf2/node_modules/hash-base": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz",
"integrity": "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.1"
}
},
"node_modules/pbkdf2/node_modules/ripemd160": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz",
"integrity": "sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==",
"license": "MIT",
"dependencies": {
"hash-base": "^2.0.0",
"inherits": "^2.0.1"
}
},
"node_modules/pe-library": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz",
@ -24163,7 +24132,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
"integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -24373,9 +24341,9 @@
}
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz",
"integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==",
"dev": true,
"license": "MIT",
"bin": {
@ -24535,9 +24503,9 @@
}
},
"node_modules/protons-runtime": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.5.0.tgz",
"integrity": "sha512-EsALjF9QsrEk6gbCx3lmfHxVN0ah7nG3cY7GySD4xf4g8cr7g543zB88Foh897Sr1RQJ9yDCUsoT1i1H/cVUFA==",
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-5.6.0.tgz",
"integrity": "sha512-/Kde+sB9DsMFrddJT/UZWe6XqvL7SL5dbag/DBCElFKhkwDj7XKt53S+mzLyaDP5OqS0wXjV5SA572uWDaT0Hg==",
"license": "Apache-2.0 OR MIT",
"dependencies": {
"uint8-varint": "^2.0.2",
@ -24744,9 +24712,9 @@
}
},
"node_modules/qrcode-generator": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.5.0.tgz",
"integrity": "sha512-sqo7otiDq5rA4djRkFI7IjLQqxRrLpIou0d3rqr03JJLUGf5raPh91xCio+lFFbQf0SlcVckStz0EmDEX3EeZA==",
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.5.1.tgz",
"integrity": "sha512-u0zerMlMtKBgkDlDzWXG/7F7jo2En1d7bivC7V33HGqP62XxGiHGnbCJNkSCMxGfNlhXGBGEIamNMHbZ4P4mLg==",
"license": "MIT"
},
"node_modules/qrcode-terminal": {
@ -26906,7 +26874,6 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"dev": true,
"license": "MIT",
"dependencies": {
"define-data-property": "^1.1.4",
@ -28836,6 +28803,20 @@
"optional": true,
"peer": true
},
"node_modules/to-buffer": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz",
"integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==",
"license": "MIT",
"dependencies": {
"isarray": "^2.0.5",
"safe-buffer": "^5.2.1",
"typed-array-buffer": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -29080,7 +29061,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
"integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.3",
@ -29169,9 +29149,9 @@
"license": "MIT"
},
"node_modules/typeorm": {
"version": "0.3.24",
"resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.24.tgz",
"integrity": "sha512-4IrHG7A0tY8l5gEGXfW56VOMfUVWEkWlH/h5wmcyZ+V8oCiLj7iTPp0lEjMEZVrxEkGSdP9ErgTKHKXQApl/oA==",
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.25.tgz",
"integrity": "sha512-fTKDFzWXKwAaBdEMU4k661seZewbNYET4r1J/z3Jwf+eAvlzMVpTLKAVcAzg75WwQk7GDmtsmkZ5MfkmXCiFWg==",
"license": "MIT",
"dependencies": {
"@sqltools/formatter": "^1.2.5",
@ -30352,7 +30332,6 @@
"version": "1.1.19",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
"integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
"dev": true,
"license": "MIT",
"dependencies": {
"available-typed-arrays": "^1.0.7",

70
package.json

@ -20,25 +20,14 @@
"test:ios": "node scripts/test-ios.js",
"check:android-device": "adb devices | grep -w 'device' || (echo 'No Android device connected' && exit 1)",
"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:electron": "./scripts/build-electron.sh && npm run build:electron-renderer",
"build:electron-renderer": "vite build --config vite.config.electron.renderer.mts",
"build:capacitor": "vite build --mode capacitor --config vite.config.capacitor.mts",
"build:capacitor": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --mode capacitor --config vite.config.capacitor.mts",
"build:web": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.web.mts",
"electron:dev": "./scripts/electron-dev.sh",
"electron:start": "electron .",
"clean:android": "adb uninstall app.timesafari.app || true",
"build:android": "./scripts/build-android.sh",
"electron:build-linux": "./scripts/build-electron-linux.sh",
"electron:build-linux-deb": "./scripts/build-electron-linux.sh deb",
"electron:build-linux-prod": "./scripts/build-electron-linux.sh prod",
"build:electron-prod": "NODE_ENV=production npm run build:electron",
"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",
"electron:build-mac": "./scripts/build-electron-mac.sh",
"electron:build-mac-universal": "./scripts/build-electron-mac.sh universal"
"fastlane:android:release": "cd android && fastlane release"
},
"dependencies": {
"@capacitor-community/sqlite": "6.0.2",
@ -145,8 +134,6 @@
"browserify-fs": "^1.0.0",
"concurrently": "^8.2.2",
"crypto-browserify": "^3.12.1",
"electron": "^33.2.1",
"electron-builder": "^25.1.8",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
@ -163,58 +150,5 @@
"typescript": "~5.2.2",
"vite": "^5.2.0",
"vite-plugin-pwa": "^1.0.0"
},
"main": "./dist-electron/main.js",
"build": {
"appId": "app.timesafari.app",
"productName": "TimeSafari",
"directories": {
"output": "dist-electron-packages"
},
"files": [
"dist-electron/**/*",
"dist/**/*"
],
"extraResources": [
{
"from": "dist-electron/www",
"to": "www"
}
],
"linux": {
"target": [
"AppImage",
"deb"
],
"category": "Office",
"icon": "build/icon.png"
},
"asar": true,
"mac": {
"target": [
"dmg",
"zip"
],
"category": "public.app-category.productivity",
"icon": "build/icon.png",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "ios/App/App/entitlements.mac.plist",
"entitlementsInherit": "ios/App/App/entitlements.mac.plist"
},
"dmg": {
"contents": [
{
"x": 130,
"y": 220
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
]
}
}
}

19
scripts/README.md

@ -27,7 +27,6 @@ All scripts automatically handle environment variables for different build types
|----------|------|-------------|-----------------|--------------|
| `web` | web | true | false | `build-web.sh` |
| `capacitor` | capacitor | false | true | `build-capacitor.sh` |
| `electron` | electron | false | true | `build-electron.sh` |
#### Automatic Environment Setup
@ -96,10 +95,7 @@ exit 0
### Build Scripts
- **`build-electron.sh`**: Complete Electron build process
- **`build-android.sh`**: Complete Android build process
- **`build-electron-linux.sh`**: Linux Electron packaging (AppImage, .deb)
- **`build-electron-mac.sh`**: macOS Electron packaging (standard, universal)
### Development Scripts
@ -152,10 +148,8 @@ print_footer "Script Title"
```
### Building Applications
```bash
# Build Electron
./scripts/build-electron.sh
```bash
# Build Android
./scripts/build-android.sh
@ -170,19 +164,21 @@ print_footer "Script Title"
```
### Development Workflows
```bash
# Start Electron development
./scripts/electron-dev.sh
# Start development
npm run dev
```
## Environment Variable Features
### Automatic Setup
All scripts automatically configure the correct environment variables for their build type:
```bash
# Electron builds automatically get:
export VITE_PLATFORM=electron
# Capacitor builds automatically get:
export VITE_PLATFORM=capacitor
export VITE_PWA_ENABLED=false
export VITE_DISABLE_PWA=true
export DEBUG_MIGRATIONS=0
@ -214,7 +210,6 @@ validate_env_vars "VITE_API_URL" "VITE_DEBUG" || exit 1
View current environment variables with the `--env` flag:
```bash
./scripts/build-electron.sh --env
./scripts/test-env.sh --env
```

82
scripts/build-electron-linux.sh

@ -1,82 +0,0 @@
#!/bin/bash
# build-electron-linux.sh
# Author: Matthew Raymer
# Description: Electron Linux build script for TimeSafari application
# This script builds Electron packages for Linux with support for different formats.
#
# Usage: ./scripts/build-electron-linux.sh [deb|prod]
# - No argument: Builds AppImage
# - deb: Builds .deb package
# - prod: Builds production AppImage
#
# Exit Codes:
# 1 - Build failed
# 2 - Invalid argument
# Exit on any error
set -e
# Source common utilities
source "$(dirname "$0")/common.sh"
# Parse command line arguments
parse_args "$@"
# Parse build type argument
BUILD_TYPE=${1:-"appimage"}
PRODUCTION=false
case $BUILD_TYPE in
"deb")
BUILD_TARGET="deb"
log_info "Building .deb package"
;;
"prod")
BUILD_TARGET="AppImage"
PRODUCTION=true
log_info "Building production AppImage"
;;
"appimage"|"")
BUILD_TARGET="AppImage"
log_info "Building AppImage"
;;
*)
log_error "Invalid build type: $BUILD_TYPE"
log_error "Usage: $0 [deb|prod]"
exit 2
;;
esac
# Print build header
print_header "TimeSafari Electron Linux Build"
log_info "Starting Linux build process at $(date)"
log_info "Build type: $BUILD_TYPE"
log_info "Build target: $BUILD_TARGET"
log_info "Production mode: $PRODUCTION"
# Setup environment for Electron build
setup_build_env "electron" "$PRODUCTION"
# Setup application directories
setup_app_directories
# Load environment from .env file if it exists
load_env_file ".env"
# Step 1: Build Electron application
if [ "$PRODUCTION" = true ]; then
safe_execute "Building production Electron application" "npm run build:electron-prod" || exit 1
else
safe_execute "Building Electron application" "npm run build:electron" || exit 1
fi
# Step 2: Build package
safe_execute "Building Linux package" "npx electron-builder --linux $BUILD_TARGET" || exit 1
# Print build summary
log_success "Linux build completed successfully!"
log_info "Package type: $BUILD_TARGET"
print_footer "Linux Build"
# Exit with success
exit 0

74
scripts/build-electron-mac.sh

@ -1,74 +0,0 @@
#!/bin/bash
# build-electron-mac.sh
# Author: Matthew Raymer
# Description: Electron Mac build script for TimeSafari application
# This script builds Electron packages for macOS with support for universal builds.
#
# Usage: ./scripts/build-electron-mac.sh [universal]
# - No argument: Builds standard Mac package
# - universal: Builds universal Mac package (Intel + Apple Silicon)
#
# Exit Codes:
# 1 - Build failed
# 2 - Invalid argument
# Exit on any error
set -e
# Source common utilities
source "$(dirname "$0")/common.sh"
# Parse command line arguments
parse_args "$@"
# Parse build type argument
BUILD_TYPE=${1:-"standard"}
UNIVERSAL=false
case $BUILD_TYPE in
"universal")
UNIVERSAL=true
log_info "Building universal Mac package (Intel + Apple Silicon)"
;;
"standard"|"")
log_info "Building standard Mac package"
;;
*)
log_error "Invalid build type: $BUILD_TYPE"
log_error "Usage: $0 [universal]"
exit 2
;;
esac
# Print build header
print_header "TimeSafari Electron Mac Build"
log_info "Starting Mac build process at $(date)"
log_info "Build type: $BUILD_TYPE"
log_info "Universal build: $UNIVERSAL"
# Setup environment for Electron build (production mode for packaging)
setup_build_env "electron" "true"
# Setup application directories
setup_app_directories
# Load environment from .env file if it exists
load_env_file ".env"
# Step 1: Build Electron application
safe_execute "Building Electron application" "npm run build:electron-prod" || exit 1
# Step 2: Build package
if [ "$UNIVERSAL" = true ]; then
safe_execute "Building universal Mac package" "npx electron-builder --mac --universal" || exit 1
else
safe_execute "Building Mac package" "npx electron-builder --mac" || exit 1
fi
# Print build summary
log_success "Mac build completed successfully!"
log_info "Package type: $([ "$UNIVERSAL" = true ] && echo "Universal" || echo "Standard")"
print_footer "Mac Build"
# Exit with success
exit 0

148
scripts/build-electron.js

@ -1,148 +0,0 @@
const fs = require('fs');
const path = require('path');
console.log('Starting electron build process...');
// Define paths
const electronDistPath = path.join(__dirname, '..', 'dist-electron');
const wwwPath = path.join(electronDistPath, 'www');
// Create www directory if it doesn't exist
if (!fs.existsSync(wwwPath)) {
fs.mkdirSync(wwwPath, { recursive: true });
}
// Copy the Vite-built index.html to www directory
const viteIndexPath = path.join(electronDistPath, 'index.html');
const wwwIndexPath = path.join(wwwPath, 'index.html');
if (fs.existsSync(viteIndexPath)) {
console.log('Copying Vite-built index.html to www directory...');
fs.copyFileSync(viteIndexPath, wwwIndexPath);
// Remove the original index.html from dist-electron root
fs.unlinkSync(viteIndexPath);
console.log('Moved index.html to www directory');
} else {
console.error('Vite-built index.html not found at:', viteIndexPath);
process.exit(1);
}
// Copy assets directory if it exists in dist-electron
const assetsSrc = path.join(electronDistPath, 'assets');
const assetsDest = path.join(wwwPath, 'assets');
if (fs.existsSync(assetsSrc)) {
console.log('Moving assets directory to www...');
if (fs.existsSync(assetsDest)) {
fs.rmSync(assetsDest, { recursive: true, force: true });
}
fs.renameSync(assetsSrc, assetsDest);
console.log('Moved assets directory to www');
}
// Copy favicon if it exists
const faviconSrc = path.join(electronDistPath, 'favicon.ico');
const faviconDest = path.join(wwwPath, 'favicon.ico');
if (fs.existsSync(faviconSrc)) {
console.log('Moving favicon to www...');
fs.renameSync(faviconSrc, faviconDest);
console.log('Moved favicon to www');
}
// Remove service worker files from www directory
const swFilesToRemove = [
'sw.js',
'sw.js.map',
'workbox-*.js',
'workbox-*.js.map',
'registerSW.js',
'manifest.webmanifest'
];
console.log('Removing service worker files...');
swFilesToRemove.forEach(pattern => {
const files = fs.readdirSync(wwwPath).filter(file =>
file.match(new RegExp(pattern.replace(/\*/g, '.*')))
);
files.forEach(file => {
const filePath = path.join(wwwPath, file);
console.log(`Removing ${filePath}`);
try {
fs.unlinkSync(filePath);
} catch (err) {
console.warn(`Could not remove ${filePath}:`, err.message);
}
});
});
// Also check and remove from assets directory
if (fs.existsSync(assetsDest)) {
swFilesToRemove.forEach(pattern => {
const files = fs.readdirSync(assetsDest).filter(file =>
file.match(new RegExp(pattern.replace(/\*/g, '.*')))
);
files.forEach(file => {
const filePath = path.join(assetsDest, file);
console.log(`Removing ${filePath}`);
try {
fs.unlinkSync(filePath);
} catch (err) {
console.warn(`Could not remove ${filePath}:`, err.message);
}
});
});
}
// Verify the final index.html structure
const finalIndexContent = fs.readFileSync(wwwIndexPath, 'utf8');
console.log('Final index.html structure:');
console.log('- Has CSS link:', finalIndexContent.includes('<link rel="stylesheet"'));
console.log('- Has main script:', finalIndexContent.includes('main.electron.js'));
console.log('- No service worker references:', !finalIndexContent.includes('serviceWorker'));
// Copy main process files to the correct location
console.log('Setting up main process files...');
// The main process files are already in the correct location
// Just verify they exist and are ready
const mainPath = path.join(electronDistPath, 'main.js');
const preloadPath = path.join(electronDistPath, 'preload.js');
if (fs.existsSync(mainPath)) {
console.log('Main process file ready at:', mainPath);
} else {
console.error('Main process file not found at:', mainPath);
process.exit(1);
}
if (fs.existsSync(preloadPath)) {
console.log('Preload script ready at:', preloadPath);
} else {
console.warn('Preload script not found at:', preloadPath);
}
// Clean up any remaining files in dist-electron root (except main.js, preload.js, and www directory)
const remainingFiles = fs.readdirSync(electronDistPath);
remainingFiles.forEach(file => {
if (file !== 'main.js' && file !== 'preload.js' && file !== 'www') {
const filePath = path.join(electronDistPath, file);
console.log(`Removing remaining file: ${file}`);
try {
if (fs.statSync(filePath).isDirectory()) {
fs.rmSync(filePath, { recursive: true, force: true });
} else {
fs.unlinkSync(filePath);
}
} catch (err) {
console.warn(`Could not remove ${filePath}:`, err.message);
}
}
});
console.log('Electron build process completed successfully');
console.log('Final structure:');
console.log('- Main process:', path.join(electronDistPath, 'main.js'));
console.log('- Preload script:', path.join(electronDistPath, 'preload.js'));
console.log('- Web assets:', path.join(electronDistPath, 'www'));

53
scripts/build-electron.sh

@ -1,53 +0,0 @@
#!/bin/bash
# build-electron.sh
# Author: Matthew Raymer
# Description: Electron build script for TimeSafari application
# This script handles the complete Electron build process including cleanup,
# TypeScript compilation, Vite build, and Electron-specific setup.
#
# Exit Codes:
# 1 - Cleanup failed
# 2 - TypeScript compilation failed
# 3 - Vite build failed
# 4 - Electron build script failed
# Exit on any error
set -e
# Source common utilities
source "$(dirname "$0")/common.sh"
# Parse command line arguments
parse_args "$@"
# Print build header
print_header "TimeSafari Electron Build Process"
log_info "Starting Electron build process at $(date)"
# Setup environment for Electron build
setup_build_env "electron"
# Setup application directories
setup_app_directories
# Load environment from .env file if it exists
load_env_file ".env"
# Step 1: Clean previous builds
safe_execute "Cleaning previous builds" "npm run clean:electron" || exit 1
# Step 2: Compile TypeScript for Electron
safe_execute "Compiling TypeScript for Electron" "npx tsc -p tsconfig.electron.json" || exit 2
# Step 3: Build with Vite
safe_execute "Building with Vite" "npx vite build --config vite.config.electron.mts" || exit 3
# Step 4: Run Electron build script
safe_execute "Running Electron build script" "node scripts/build-electron.js" || exit 4
# Print build summary
log_success "Electron build completed successfully!"
print_footer "Electron Build"
# Exit with success
exit 0

11
scripts/common.sh

@ -176,16 +176,6 @@ setup_build_env() {
log_debug "Set VITE_GIT_HASH=$git_hash"
case $build_type in
"electron")
export VITE_PLATFORM=electron
export VITE_PWA_ENABLED=false
export VITE_DISABLE_PWA=true
export DEBUG_MIGRATIONS=0
if [ "$production" = true ]; then
export NODE_ENV=production
log_debug "Set production mode for Electron"
fi
;;
"capacitor")
export VITE_PLATFORM=capacitor
export VITE_PWA_ENABLED=false
@ -227,7 +217,6 @@ setup_app_directories() {
# Create build directories if they don't exist
mkdir -p dist
mkdir -p dist-electron
log_debug "Application directories created"
}

44
scripts/electron-dev.sh

@ -1,44 +0,0 @@
#!/bin/bash
# electron-dev.sh
# Author: Matthew Raymer
# Description: Electron development script for TimeSafari application
# This script builds the application and starts Electron for development.
#
# Exit Codes:
# 1 - Build failed
# 2 - Electron start failed
# Exit on any error
set -e
# Source common utilities
source "$(dirname "$0")/common.sh"
# Parse command line arguments
parse_args "$@"
# Print dev header
print_header "TimeSafari Electron Development"
log_info "Starting Electron development at $(date)"
# Setup environment for Electron development
setup_build_env "electron"
# Setup application directories
setup_app_directories
# Load environment from .env file if it exists
load_env_file ".env"
# Step 1: Build the application
safe_execute "Building application" "npm run build" || exit 1
# Step 2: Start Electron
safe_execute "Starting Electron" "electron ." || exit 2
# Print dev summary
log_success "Electron development session ended"
print_footer "Electron Development"
# Exit with success
exit 0

35
scripts/test-env.sh

@ -17,37 +17,34 @@ parse_args "$@"
print_header "Environment Variable Test"
log_info "Testing environment variable handling at $(date)"
# Test 1: Electron environment
log_info "Test 1: Setting up Electron environment..."
setup_build_env "electron"
print_env_vars "VITE_"
# Test 2: Capacitor environment
log_info "Test 2: Setting up Capacitor environment..."
# Test 1: Capacitor environment
log_info "Test 1: Setting up Capacitor environment..."
setup_build_env "capacitor"
print_env_vars "VITE_"
echo ""
# Test 3: Web environment
log_info "Test 3: Setting up Web environment..."
# Test 2: Web environment
log_info "Test 2: Setting up Web environment..."
setup_build_env "web"
print_env_vars "VITE_"
echo ""
# Test 4: Production Electron environment
log_info "Test 4: Setting up Production Electron environment..."
setup_build_env "electron" "true"
# Test 3: Production Capacitor environment
log_info "Test 3: Setting up Production Capacitor environment..."
setup_build_env "capacitor" "true"
print_env_vars "VITE_"
print_env_vars "NODE_ENV"
echo ""
# Test 5: Application directories
log_info "Test 5: Setting up application directories..."
# Test 4: Application directories
log_info "Test 4: Setting up application directories..."
setup_app_directories
# Test 6: Load .env file (if it exists)
log_info "Test 6: Loading .env file..."
# Test 5: Load .env file (if it exists)
log_info "Test 5: Loading .env file..."
load_env_file ".env"
# Test 7: Git hash
log_info "Test 7: Getting git hash..."
# Test 6: Git hash
log_info "Test 6: Getting git hash..."
GIT_HASH=$(get_git_hash)
log_info "Git hash: $GIT_HASH"

174
src/electron/main.js

@ -1,174 +0,0 @@
const { app, BrowserWindow } = require("electron");
const path = require("path");
const fs = require("fs");
const logger = require("../utils/logger");
// Check if running in dev mode
const isDev = process.argv.includes("--inspect");
function createWindow() {
// 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.
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
webSecurity: true,
allowRunningInsecureContent: false,
preload: path.join(__dirname, "preload.js"),
},
});
// Always open DevTools for now
mainWindow.webContents.openDevTools();
// 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;
// Handle paths that don't start with file://
if (!url.startsWith("file://") && url.includes("/assets/")) {
url = `file://${path.join(__dirname, "www", url)}`;
}
// 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;
}
callback({}); // No redirect for other URLs
},
);
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());
}
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();
}
}
// Handle app ready
app.whenReady().then(createWindow);
// Handle all windows closed
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// Handle any errors
process.on("uncaughtException", (error) => {
logger.error("Uncaught Exception:", error);
});

221
src/electron/main.ts

@ -1,221 +0,0 @@
import { app, BrowserWindow } from "electron";
import path from "path";
import fs from "fs";
// Simple logger implementation
const logger = {
// eslint-disable-next-line no-console
log: (...args: unknown[]) => console.log(...args),
// eslint-disable-next-line no-console
error: (...args: unknown[]) => console.error(...args),
// eslint-disable-next-line no-console
info: (...args: unknown[]) => console.info(...args),
// eslint-disable-next-line no-console
warn: (...args: unknown[]) => console.warn(...args),
// eslint-disable-next-line no-console
debug: (...args: unknown[]) => console.debug(...args),
};
// Check if running in dev mode
const isDev = process.argv.includes("--inspect");
async function createWindow(): Promise<void> {
// Add before createWindow function
const preloadPath = app.isPackaged
? path.join(app.getAppPath(), "dist-electron", "preload.js")
: path.join(__dirname, "preload.js");
logger.log("Checking preload path:", preloadPath);
logger.log("Preload exists:", fs.existsSync(preloadPath));
// Log environment and paths
logger.log("process.cwd():", process.cwd());
logger.log("__dirname:", __dirname);
logger.log("app.getAppPath():", app.getAppPath());
logger.log("app.isPackaged:", app.isPackaged);
// List files in __dirname and __dirname/www
try {
logger.log("Files in __dirname:", fs.readdirSync(__dirname));
const wwwDir = path.join(__dirname, "www");
if (fs.existsSync(wwwDir)) {
logger.log("Files in www:", fs.readdirSync(wwwDir));
} else {
logger.log("www directory does not exist in __dirname");
}
} catch (e) {
logger.error("Error reading directories:", e);
}
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
webSecurity: true,
allowRunningInsecureContent: false,
preload: preloadPath,
},
});
// Always open DevTools for now
mainWindow.webContents.openDevTools();
// Intercept requests to robustly fix asset paths for Electron
mainWindow.webContents.session.webRequest.onBeforeRequest(
{ urls: ["*://*/*"] },
(details, callback) => {
const url = details.url;
logger.log('[main.ts] Intercepted request:', url);
// Match both file:///assets/... and /assets/...
const assetMatch = url.match(/(?:file:\/\/\/|file:\/\/|https?:\/\/[^\/]+)?\/assets\/(.+)/);
if (assetMatch) {
const assetRelPath = assetMatch[1];
const assetAbsPath = path.join(app.getAppPath(), "dist-electron", "www", "assets", assetRelPath);
logger.log('[main.ts] Asset request detected:', {
originalUrl: url,
assetRelPath,
assetAbsPath,
exists: fs.existsSync(assetAbsPath)
});
if (fs.existsSync(assetAbsPath)) {
const newUrl = `file://${assetAbsPath}`;
logger.log('[main.ts] Redirecting to:', newUrl);
callback({ redirectURL: newUrl });
return;
} else {
logger.error('[main.ts] Asset file not found:', assetAbsPath);
}
}
callback({});
}
);
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());
}
let indexPath: string;
if (app.isPackaged) {
indexPath = path.join(
app.getAppPath(),
"dist-electron",
"www",
"index.html",
);
logger.log("[main.ts] Using packaged indexPath:", indexPath);
} else {
indexPath = path.resolve(
process.cwd(),
"dist-electron",
"www",
"index.html",
);
logger.log("[main.ts] Using dev indexPath:", indexPath);
if (!fs.existsSync(indexPath)) {
logger.error("[main.ts] Dev index.html not found:", indexPath);
throw new Error("Index file not found");
}
}
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"));
}
// 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 with the correct base URL for assets
const baseURL = `file://${path.dirname(indexPath)}/`;
logger.log('[main.ts] Loading with base URL:', baseURL);
try {
await mainWindow.loadURL(`file://${indexPath}`);
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();
}
}
// Handle app ready
app.whenReady().then(createWindow);
// Handle all windows closed
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// Handle any errors
process.on("uncaughtException", (error) => {
logger.error("Uncaught Exception:", error);
});

91
src/electron/preload.js

@ -1,91 +0,0 @@
const { contextBridge, ipcRenderer } = require("electron");
const logger = {
log: (message, ...args) => {
// Always log in development, log with context in production
if (process.env.NODE_ENV !== "production") {
/* eslint-disable no-console */
console.log(`[Preload] ${message}`, ...args);
/* eslint-enable no-console */
}
},
warn: (message, ...args) => {
// Always log warnings
/* eslint-disable no-console */
console.warn(`[Preload] ${message}`, ...args);
/* eslint-enable no-console */
},
error: (message, ...args) => {
// Always log errors
/* eslint-disable no-console */
console.error(`[Preload] ${message}`, ...args);
/* eslint-enable no-console */
},
info: (message, ...args) => {
// Always log info in development, log with context in production
if (process.env.NODE_ENV !== "production") {
/* eslint-disable no-console */
console.info(`[Preload] ${message}`, ...args);
/* eslint-enable no-console */
}
},
};
// 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.info("Preload script starting...");
// Force electron platform in the renderer process
window.process = { env: { VITE_PLATFORM: "electron" } };
try {
contextBridge.exposeInMainWorld("electronAPI", {
// Path utilities
getPath,
// IPC functions
send: (channel, data) => {
const validChannels = ["toMain"];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
const validChannels = ["fromMain"];
if (validChannels.includes(channel)) {
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
},
// Environment info
env: {
isElectron: true,
isDev: process.env.NODE_ENV === "development",
platform: "electron", // Explicitly set platform
},
// Path utilities
getBasePath: () => {
return process.env.NODE_ENV === "development" ? "/" : "./";
},
});
logger.info("Preload script completed successfully");
} catch (error) {
logger.error("Error in preload script:", error);
}

17
src/main.electron.ts

@ -1,17 +0,0 @@
import './assets/styles/tailwind.css';
import { initializeApp } from "./main.common";
import { logger } from "./utils/logger";
const platform = process.env.VITE_PLATFORM;
const pwa_enabled = process.env.VITE_PWA_ENABLED === "true";
logger.info("[Electron] Initializing app");
logger.info("[Electron] Platform:", { platform });
logger.info("[Electron] PWA enabled:", { pwa_enabled });
if (pwa_enabled) {
logger.warn("[Electron] PWA is enabled, but not supported in electron");
}
const app = initializeApp();
app.mount("#app");

2
src/main.web.ts

@ -6,7 +6,7 @@ const platform = process.env.VITE_PLATFORM;
const pwa_enabled = process.env.VITE_PWA_ENABLED === "true";
// Only import service worker for web builds
if (platform !== "electron" && pwa_enabled) {
if (pwa_enabled) {
import("./registerServiceWorker"); // Web PWA support
}

16
src/registerServiceWorker.ts

@ -2,18 +2,10 @@
import { register } from "register-service-worker";
// Check if we're in an Electron environment
const isElectron =
process.env.VITE_PLATFORM === "electron" ||
process.env.VITE_DISABLE_PWA === "true" ||
window.navigator.userAgent.toLowerCase().includes("electron");
// Only register service worker if:
// 1. Not in Electron
// 2. PWA is explicitly enabled
// 3. In production mode
// 1. PWA is explicitly enabled
// 2. In production mode
if (
!isElectron &&
process.env.VITE_PWA_ENABLED === "true" &&
process.env.NODE_ENV === "production"
) {
@ -45,9 +37,7 @@ if (
} else {
console.log(
`Service worker registration skipped - ${
isElectron
? "running in Electron"
: process.env.VITE_PWA_ENABLED !== "true"
process.env.VITE_PWA_ENABLED !== "true"
? "PWA not enabled"
: "not in production mode"
}`,

9
src/router/index.ts

@ -277,14 +277,9 @@ const routes: Array<RouteRecordRaw> = [
},
];
const isElectron = window.location.protocol === "file:";
const initialPath = isElectron
? window.location.pathname.split("/dist-electron/www/")[1] || "/"
: window.location.pathname;
const initialPath = window.location.pathname;
const history = isElectron
? createMemoryHistory() // Memory history for Electron
: createWebHistory("/"); // Add base path for web apps
const history = createWebHistory("/"); // Add base path for web apps
/** @type {*} */
const router = createRouter({

5
src/services/PlatformServiceFactory.ts

@ -1,7 +1,6 @@
import { PlatformService } from "./PlatformService";
import { WebPlatformService } from "./platforms/WebPlatformService";
import { CapacitorPlatformService } from "./platforms/CapacitorPlatformService";
import { ElectronPlatformService } from "./platforms/ElectronPlatformService";
/**
* Factory class for creating platform-specific service implementations.
@ -10,7 +9,6 @@ import { ElectronPlatformService } from "./platforms/ElectronPlatformService";
* The factory determines which platform implementation to use based on the VITE_PLATFORM
* environment variable. Supported platforms are:
* - capacitor: Mobile platform using Capacitor
* - electron: Desktop platform using Electron
* - web: Default web platform (fallback)
*
* @example
@ -39,9 +37,6 @@ export class PlatformServiceFactory {
case "capacitor":
PlatformServiceFactory.instance = new CapacitorPlatformService();
break;
case "electron":
PlatformServiceFactory.instance = new ElectronPlatformService();
break;
case "web":
default:
PlatformServiceFactory.instance = new WebPlatformService();

10
src/services/platforms/CapacitorPlatformService.ts

@ -244,13 +244,15 @@ export class CapacitorPlatformService implements PlatformService {
* @returns Platform capabilities object
*/
getCapabilities(): PlatformCapabilities {
const platform = Capacitor.getPlatform();
return {
hasFileSystem: true,
hasCamera: true,
isMobile: true,
isIOS: Capacitor.getPlatform() === "ios",
hasFileDownload: false,
needsFileHandlingInstructions: true,
isMobile: true, // Capacitor is always mobile
isIOS: platform === "ios",
hasFileDownload: false, // Mobile platforms need sharing
needsFileHandlingInstructions: true, // Mobile needs instructions
isNativeApp: true,
};
}

358
src/services/platforms/ElectronPlatformService.ts

@ -1,358 +0,0 @@
import {
ImageResult,
PlatformService,
PlatformCapabilities,
} from "../PlatformService";
import { logger } from "../../utils/logger";
import { QueryExecResult, SqlValue } from "@/interfaces/database";
import {
SQLiteConnection,
SQLiteDBConnection,
CapacitorSQLite,
Changes,
} from "@capacitor-community/sqlite";
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";
interface Migration {
name: string;
sql: string;
}
/**
* Platform service implementation for Electron (desktop) platform.
* Provides native desktop functionality through Electron and Capacitor plugins for:
* - File system operations (TODO)
* - Camera integration (TODO)
* - SQLite database operations
* - System-level features (TODO)
*/
export class ElectronPlatformService implements PlatformService {
private sqlite: SQLiteConnection;
private db: SQLiteDBConnection | null = null;
private dbName = "timesafari.db";
private initialized = false;
constructor() {
this.sqlite = new SQLiteConnection(CapacitorSQLite);
}
private async initializeDatabase(): Promise<void> {
if (this.initialized) {
return;
}
try {
// Create/Open database
this.db = await this.sqlite.createConnection(
this.dbName,
false,
"no-encryption",
1,
false,
);
await this.db.open();
// Set journal mode to WAL for better performance
await this.db.execute("PRAGMA journal_mode=WAL;");
// Run migrations
await this.runMigrations();
this.initialized = true;
logger.log(
"[ElectronPlatformService] SQLite database initialized successfully",
);
} catch (error) {
logger.error(
"[ElectronPlatformService] Error initializing SQLite database:",
error,
);
throw new Error(
"[ElectronPlatformService] Failed to initialize database",
);
}
}
private async runMigrations(): Promise<void> {
if (!this.db) {
throw new Error("Database not initialized");
}
// Create migrations table if it doesn't exist
await this.db.execute(`
CREATE TABLE IF NOT EXISTS migrations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
`);
// Get list of executed migrations
const result = await this.db.query("SELECT name FROM migrations;");
const executedMigrations = new Set(
result.values?.map((row) => row[0]) || [],
);
// Run pending migrations in order
const migrations: Migration[] = [
{
name: "001_initial",
sql: `
CREATE TABLE IF NOT EXISTS accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
dateCreated TEXT NOT NULL,
derivationPath TEXT,
did TEXT NOT NULL,
identityEncrBase64 TEXT,
mnemonicEncrBase64 TEXT,
passkeyCredIdHex TEXT,
publicKeyHex TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_accounts_did ON accounts(did);
CREATE TABLE IF NOT EXISTS secret (
id INTEGER PRIMARY KEY AUTOINCREMENT,
secretBase64 TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
accountDid TEXT,
activeDid TEXT,
apiServer TEXT,
filterFeedByNearby BOOLEAN,
filterFeedByVisible BOOLEAN,
finishedOnboarding BOOLEAN,
firstName TEXT,
hideRegisterPromptOnNewContact BOOLEAN,
isRegistered BOOLEAN,
lastName TEXT,
lastAckedOfferToUserJwtId TEXT,
lastAckedOfferToUserProjectsJwtId TEXT,
lastNotifiedClaimId TEXT,
lastViewedClaimId TEXT,
notifyingNewActivityTime TEXT,
notifyingReminderMessage TEXT,
notifyingReminderTime TEXT,
partnerApiServer TEXT,
passkeyExpirationMinutes INTEGER,
profileImageUrl TEXT,
searchBoxes TEXT,
showContactGivesInline BOOLEAN,
showGeneralAdvanced BOOLEAN,
showShortcutBvc BOOLEAN,
vapid TEXT,
warnIfProdServer BOOLEAN,
warnIfTestServer BOOLEAN,
webPushServer TEXT
);
CREATE INDEX IF NOT EXISTS idx_settings_accountDid ON settings(accountDid);
INSERT INTO settings (id, apiServer) VALUES (1, '${DEFAULT_ENDORSER_API_SERVER}');
CREATE TABLE IF NOT EXISTS contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
did TEXT NOT NULL,
name TEXT,
contactMethods TEXT,
nextPubKeyHashB64 TEXT,
notes TEXT,
profileImageUrl TEXT,
publicKeyBase64 TEXT,
seesMe BOOLEAN,
registered BOOLEAN
);
CREATE INDEX IF NOT EXISTS idx_contacts_did ON contacts(did);
CREATE INDEX IF NOT EXISTS idx_contacts_name ON contacts(name);
CREATE TABLE IF NOT EXISTS logs (
date TEXT PRIMARY KEY,
message TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS temp (
id TEXT PRIMARY KEY,
blobB64 TEXT
);
`,
},
];
for (const migration of migrations) {
if (!executedMigrations.has(migration.name)) {
await this.db.execute(migration.sql);
await this.db.run("INSERT INTO migrations (name) VALUES (?)", [
migration.name,
]);
logger.log(`Migration ${migration.name} executed successfully`);
}
}
}
/**
* Gets the capabilities of the Electron platform
* @returns Platform capabilities object
*/
getCapabilities(): PlatformCapabilities {
return {
hasFileSystem: false, // Not implemented yet
hasCamera: false, // Not implemented yet
isMobile: false,
isIOS: false,
hasFileDownload: false, // Not implemented yet
needsFileHandlingInstructions: false,
isNativeApp: true, // Electron is a native app
};
}
/**
* Reads a file from the filesystem.
* @param _path - Path to the file to read
* @returns Promise that should resolve to file contents
* @throws Error with "Not implemented" message
* @todo Implement file reading using Electron's file system API
*/
async readFile(_path: string): Promise<string> {
throw new Error("Not implemented");
}
/**
* Writes content to a file.
* @param _path - Path where to write the file
* @param _content - Content to write to the file
* @throws Error with "Not implemented" message
* @todo Implement file writing using Electron's file system API
*/
async writeFile(_path: string, _content: string): Promise<void> {
throw new Error("Not implemented");
}
/**
* Writes content to a file and opens the system share dialog.
* @param _fileName - Name of the file to create
* @param _content - Content to write to the file
* @throws Error with "Not implemented" message
* @todo Implement using Electron's dialog and file system APIs
*/
async writeAndShareFile(_fileName: string, _content: string): Promise<void> {
throw new Error("Not implemented");
}
/**
* Deletes a file from the filesystem.
* @param _path - Path to the file to delete
* @throws Error with "Not implemented" message
* @todo Implement file deletion using Electron's file system API
*/
async deleteFile(_path: string): Promise<void> {
throw new Error("Not implemented");
}
/**
* Lists files in the specified directory.
* @param _directory - Path to the directory to list
* @returns Promise that should resolve to array of filenames
* @throws Error with "Not implemented" message
* @todo Implement directory listing using Electron's file system API
*/
async listFiles(_directory: string): Promise<string[]> {
throw new Error("Not implemented");
}
/**
* Should open system camera to take a picture.
* @returns Promise that should resolve to captured image data
* @throws Error with "Not implemented" message
* @todo Implement camera access using Electron's media APIs
*/
async takePicture(): Promise<ImageResult> {
logger.error("takePicture not implemented in Electron platform");
throw new Error("Not implemented");
}
/**
* Should open system file picker for selecting an image.
* @returns Promise that should resolve to selected image data
* @throws Error with "Not implemented" message
* @todo Implement file picker using Electron's dialog API
*/
async pickImage(): Promise<ImageResult> {
logger.error("pickImage not implemented in Electron platform");
throw new Error("Not implemented");
}
/**
* Should handle deep link URLs for the desktop application.
* @param _url - The deep link URL to handle
* @throws Error with "Not implemented" message
* @todo Implement deep link handling using Electron's protocol handler
*/
async handleDeepLink(_url: string): Promise<void> {
logger.error("handleDeepLink not implemented in Electron platform");
throw new Error("Not implemented");
}
/**
* @see PlatformService.dbQuery
*/
async dbQuery(sql: string, params?: unknown[]): Promise<QueryExecResult> {
await this.initializeDatabase();
if (!this.db) {
throw new Error("Database not initialized");
}
try {
const result = await this.db.query(sql, params || []);
const values = result.values || [];
return {
columns: [], // SQLite plugin doesn't provide column names in query result
values: values as SqlValue[][],
};
} catch (error) {
logger.error("Error executing query:", error);
throw new Error(
`Database query failed: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
/**
* @see PlatformService.dbExec
*/
async dbExec(
sql: string,
params?: unknown[],
): Promise<{ changes: number; lastId?: number }> {
await this.initializeDatabase();
if (!this.db) {
throw new Error("Database not initialized");
}
try {
const result = await this.db.run(sql, params || []);
const changes = result.changes as Changes;
return {
changes: changes?.changes || 0,
lastId: changes?.lastId,
};
} catch (error) {
logger.error("Error executing statement:", error);
throw new Error(
`Database execution failed: ${error instanceof Error ? error.message : String(error)}`,
);
}
}
/**
* Rotates the camera between front and back cameras.
* @returns Promise that resolves when the camera is rotated
* @throws Error indicating camera rotation is not implemented in Electron
*/
async rotateCamera(): Promise<void> {
throw new Error("Camera rotation not implemented in Electron platform");
}
}

28
tsconfig.electron.json

@ -1,28 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"target": "ES2020",
"outDir": "dist-electron",
"rootDir": "src",
"sourceMap": true,
"esModuleInterop": true,
"allowJs": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"allowImportingTsExtensions": true,
"types": ["vite/client"],
"paths": {
"@/*": ["./src/*"]
},
"typeRoots": ["./node_modules/@types", "./src/types"]
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
]
}

23
vite.config.common.mts

@ -13,28 +13,27 @@ const __dirname = path.dirname(__filename);
export async function createBuildConfig(mode: string): Promise<UserConfig> {
const appConfig = await loadAppConfig();
const isElectron = mode === "electron";
const isCapacitor = mode === "capacitor";
const isNative = isElectron || isCapacitor;
const isNative = isCapacitor;
// Explicitly set platform and disable PWA for Electron
// Set platform and disable PWA for native platforms
process.env.VITE_PLATFORM = mode;
process.env.VITE_PWA_ENABLED = isElectron ? 'false' : 'true';
process.env.VITE_DISABLE_PWA = isElectron ? 'true' : 'false';
process.env.VITE_PWA_ENABLED = isCapacitor ? 'false' : 'true';
process.env.VITE_DISABLE_PWA = isCapacitor ? 'true' : 'false';
if (isElectron || isCapacitor) {
if (isCapacitor) {
process.env.VITE_PWA_ENABLED = 'false';
}
return {
base: isElectron ? "./" : "/",
base: "/",
plugins: [vue()],
server: {
port: parseInt(process.env.VITE_PORT || "8080"),
fs: { strict: false },
},
build: {
outDir: isElectron ? "dist-electron" : "dist",
outDir: "dist",
assetsDir: 'assets',
chunkSizeWarningLimit: 1000,
rollupOptions: {
@ -57,9 +56,9 @@ export async function createBuildConfig(mode: string): Promise<UserConfig> {
define: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.VITE_PLATFORM': JSON.stringify(mode),
'process.env.VITE_PWA_ENABLED': JSON.stringify(!isElectron),
'process.env.VITE_DISABLE_PWA': JSON.stringify(isElectron),
__dirname: isElectron ? JSON.stringify(process.cwd()) : '""',
'process.env.VITE_PWA_ENABLED': JSON.stringify(!isCapacitor),
'process.env.VITE_DISABLE_PWA': JSON.stringify(isCapacitor),
__dirname: JSON.stringify(process.cwd()),
__IS_MOBILE__: JSON.stringify(isCapacitor),
__USE_QR_READER__: JSON.stringify(!isCapacitor),
'process.platform': JSON.stringify('browser'),
@ -88,7 +87,7 @@ export async function createBuildConfig(mode: string): Promise<UserConfig> {
'@nostr/tools',
'@nostr/tools/nip06',
],
exclude: isElectron ? [
exclude: isCapacitor ? [
'register-service-worker',
'workbox-window',
'web-push',

154
vite.config.electron.mts

@ -1,154 +0,0 @@
import { defineConfig, mergeConfig } from "vite";
import { createBuildConfig } from "./vite.config.common.mts";
import path from 'path';
export default defineConfig(async () => {
const baseConfig = await createBuildConfig('electron');
return mergeConfig(baseConfig, {
build: {
outDir: 'dist-electron',
rollupOptions: {
input: {
// Main process entry points
main: path.resolve(__dirname, 'src/electron/main.ts'),
preload: path.resolve(__dirname, 'src/electron/preload.js'),
// Renderer process entry point (the web app)
app: path.resolve(__dirname, 'index.html'),
},
external: ['electron'],
output: {
format: 'cjs',
entryFileNames: (chunkInfo) => {
// Use different formats for main process vs renderer
if (chunkInfo.name === 'main' || chunkInfo.name === 'preload') {
return '[name].js';
}
return 'assets/[name].[hash].js';
},
assetFileNames: (assetInfo) => {
// Keep main process files in root, others in assets
if (assetInfo.name === 'main.js' || assetInfo.name === 'preload.js') {
return '[name].[ext]';
}
return 'assets/[name].[hash].[ext]';
},
chunkFileNames: 'assets/[name].[hash].js',
},
},
target: 'node18',
minify: false,
sourcemap: true,
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
optimizeDeps: {
include: ['@/utils/logger']
},
plugins: [
{
name: 'typescript-transform',
transform(code: string, id: string) {
if (id.endsWith('main.ts')) {
// Replace the logger import with inline logger
return code.replace(
/import\s*{\s*logger\s*}\s*from\s*['"]@\/utils\/logger['"];?/,
`const logger = {
log: (...args) => console.log(...args),
error: (...args) => console.error(...args),
info: (...args) => console.info(...args),
warn: (...args) => console.warn(...args),
debug: (...args) => console.debug(...args),
};`
);
}
return code;
}
},
{
name: 'remove-sw-imports',
transform(code: string, id: string) {
// Remove service worker imports and registrations
if (id.includes('registerServiceWorker') ||
id.includes('register-service-worker') ||
id.includes('sw_scripts') ||
id.includes('PushNotificationPermission') ||
code.includes('navigator.serviceWorker')) {
return {
code: code
.replace(/import.*registerServiceWorker.*$/mg, '')
.replace(/import.*register-service-worker.*$/mg, '')
.replace(/navigator\.serviceWorker/g, 'undefined')
.replace(/if\s*\([^)]*serviceWorker[^)]*\)\s*{[^}]*}/g, '')
.replace(/import.*workbox.*$/mg, '')
.replace(/importScripts\([^)]*\)/g, '')
};
}
return code;
}
},
{
name: 'remove-sw-files',
enforce: 'pre',
resolveId(id: string) {
// Prevent service worker files from being included
if (id.includes('sw.js') ||
id.includes('workbox') ||
id.includes('registerSW.js') ||
id.includes('manifest.webmanifest')) {
return '\0empty';
}
return null;
},
load(id: string) {
if (id === '\0empty') {
return 'export default {}';
}
return null;
}
},
{
name: 'electron-css-injection',
enforce: 'post',
generateBundle(options, bundle) {
// Find the main CSS file
const cssAsset = Object.values(bundle).find(
(asset: any) => asset.type === 'asset' && asset.fileName?.endsWith('.css')
) as any;
if (cssAsset) {
// Find the HTML file and inject CSS link
const htmlAsset = Object.values(bundle).find(
(asset: any) => asset.type === 'asset' && asset.fileName?.endsWith('.html')
) as any;
if (htmlAsset) {
const cssHref = `./${cssAsset.fileName}`;
const cssLink = ` <link rel="stylesheet" href="${cssHref}">\n`;
// Check if CSS link already exists
if (!htmlAsset.source.includes(cssHref)) {
// Inject CSS link after the title tag
htmlAsset.source = htmlAsset.source.replace(
/(<title>.*?<\/title>)/,
`$1\n${cssLink}`
);
console.log(`[electron-css-injection] Injected CSS link: ${cssHref}`);
} else {
console.log(`[electron-css-injection] CSS link already present: ${cssHref}`);
}
}
}
}
}
],
ssr: {
noExternal: ['@/utils/logger']
},
base: './',
publicDir: 'public',
});
});

31
vite.config.electron.renderer.mts

@ -1,31 +0,0 @@
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';
export default defineConfig({
plugins: [vue()],
base: './',
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
build: {
outDir: 'dist-electron/www',
emptyOutDir: false,
assetsDir: 'assets',
cssCodeSplit: false,
rollupOptions: {
input: path.resolve(__dirname, 'src/main.electron.ts'),
output: {
entryFileNames: 'main.electron.js',
assetFileNames: 'assets/[name]-[hash][extname]',
chunkFileNames: 'assets/[name]-[hash].js',
manualChunks: undefined
}
},
commonjsOptions: {
transformMixedEsModules: true
}
}
});
Loading…
Cancel
Save