Compare commits
12 Commits
entitygrid
...
build-with
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a2966a13e | |||
| bfa75484f3 | |||
| f71bd9fb42 | |||
| 2bd191e255 | |||
| 5905ae871a | |||
| 65877627c7 | |||
| cbe8c2e427 | |||
| 538c2a4369 | |||
| 36469a7fd2 | |||
| 43c5a28153 | |||
| b6c932b22c | |||
| 322c785119 |
@@ -3,7 +3,7 @@
|
||||
# iOS doesn't like spaces in the app title.
|
||||
TIME_SAFARI_APP_TITLE="TimeSafari_Test"
|
||||
VITE_APP_SERVER=https://test.timesafari.app
|
||||
# This is the claim ID for actions in the BVC project, with the JWT ID on this environment (not production).
|
||||
# This is the claim ID for actions in the BVC project.
|
||||
VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F
|
||||
VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -8,6 +8,7 @@ signature.bin
|
||||
# generated during `npm run build`
|
||||
sw_scripts-combined.js
|
||||
*.pem
|
||||
tsconfig.node.tsbuildinfo
|
||||
verified.txt
|
||||
myenv
|
||||
|
||||
@@ -40,19 +41,15 @@ pnpm-debug.log*
|
||||
playwright-tests
|
||||
dist-electron-packages
|
||||
.ruby-version
|
||||
+.env
|
||||
|
||||
# Test files generated by scripts test-ios.js & test-android.js
|
||||
.generated/
|
||||
|
||||
.env.default
|
||||
vendor/
|
||||
|
||||
# Build logs
|
||||
build_logs/
|
||||
|
||||
# PWA icon files generated by capacitor-assets
|
||||
icons
|
||||
|
||||
|
||||
android/app/src/main/res/
|
||||
android/app/src/main/res/
|
||||
|
||||
@@ -7,8 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
|
||||
## [Unreleased]
|
||||
### Fixed
|
||||
- Set the environment variables correctly (so simulator deep link domain is right).
|
||||
### Changed
|
||||
- Photo is pinned to profile mode
|
||||
- Photo is pinned to profile mode.
|
||||
- NODE_ENV is now mandatory.
|
||||
|
||||
|
||||
## [1.0.2] - 2025.06.20 - 276e0a741bc327de3380c4e508cccb7fee58c06d
|
||||
|
||||
38
README.md
38
README.md
@@ -3,49 +3,22 @@
|
||||
[Time Safari](https://timesafari.org/) allows people to ease into collaboration: start with expressions of gratitude
|
||||
and expand to crowd-fund with time & money, then record and see the impact of contributions.
|
||||
|
||||
## Database Migration Status
|
||||
|
||||
**Current Status**: The application is undergoing a migration from Dexie (IndexedDB) to SQLite using absurd-sql. This migration is in **Phase 2** with a well-defined migration fence in place.
|
||||
|
||||
### Migration Progress
|
||||
- ✅ **SQLite Database Service**: Fully implemented with absurd-sql
|
||||
- ✅ **Platform Service Layer**: Unified database interface across platforms
|
||||
- ✅ **Settings Migration**: Core user settings transferred
|
||||
- ✅ **Account Migration**: Identity and key management
|
||||
- 🔄 **Contact Migration**: User contact data (via import interface)
|
||||
- 📋 **Code Cleanup**: Remove unused Dexie imports
|
||||
|
||||
### Migration Fence
|
||||
The migration is controlled by a **migration fence** that separates legacy Dexie code from the new SQLite implementation. See [Migration Fence Definition](doc/migration-fence-definition.md) for complete details.
|
||||
|
||||
**Key Points**:
|
||||
- Legacy Dexie database is disabled by default (`USE_DEXIE_DB = false`)
|
||||
- All database operations go through `PlatformService`
|
||||
- Migration tools provide controlled access to both databases
|
||||
- Clear separation between legacy and new code
|
||||
|
||||
### Migration Documentation
|
||||
- [Migration Guide](doc/migration-to-wa-sqlite.md) - Complete migration process
|
||||
- [Migration Fence Definition](doc/migration-fence-definition.md) - Fence boundaries and rules
|
||||
- [Database Migration Guide](doc/database-migration-guide.md) - User-facing migration tools
|
||||
|
||||
## Roadmap
|
||||
|
||||
See [project.task.yaml](project.task.yaml) for current priorities.
|
||||
(Numbers at the beginning of lines are estimated hours. See [taskyaml.org](https://taskyaml.org/) for details.)
|
||||
See [ClickUp](https://sharing.clickup.com/9014278710/l/h/8cmnyhp-174/10573fec74e2ba0) for current priorities.
|
||||
|
||||
## Setup & Building
|
||||
|
||||
Quick start:
|
||||
|
||||
* For setup, we recommend [pkgx](https://pkgx.dev), which installs what you need (either automatically or with the `dev` command). Core dependencies are typescript & npm; when building for other platforms, you'll need other things such as those in the pkgx.yaml & BUILDING.md files.
|
||||
* For setup, we recommend [pkgx](https://pkgx.dev), which installs what you need (either automatically or with the `dev` command). Core dependencies are typescript & npm; when building for other platforms, you'll need other things such as those in the pkgx.yaml & doc/BUILDING.md files.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
NODE_ENV=dev npm run start:web
|
||||
```
|
||||
|
||||
See [BUILDING.md](BUILDING.md) for more details.
|
||||
See [BUILDING.md](doc/BUILDING.md) for more details.
|
||||
|
||||
## Tests
|
||||
|
||||
@@ -97,9 +70,6 @@ The application uses a platform-agnostic database layer:
|
||||
|
||||
**Development Guidelines**:
|
||||
- Always use `PlatformService` for database operations
|
||||
- Never import Dexie directly in application code
|
||||
- Test with `USE_DEXIE_DB = false` for new features
|
||||
- Use migration tools for data transfer between systems
|
||||
|
||||
### Kudos
|
||||
|
||||
|
||||
@@ -11,17 +11,6 @@ For a quick dev environment setup, use [pkgx](https://pkgx.dev).
|
||||
- Git
|
||||
- For desktop builds: Additional build tools based on your OS
|
||||
|
||||
## Forks
|
||||
|
||||
If you have forked this to make your own app, you'll want to customize the iOS & Android files. You can either edit existing ones, or you can remove the `ios` and `android` directories and regenerate them before the `npx cap sync` step in each setup.
|
||||
|
||||
```bash
|
||||
npx cap add android
|
||||
npx cap add ios
|
||||
```
|
||||
|
||||
You'll also want to edit the deep link configuration (see below).
|
||||
|
||||
## Initial Setup
|
||||
|
||||
Install dependencies:
|
||||
@@ -33,7 +22,7 @@ Install dependencies:
|
||||
## Web Dev Locally
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
NODE_ENV=dev npm run start:web
|
||||
```
|
||||
|
||||
## Web Build for Server
|
||||
@@ -42,7 +31,7 @@ Install dependencies:
|
||||
|
||||
```bash
|
||||
rm -rf dist
|
||||
npm run build:web
|
||||
NODE_ENV=prod npm run build:web
|
||||
```
|
||||
|
||||
The built files will be in the `dist` directory.
|
||||
@@ -52,7 +41,7 @@ Install dependencies:
|
||||
You'll likely want to use test locations for the Endorser & image & partner servers; see "DEFAULT_ENDORSER_API_SERVER" & "DEFAULT_IMAGE_API_SERVER" & "DEFAULT_PARTNER_API_SERVER" below.
|
||||
|
||||
```bash
|
||||
npm run serve
|
||||
NODE_ENV=dev npm run serve:web
|
||||
```
|
||||
|
||||
### Compile and minify for test & production
|
||||
@@ -63,7 +52,7 @@ Install dependencies:
|
||||
|
||||
* Update the ClickUp tasks & CHANGELOG.md & the version in package.json, run `npm install`.
|
||||
|
||||
* Run a build to make sure package-lock version is updated, linting works, etc: `npm install && npm run build`
|
||||
* Run a build to make sure package-lock version is updated, linting works, etc: `npm install && npm run build:web`
|
||||
|
||||
* Commit everything (since the commit hash is used the app).
|
||||
|
||||
@@ -74,7 +63,7 @@ Install dependencies:
|
||||
* For test, build the app (because test server is not yet set up to build):
|
||||
|
||||
```bash
|
||||
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.app VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch VITE_DEFAULT_PUSH_SERVER=https://test.timesafari.app VITE_PASSKEYS_ENABLED=true npm run build:web
|
||||
NODE_ENV=test TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.app VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch VITE_DEFAULT_PUSH_SERVER=https://test.timesafari.app VITE_PASSKEYS_ENABLED=true npm run build:web
|
||||
```
|
||||
|
||||
... and transfer to the test server:
|
||||
@@ -216,10 +205,10 @@ docker run -d \
|
||||
|
||||
```bash
|
||||
# For AppImage (recommended)
|
||||
npm run electron:build-linux
|
||||
npm run build:electron-linux
|
||||
|
||||
# For .deb package
|
||||
npm run electron:build-linux-deb
|
||||
npm run build:electron-linux-deb
|
||||
```
|
||||
|
||||
3. The packaged applications will be in `dist-electron-packages/`:
|
||||
@@ -231,19 +220,19 @@ docker run -d \
|
||||
1. Build the electron app in production mode:
|
||||
|
||||
```bash
|
||||
npm run build:web
|
||||
NODE_ENV=prod npm run build:web
|
||||
npm run build:electron
|
||||
npm run electron:build-mac
|
||||
npm run build:electron-mac
|
||||
```
|
||||
|
||||
2. Package the Electron app for macOS:
|
||||
|
||||
```bash
|
||||
# For Intel Macs
|
||||
npm run electron:build-mac
|
||||
npm run build:electron-mac
|
||||
|
||||
# For Universal build (Intel + Apple Silicon)
|
||||
npm run electron:build-mac-universal
|
||||
npm run build:electron-mac-universal
|
||||
```
|
||||
|
||||
3. The packaged applications will be in `dist-electron-packages/`:
|
||||
@@ -265,7 +254,7 @@ For public distribution on macOS, you need to code sign and notarize your app:
|
||||
|
||||
2. Build with signing:
|
||||
```bash
|
||||
npm run electron:build-mac
|
||||
npm run build:electron-mac
|
||||
```
|
||||
|
||||
### Running the Packaged App
|
||||
@@ -304,10 +293,10 @@ For testing the Electron build before packaging:
|
||||
|
||||
```bash
|
||||
# Build and run in development mode (includes DevTools)
|
||||
npm run electron:dev
|
||||
npm run build:electron
|
||||
|
||||
# Build in production mode and test
|
||||
npm run build:electron-prod && npm run electron:start
|
||||
npm run build:electron-prod && npm run start:electron
|
||||
```
|
||||
|
||||
## Mobile Builds (Capacitor)
|
||||
@@ -479,3 +468,12 @@ You must add the following intent filter to the `android/app/src/main/AndroidMan
|
||||
```
|
||||
|
||||
... though when we tried that most recently it failed to 'build' the APK with: http(s) scheme and host attribute are missing, but are required for Android App Links [AppLinkUrlError]
|
||||
|
||||
## Forks
|
||||
|
||||
If you have forked this to make your own app, you'll want to customize the iOS & Android files. You can either edit existing ones, or you can remove the `ios` and `android` directories and regenerate them before the `npx cap sync` step in each setup.
|
||||
|
||||
```bash
|
||||
npx cap add android
|
||||
npx cap add ios
|
||||
```
|
||||
@@ -27,6 +27,32 @@ The Database Migration feature allows you to compare and migrate data between De
|
||||
- Clear success and error messaging
|
||||
- Export functionality for comparison data
|
||||
|
||||
## Database Migration Status
|
||||
|
||||
**Current Status**: The application is undergoing a migration from Dexie (IndexedDB) to SQLite using absurd-sql. This migration is in **Phase 2** with a well-defined migration fence in place.
|
||||
|
||||
### Migration Progress
|
||||
- ✅ **SQLite Database Service**: Fully implemented with absurd-sql
|
||||
- ✅ **Platform Service Layer**: Unified database interface across platforms
|
||||
- ✅ **Settings Migration**: Core user settings transferred
|
||||
- ✅ **Account Migration**: Identity and key management
|
||||
- 🔄 **Contact Migration**: User contact data (via import interface)
|
||||
- 📋 **Code Cleanup**: Remove unused Dexie imports
|
||||
|
||||
### Migration Fence
|
||||
The migration is controlled by a **migration fence** that separates legacy Dexie code from the new SQLite implementation. See [Migration Fence Definition](doc/migration-fence-definition.md) for complete details.
|
||||
|
||||
**Key Points**:
|
||||
- Legacy Dexie database is disabled by default (`USE_DEXIE_DB = false`)
|
||||
- All database operations go through `PlatformService`
|
||||
- Migration tools provide controlled access to both databases
|
||||
- Clear separation between legacy and new code
|
||||
|
||||
### Migration Documentation
|
||||
- [Migration Guide](doc/migration-to-wa-sqlite.md) - Complete migration process
|
||||
- [Migration Fence Definition](doc/migration-fence-definition.md) - Fence boundaries and rules
|
||||
- [Database Migration Guide](doc/database-migration-guide.md) - User-facing migration tools
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Enable Dexie Database
|
||||
|
||||
12
index.html
12
index.html
@@ -15,17 +15,21 @@
|
||||
<script type="module">
|
||||
const platform = process.env.VITE_PLATFORM;
|
||||
switch (platform) {
|
||||
case 'capacitor':
|
||||
case 'capacitor': // BuildPlatform.Capacitor
|
||||
import('./src/main.capacitor.ts');
|
||||
break;
|
||||
case 'electron':
|
||||
case 'electron': // BuildPlatform.Electron
|
||||
import('./src/main.electron.ts');
|
||||
break;
|
||||
case 'pywebview':
|
||||
case 'pywebview': // BuildPlatform.PyWebView
|
||||
import('./src/main.pywebview.ts');
|
||||
break;
|
||||
default:
|
||||
case 'web': // BuildPlatform.Web
|
||||
import('./src/main.web.ts');
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown platform: ${platform}`);
|
||||
throw new Error(`Unknown platform: ${platform}`);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
63
package.json
63
package.json
@@ -6,44 +6,37 @@
|
||||
"name": "Time Safari Team"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite --config vite.config.dev.mts --host",
|
||||
"serve": "vite preview",
|
||||
"build": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.mts",
|
||||
"lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src",
|
||||
"lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src",
|
||||
"prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js && node scripts/copy-wasm.js",
|
||||
"test:all": "npm run test:prerequisites && npm run build && npm run test:web && npm run test:mobile",
|
||||
"test:prerequisites": "node scripts/check-prerequisites.js",
|
||||
"test:web": "npx playwright test -c playwright.config-local.ts --trace on",
|
||||
"test:mobile": "npm run build:capacitor && npm run test:android && npm run test:ios",
|
||||
"test:android": "node scripts/test-android.js",
|
||||
"test:ios": "node scripts/test-ios.js",
|
||||
"build-start:pywebview": "vite build --config vite.config.pywebview.mts && .venv/bin/python src/pywebview/main.py",
|
||||
"build:android": "npm run clean:android && rm -rf dist && npm run build:web && npm run build:capacitor && cd android && ./gradlew clean && ./gradlew assembleDebug && cd .. && npx cap sync android && npx capacitor-assets generate --android && npx cap open android",
|
||||
"build:capacitor": "vite build --config vite.config.capacitor.mts",
|
||||
"build:electron": "npm run clean:electron && tsc -p tsconfig.electron.json && vite build --config vite.config.electron.mts && node scripts/build-electron.js",
|
||||
"build:electron-linux": "npm run build:electron && electron-builder --linux AppImage",
|
||||
"build:electron-linux-deb": "npm run build:electron && electron-builder --linux deb",
|
||||
"build:electron-linux-prod": "NODE_ENV=prod npm run build:electron && electron-builder --linux AppImage",
|
||||
"build:electron-mac": "npm run build:electron-prod && electron-builder --mac",
|
||||
"build:electron-mac-universal": "npm run build:electron-prod && electron-builder --mac --universal",
|
||||
"build:electron-prod": "NODE_ENV=prod npm run build:electron",
|
||||
"build:pywebview": "vite build --config vite.config.pywebview.mts",
|
||||
"build:web": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --config vite.config.web.mts",
|
||||
"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:pywebview": "vite build --config vite.config.pywebview.mts",
|
||||
"build:electron": "npm run clean:electron && tsc -p tsconfig.electron.json && vite build --config vite.config.electron.mts && node scripts/build-electron.js",
|
||||
"build:capacitor": "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": "npm run build && electron .",
|
||||
"electron:start": "electron .",
|
||||
"clean:android": "adb uninstall app.timesafari.app || true",
|
||||
"build:android": "npm run clean:android && rm -rf dist && npm run build:web && npm run build:capacitor && cd android && ./gradlew clean && ./gradlew assembleDebug && cd .. && npx cap sync android && npx capacitor-assets generate --android && npx cap open android",
|
||||
"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",
|
||||
"pywebview:package-linux": "vite build --mode pywebview --config vite.config.pywebview.mts && .venv/bin/python -m PyInstaller --name TimeSafari --add-data 'dist:www' src/pywebview/main.py",
|
||||
"pywebview:package-win": "vite build --mode pywebview --config vite.config.pywebview.mts && .venv/Scripts/python -m PyInstaller --name TimeSafari --add-data 'dist;www' src/pywebview/main.py",
|
||||
"pywebview:package-mac": "vite build --mode pywebview --config vite.config.pywebview.mts && .venv/bin/python -m PyInstaller --name TimeSafari --add-data 'dist:www' src/pywebview/main.py",
|
||||
"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": "npm run build:electron-prod && electron-builder --mac",
|
||||
"electron:build-mac-universal": "npm run build:electron-prod && electron-builder --mac --universal"
|
||||
"clean:electron": "rimraf dist-electron",
|
||||
"lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src",
|
||||
"lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src",
|
||||
"package:pywebview-linux": "vite build --config vite.config.pywebview.mts && .venv/bin/python -m PyInstaller --name TimeSafari --add-data 'dist:www' src/pywebview/main.py",
|
||||
"package:pywebview-mac": "vite build --config vite.config.pywebview.mts && .venv/bin/python -m PyInstaller --name TimeSafari --add-data 'dist:www' src/pywebview/main.py",
|
||||
"package:pywebview-win": "vite build --config vite.config.pywebview.mts && .venv/Scripts/python -m PyInstaller --name TimeSafari --add-data 'dist;www' src/pywebview/main.py",
|
||||
"prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js && node scripts/copy-wasm.js",
|
||||
"serve:web": "vite preview",
|
||||
"start:electron": "electron .",
|
||||
"start:web": "vite --config vite.config.web.mts --host",
|
||||
"test:all": "npm run test:prerequisites && npm run build && npm run test:web && npm run test:mobile",
|
||||
"test:android": "node scripts/test-android.js",
|
||||
"test:ios": "node scripts/test-ios.js",
|
||||
"test:mobile": "npm run build:capacitor && npm run test:android && npm run test:ios",
|
||||
"test:prerequisites": "node scripts/check-prerequisites.js",
|
||||
"test:web": "npx playwright test -c playwright.config-local.ts --trace on"
|
||||
},
|
||||
"dependencies": {
|
||||
"@capacitor-community/sqlite": "6.0.2",
|
||||
|
||||
@@ -101,7 +101,7 @@ export default defineConfig({
|
||||
/**
|
||||
* This could be an array of servers, meaning we could start the Endorser server as well:
|
||||
* {
|
||||
* command: "cd ../endorser-ch; NODE_ENV=test-local npm run dev",
|
||||
* command: "cd ../endorser-ch; NODE_ENV=test-local npm run start:web",
|
||||
* url: 'http://localhost:3000',
|
||||
* reuseExistingServer: !process.env.CI,
|
||||
* },
|
||||
@@ -112,7 +112,7 @@ export default defineConfig({
|
||||
*/
|
||||
webServer: {
|
||||
command:
|
||||
"VITE_APP_SERVER=http://localhost:8081 VITE_DEFAULT_ENDORSER_API_SERVER=http://localhost:3000 VITE_DEFAULT_PARTNER_API_SERVER=http://localhost:3000 VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_PASSKEYS_ENABLED=true npm run dev -- --port=8081",
|
||||
"NODE_ENV=dev VITE_APP_SERVER=http://localhost:8081 VITE_DEFAULT_ENDORSER_API_SERVER=http://localhost:3000 VITE_DEFAULT_PARTNER_API_SERVER=http://localhost:3000 VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_PASSKEYS_ENABLED=true npm run start:web -- --port=8081",
|
||||
url: "http://localhost:8081",
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
|
||||
@@ -74,7 +74,7 @@ export default defineConfig({
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command:
|
||||
// "VITE_PASSKEYS_ENABLED=true VITE_DEFAULT_ENDORSER_API_SERVER=http://localhost:3000 npm run dev",
|
||||
// "VITE_PASSKEYS_ENABLED=true VITE_DEFAULT_ENDORSER_API_SERVER=http://localhost:3000 npm run start:web",
|
||||
// url: "http://localhost:8080",
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// },
|
||||
|
||||
@@ -50,8 +50,8 @@ backup and database export, with platform-specific download instructions. * *
|
||||
v-if="platformCapabilities.isMobile && !platformCapabilities.isIOS"
|
||||
class="list-disc list-outside ml-4"
|
||||
>
|
||||
On Android: You will be prompted to choose a location to save your
|
||||
backup file.
|
||||
On Android: You will be prompted to choose an app for sharing your
|
||||
backup file. To save on your phone, you will need a file manager app.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ export enum AppString {
|
||||
// This is used in titles and verbiage inside the app.
|
||||
// There is also an app name without spaces, for packaging in the package.json file used in the manifest.
|
||||
APP_NAME = "Time Safari",
|
||||
// iOS doesn't like spaces in the app title.
|
||||
APP_NAME_NO_SPACES = "TimeSafari",
|
||||
|
||||
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
|
||||
|
||||
@@ -29,6 +29,7 @@ import { arrayBufferToBase64 } from "@/libs/crypto";
|
||||
|
||||
const randomBytes = crypto.getRandomValues(new Uint8Array(32));
|
||||
const secretBase64 = arrayBufferToBase64(randomBytes);
|
||||
console.log('secretBase64', secretBase64); // useful while we have multiple DBs activating (at least on web)
|
||||
|
||||
// Each migration can include multiple SQL statements (with semicolons)
|
||||
const MIGRATIONS = [
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
const { contextBridge, ipcRenderer } = require("electron");
|
||||
import { NodeEnv, BuildPlatform } from "@/interfaces/build";
|
||||
|
||||
const logger = {
|
||||
log: (message, ...args) => {
|
||||
// Always log in development, log with context in production
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (process.env.NODE_ENV !== NodeEnv.Prod) {
|
||||
/* eslint-disable no-console */
|
||||
console.log(`[Preload] ${message}`, ...args);
|
||||
/* eslint-enable no-console */
|
||||
@@ -23,7 +24,7 @@ const logger = {
|
||||
},
|
||||
info: (message, ...args) => {
|
||||
// Always log info in development, log with context in production
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (process.env.NODE_ENV !== NodeEnv.Prod) {
|
||||
/* eslint-disable no-console */
|
||||
console.info(`[Preload] ${message}`, ...args);
|
||||
/* eslint-enable no-console */
|
||||
@@ -53,7 +54,7 @@ const getPath = (pathType) => {
|
||||
logger.info("Preload script starting...");
|
||||
|
||||
// Force electron platform in the renderer process
|
||||
window.process = { env: { VITE_PLATFORM: "electron" } };
|
||||
window.process = { env: { VITE_PLATFORM: BuildPlatform.Electron } };
|
||||
|
||||
try {
|
||||
contextBridge.exposeInMainWorld("electronAPI", {
|
||||
@@ -76,12 +77,12 @@ try {
|
||||
// Environment info
|
||||
env: {
|
||||
isElectron: true,
|
||||
isDev: process.env.NODE_ENV === "development",
|
||||
platform: "electron", // Explicitly set platform
|
||||
isDev: process.env.NODE_ENV === NodeEnv.Dev,
|
||||
platform: BuildPlatform.Electron, // Explicitly set platform
|
||||
},
|
||||
// Path utilities
|
||||
getBasePath: () => {
|
||||
return process.env.NODE_ENV === "development" ? "/" : "./";
|
||||
return process.env.NODE_ENV === NodeEnv.Dev ? "/" : "./";
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
21
src/interfaces/build.ts
Normal file
21
src/interfaces/build.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export const NodeEnv = {
|
||||
Dev: "dev",
|
||||
Test: "test",
|
||||
Prod: "prod",
|
||||
} as const;
|
||||
export type NodeEnv = typeof NodeEnv[keyof typeof NodeEnv];
|
||||
|
||||
export const BuildEnv = {
|
||||
Development: "development",
|
||||
Testing: "testing",
|
||||
Production: "production",
|
||||
} as const;
|
||||
export type BuildEnv = typeof BuildEnv[keyof typeof BuildEnv];
|
||||
|
||||
export const BuildPlatform = {
|
||||
Web: "web",
|
||||
Electron: "electron",
|
||||
Capacitor: "capacitor",
|
||||
PyWebView: "pywebview",
|
||||
} as const;
|
||||
export type BuildPlatform = typeof BuildPlatform[keyof typeof BuildPlatform];
|
||||
@@ -60,7 +60,7 @@ import {
|
||||
KeyMetaMaybeWithPrivate,
|
||||
} from "../interfaces/common";
|
||||
import { PlanSummaryRecord } from "../interfaces/records";
|
||||
import { logger } from "../utils/logger";
|
||||
import { logger, safeStringify } from "../utils/logger";
|
||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||
|
||||
/**
|
||||
@@ -437,19 +437,23 @@ export async function getHeaders(
|
||||
}
|
||||
headers["Authorization"] = "Bearer " + token;
|
||||
} catch (error) {
|
||||
// This rarely happens: we've seen it when they have account info but the
|
||||
// encryption secret got lost. But in most cases we want users to at
|
||||
// least see their feed -- and anything else that returns results for
|
||||
// anonymous users.
|
||||
|
||||
// We'll continue with an anonymous request... still want to show feed and other things, but ideally let them know.
|
||||
// This rarely happens: we've seen it when they have account info but the
|
||||
// encryption secret got lost.
|
||||
// Replicate this in Chrome: go to Storage and hit 'Clear site data'.
|
||||
// Check the util.ts retrieveFullyDecryptedAccount method where it calls simpleDecrypt.
|
||||
|
||||
// In most cases we want users to at least see their feed -- and anything
|
||||
// else that returns results for anonymous users.
|
||||
// We'll continue with an anonymous request... still want to show feed
|
||||
// and other things, but we need to let them know.
|
||||
logConsoleAndDb(
|
||||
"Something failed in getHeaders call (will proceed anonymously" +
|
||||
($notify ? " and notify user" : "") +
|
||||
"): " +
|
||||
// IntelliJ type system complains about getCircularReplacer() with: Argument of type '(obj: any, key: string, value: any) => any' is not assignable to parameter of type '(this: any, key: string, value: any) => any'.
|
||||
//JSON.stringify(error, getCircularReplacer()), // JSON.stringify(error) on a Dexie error throws another error about: Converting circular structure to JSON
|
||||
error,
|
||||
error + " - " + safeStringify(error),
|
||||
true,
|
||||
);
|
||||
if ($notify) {
|
||||
|
||||
@@ -605,7 +605,7 @@ export const retrieveFullyDecryptedAccount = async (
|
||||
dbAccount.values.length === 0 ||
|
||||
dbAccount.values[0].length === 0
|
||||
) {
|
||||
throw new Error("Account not found.");
|
||||
throw new Error("Account not found for did: " + activeDid);
|
||||
}
|
||||
const fullAccountData = databaseUtil.mapQueryResultToValues(
|
||||
dbAccount,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { initBackend } from "absurd-sql/dist/indexeddb-main-thread";
|
||||
|
||||
import { initializeApp } from "./main.common";
|
||||
import { logger } from "./utils/logger";
|
||||
import { BuildPlatform } from "@/interfaces/build";
|
||||
|
||||
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 (platform !== BuildPlatform.Electron && pwa_enabled) {
|
||||
import("./registerServiceWorker"); // Web PWA support
|
||||
}
|
||||
|
||||
@@ -25,7 +27,7 @@ function sqlInit() {
|
||||
// workers through the main thread
|
||||
initBackend(worker);
|
||||
}
|
||||
if (platform === "web" || platform === "development") {
|
||||
if (platform === BuildPlatform.Web) {
|
||||
sqlInit();
|
||||
} else {
|
||||
logger.warn("[Web] SQL not initialized for platform", { platform });
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { register } from "register-service-worker";
|
||||
import { NodeEnv, BuildPlatform } from "@/interfaces/build";
|
||||
|
||||
// Check if we're in an Electron environment
|
||||
const isElectron =
|
||||
process.env.VITE_PLATFORM === "electron" ||
|
||||
process.env.VITE_PLATFORM === BuildPlatform.Electron ||
|
||||
process.env.VITE_DISABLE_PWA === "true" ||
|
||||
window.navigator.userAgent.toLowerCase().includes("electron");
|
||||
|
||||
@@ -15,7 +16,7 @@ const isElectron =
|
||||
if (
|
||||
!isElectron &&
|
||||
process.env.VITE_PWA_ENABLED === "true" &&
|
||||
process.env.NODE_ENV === "production"
|
||||
process.env.NODE_ENV === NodeEnv.Prod
|
||||
) {
|
||||
register(`${process.env.BASE_URL}sw.js`, {
|
||||
ready() {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { WebPlatformService } from "./platforms/WebPlatformService";
|
||||
import { CapacitorPlatformService } from "./platforms/CapacitorPlatformService";
|
||||
import { ElectronPlatformService } from "./platforms/ElectronPlatformService";
|
||||
import { PyWebViewPlatformService } from "./platforms/PyWebViewPlatformService";
|
||||
import { BuildPlatform } from "@/interfaces/build";
|
||||
|
||||
/**
|
||||
* Factory class for creating platform-specific service implementations.
|
||||
@@ -35,19 +36,19 @@ export class PlatformServiceFactory {
|
||||
return PlatformServiceFactory.instance;
|
||||
}
|
||||
|
||||
const platform = process.env.VITE_PLATFORM || "web";
|
||||
const platform = process.env.VITE_PLATFORM || BuildPlatform.Web;
|
||||
|
||||
switch (platform) {
|
||||
case "capacitor":
|
||||
case BuildPlatform.Capacitor:
|
||||
PlatformServiceFactory.instance = new CapacitorPlatformService();
|
||||
break;
|
||||
case "electron":
|
||||
case BuildPlatform.Electron:
|
||||
PlatformServiceFactory.instance = new ElectronPlatformService();
|
||||
break;
|
||||
case "pywebview":
|
||||
case BuildPlatform.PyWebView:
|
||||
PlatformServiceFactory.instance = new PyWebViewPlatformService();
|
||||
break;
|
||||
case "web":
|
||||
case BuildPlatform.Web:
|
||||
default:
|
||||
PlatformServiceFactory.instance = new WebPlatformService();
|
||||
break;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import { AxiosError } from "axios";
|
||||
import { logger, safeStringify } from "../utils/logger";
|
||||
import { BuildPlatform } from "@/interfaces/build";
|
||||
|
||||
/**
|
||||
* Handles API errors with platform-specific logging and error processing.
|
||||
@@ -36,7 +37,7 @@ import { logger, safeStringify } from "../utils/logger";
|
||||
* ```
|
||||
*/
|
||||
export const handleApiError = (error: AxiosError, endpoint: string) => {
|
||||
if (process.env.VITE_PLATFORM === "capacitor") {
|
||||
if (process.env.VITE_PLATFORM === BuildPlatform.Capacitor) {
|
||||
const endpointStr = safeStringify(endpoint); // we've seen this as an object in deep links
|
||||
logger.error(`[Capacitor API Error] ${endpointStr}:`, {
|
||||
message: error.message,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { logToDb } from "../db/databaseUtil";
|
||||
import { BuildPlatform, NodeEnv } from "@/interfaces/build";
|
||||
|
||||
export function safeStringify(obj: unknown) {
|
||||
const seen = new WeakSet();
|
||||
@@ -21,7 +22,7 @@ export function safeStringify(obj: unknown) {
|
||||
|
||||
export const logger = {
|
||||
debug: (message: string, ...args: unknown[]) => {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (process.env.NODE_ENV !== NodeEnv.Prod) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(message, ...args);
|
||||
// const argsString = args.length > 0 ? " - " + safeStringify(args) : "";
|
||||
@@ -30,8 +31,8 @@ export const logger = {
|
||||
},
|
||||
log: (message: string, ...args: unknown[]) => {
|
||||
if (
|
||||
process.env.NODE_ENV !== "production" ||
|
||||
process.env.VITE_PLATFORM === "capacitor"
|
||||
process.env.NODE_ENV !== NodeEnv.Prod ||
|
||||
process.env.VITE_PLATFORM === BuildPlatform.Capacitor
|
||||
) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(message, ...args);
|
||||
@@ -41,9 +42,9 @@ export const logger = {
|
||||
},
|
||||
info: (message: string, ...args: unknown[]) => {
|
||||
if (
|
||||
process.env.NODE_ENV !== "production" ||
|
||||
process.env.VITE_PLATFORM === "capacitor" ||
|
||||
process.env.VITE_PLATFORM === "electron"
|
||||
process.env.NODE_ENV !== NodeEnv.Prod ||
|
||||
process.env.VITE_PLATFORM === BuildPlatform.Capacitor ||
|
||||
process.env.VITE_PLATFORM === BuildPlatform.Electron
|
||||
) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(message, ...args);
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
<div class="flex justify-center text-center text-sm leading-tight mb-1">
|
||||
People {{ profileImageUrl ? "without your image" : "" }} see this
|
||||
<br />
|
||||
(if you've let them see your activity):
|
||||
(if you've let them see which posts are yours):
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<EntityIcon
|
||||
@@ -1573,24 +1573,25 @@ export default class AccountViewView extends Vue {
|
||||
* @throws Will notify the user if there is an export error.
|
||||
*/
|
||||
public async exportDatabase() {
|
||||
try {
|
||||
// Generate the blob from the database
|
||||
const blob = await this.generateDatabaseBlob();
|
||||
throw new Error("Not implemented");
|
||||
// try {
|
||||
// // Generate the blob from the database
|
||||
// const blob = await this.generateDatabaseBlob();
|
||||
|
||||
// Create a temporary URL for the blob
|
||||
this.downloadUrl = this.createBlobURL(blob);
|
||||
// // Create a temporary URL for the blob
|
||||
// this.downloadUrl = this.createBlobURL(blob);
|
||||
|
||||
// Trigger the download
|
||||
this.downloadDatabaseBackup(this.downloadUrl);
|
||||
// // Trigger the download
|
||||
// this.downloadDatabaseBackup(this.downloadUrl);
|
||||
|
||||
// Notify the user that the download has started
|
||||
this.notifyDownloadStarted();
|
||||
// // Notify the user that the download has started
|
||||
// this.notifyDownloadStarted();
|
||||
|
||||
// Revoke the temporary URL -- after a pause to avoid DuckDuckGo download failure
|
||||
setTimeout(() => URL.revokeObjectURL(this.downloadUrl), 1000);
|
||||
} catch (error) {
|
||||
this.handleExportError(error);
|
||||
}
|
||||
// // Revoke the temporary URL -- after a pause to avoid DuckDuckGo download failure
|
||||
// setTimeout(() => URL.revokeObjectURL(this.downloadUrl), 1000);
|
||||
// } catch (error) {
|
||||
// this.handleExportError(error);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -100,6 +100,7 @@ import { Component, Vue } from "vue-facing-decorator";
|
||||
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||
|
||||
import { APP_SERVER } from "@/constants/app";
|
||||
import { NodeEnv } from "@/interfaces/build";
|
||||
import { logger } from "@/utils/logger";
|
||||
import { errorStringForLog } from "@/libs/endorserServer";
|
||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||
@@ -148,7 +149,7 @@ export default class DeepLinkRedirectView extends Vue {
|
||||
this.deepLinkUrl = `timesafari://${fullPathWithQuery}`;
|
||||
this.webUrl = `${APP_SERVER}/${fullPathWithQuery}`;
|
||||
|
||||
this.isDevelopment = process.env.NODE_ENV !== "production";
|
||||
this.isDevelopment = process.env.NODE_ENV !== NodeEnv.Prod;
|
||||
this.userAgent = navigator.userAgent;
|
||||
|
||||
this.openDeepLink();
|
||||
|
||||
@@ -102,8 +102,7 @@ Raymer * @version 1.0.0 */
|
||||
class="text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
|
||||
@click="showNameThenIdDialog()"
|
||||
>
|
||||
Show them {{ PASSKEYS_ENABLED ? "default" : "your" }} identifier
|
||||
info
|
||||
Show them your identification info
|
||||
</button>
|
||||
</div>
|
||||
<UserNameDialog ref="userNameDialog" />
|
||||
@@ -683,7 +682,7 @@ export default class HomeView extends Vue {
|
||||
group: "alert",
|
||||
type: "warning",
|
||||
title: "Feed Loading Issue",
|
||||
text: "Some feed data may be unavailable. Pull to refresh.",
|
||||
text: "Some feed data may be unavailable. Try refreshing the page.",
|
||||
},
|
||||
5000,
|
||||
);
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import * as path from "path";
|
||||
import { promises as fs } from "fs";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
export async function loadAppConfig() {
|
||||
const packageJson = await loadPackageJson();
|
||||
const appName = process.env.TIME_SAFARI_APP_TITLE || packageJson.name;
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
return {
|
||||
pwaConfig: {
|
||||
manifest: {
|
||||
name: appName,
|
||||
short_name: appName,
|
||||
icons: [
|
||||
{
|
||||
src: "./img/icons/android-chrome-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./img/icons/android-chrome-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./img/icons/android-chrome-maskable-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
purpose: "maskable",
|
||||
},
|
||||
{
|
||||
src: "./img/icons/android-chrome-maskable-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "maskable",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
aliasConfig: {
|
||||
"@": path.resolve(path.dirname(__dirname), "src"),
|
||||
buffer: path.resolve(path.dirname(__dirname), "node_modules", "buffer"),
|
||||
"dexie-export-import/dist/import":
|
||||
"dexie-export-import/dist/import/index.js",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function loadPackageJson() {
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const packageJsonPath = path.resolve(path.dirname(__dirname), "package.json");
|
||||
const packageJsonData = await fs.readFile(packageJsonPath, "utf-8");
|
||||
return JSON.parse(packageJsonData);
|
||||
}
|
||||
@@ -2,14 +2,14 @@ import { test, expect } from '@playwright/test';
|
||||
import { importUser, generateNewEthrUser, switchToUser } from './testUtils';
|
||||
|
||||
test('New offers for another user', async ({ page }) => {
|
||||
const user01Did = await generateNewEthrUser(page);
|
||||
const newUserDid = await generateNewEthrUser(page);
|
||||
await page.goto('./');
|
||||
await page.getByTestId('closeOnboardingAndFinish').click();
|
||||
await expect(page.getByTestId('newDirectOffersActivityNumber')).toBeHidden();
|
||||
|
||||
await importUser(page, '00');
|
||||
await page.goto('./contacts');
|
||||
await page.getByPlaceholder('URL or DID, Name, Public Key').fill(user01Did + ', A Friend');
|
||||
await page.getByPlaceholder('URL or DID, Name, Public Key').fill(newUserDid + ', A Friend');
|
||||
await expect(page.locator('button > svg.fa-plus')).toBeVisible();
|
||||
await page.locator('button > svg.fa-plus').click();
|
||||
await expect(page.locator('div[role="alert"] span:has-text("Contact Added")')).toBeVisible();
|
||||
@@ -18,7 +18,7 @@ test('New offers for another user', async ({ page }) => {
|
||||
await expect(page.locator('div[role="alert"] button > svg.fa-xmark')).toBeHidden(); // ensure alert is gone
|
||||
|
||||
// show buttons to make offers directly to people
|
||||
await page.getByRole('button').filter({ hasText: /See Hours/i }).click();
|
||||
await page.getByRole('button').filter({ hasText: /See Actions/i }).click();
|
||||
|
||||
// make an offer directly to user 1
|
||||
// Generate a random string of 3 characters, skipping the "0." at the beginning
|
||||
@@ -42,7 +42,7 @@ test('New offers for another user', async ({ page }) => {
|
||||
await expect(page.locator('div[role="alert"] button > svg.fa-xmark')).toBeHidden(); // ensure alert is gone
|
||||
|
||||
// as user 1, go to the home page and check that two offers are shown as new
|
||||
await switchToUser(page, user01Did);
|
||||
await switchToUser(page, newUserDid);
|
||||
await page.goto('./');
|
||||
let offerNumElem = page.getByTestId('newDirectOffersActivityNumber');
|
||||
await expect(offerNumElem).toHaveText('2');
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["vite.config.*"]
|
||||
"include": ["vite.config.*", "./src/interfaces/build.ts"]
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { defineConfig } from "vite";
|
||||
import { createBuildConfig } from "./vite.config.common.mts";
|
||||
import { BuildPlatform } from "./src/interfaces/build.ts";
|
||||
|
||||
export default defineConfig(async () => createBuildConfig('capacitor'));
|
||||
export default defineConfig(async () => createBuildConfig(BuildPlatform.Capacitor));
|
||||
@@ -45,71 +45,73 @@ interface PWAConfig {
|
||||
}
|
||||
|
||||
interface AppConfig {
|
||||
pwaConfig: PWAConfig;
|
||||
aliasConfig: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadAppConfig(): Promise<AppConfig> {
|
||||
const packageJson = await loadPackageJson();
|
||||
const appName = process.env.TIME_SAFARI_APP_TITLE || packageJson.name;
|
||||
|
||||
return {
|
||||
pwaConfig: {
|
||||
registerType: "autoUpdate",
|
||||
strategies: "injectManifest",
|
||||
srcDir: ".",
|
||||
filename: "sw_scripts-combined.js",
|
||||
manifest: {
|
||||
name: appName,
|
||||
short_name: appName,
|
||||
theme_color: "#4a90e2",
|
||||
background_color: "#ffffff",
|
||||
icons: [
|
||||
{
|
||||
src: "./img/icons/android-chrome-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./img/icons/android-chrome-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./img/icons/android-chrome-maskable-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
purpose: "maskable",
|
||||
},
|
||||
{
|
||||
src: "./img/icons/android-chrome-maskable-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "maskable",
|
||||
},
|
||||
],
|
||||
share_target: {
|
||||
action: "/share-target",
|
||||
method: "POST",
|
||||
enctype: "multipart/form-data",
|
||||
params: {
|
||||
files: [
|
||||
{
|
||||
name: "photo",
|
||||
accept: ["image/*"],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
aliasConfig: {
|
||||
"@": path.resolve(__dirname, "src"),
|
||||
buffer: path.resolve(__dirname, "node_modules", "buffer"),
|
||||
"dexie-export-import/dist/import":
|
||||
"dexie-export-import/dist/import/index.js",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadPwaConfig(): Promise<PWAConfig> {
|
||||
const packageJson = await loadPackageJson();
|
||||
const appName = process.env.TIME_SAFARI_APP_TITLE || packageJson.name;
|
||||
|
||||
return {
|
||||
registerType: "autoUpdate",
|
||||
strategies: "injectManifest",
|
||||
srcDir: ".",
|
||||
filename: "sw_scripts-combined.js",
|
||||
manifest: {
|
||||
name: appName,
|
||||
short_name: appName,
|
||||
theme_color: "#4a90e2",
|
||||
background_color: "#ffffff",
|
||||
icons: [
|
||||
{
|
||||
src: "./img/icons/android-chrome-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./img/icons/android-chrome-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./img/icons/android-chrome-maskable-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
purpose: "maskable",
|
||||
},
|
||||
{
|
||||
src: "./img/icons/android-chrome-maskable-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "maskable",
|
||||
},
|
||||
],
|
||||
share_target: {
|
||||
action: "/share-target",
|
||||
method: "POST",
|
||||
enctype: "multipart/form-data",
|
||||
params: {
|
||||
files: [
|
||||
{
|
||||
name: "photo",
|
||||
accept: ["image/*"],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,31 +1,45 @@
|
||||
import { defineConfig, UserConfig, Plugin } from "vite";
|
||||
import { defineConfig, UserConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import dotenv from "dotenv";
|
||||
import { loadAppConfig } from "./vite.config.utils.mts";
|
||||
import { loadAppConfig } from "./vite.config.common-utils.mts";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from 'url';
|
||||
import { NodeEnv, BuildEnv, BuildPlatform } from "./src/interfaces/build.ts";
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
let buildEnv: BuildEnv;
|
||||
if (process.env.NODE_ENV === NodeEnv.Dev) {
|
||||
buildEnv = BuildEnv.Development;
|
||||
} else if (process.env.NODE_ENV === NodeEnv.Test) {
|
||||
buildEnv = BuildEnv.Testing;
|
||||
} else if (process.env.NODE_ENV === NodeEnv.Prod) {
|
||||
buildEnv = BuildEnv.Production;
|
||||
} else {
|
||||
console.error("NODE_ENV is not set. Invoke with NODE_ENV=" + Object.values(NodeEnv).join("|"));
|
||||
throw new Error("NODE_ENV is not set. Invoke with NODE_ENV=" + Object.values(NodeEnv).join("|"));
|
||||
}
|
||||
console.log(`Environment: ${buildEnv}`);
|
||||
|
||||
dotenv.config({ path: `.env.${buildEnv}` });
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
export async function createBuildConfig(mode: string): Promise<UserConfig> {
|
||||
export async function createBuildConfig(platform: BuildPlatform): Promise<UserConfig> {
|
||||
const appConfig = await loadAppConfig();
|
||||
const isElectron = mode === "electron";
|
||||
const isCapacitor = mode === "capacitor";
|
||||
const isPyWebView = mode === "pywebview";
|
||||
|
||||
console.log(`Platform: ${platform}`);
|
||||
const isElectron = platform === BuildPlatform.Electron;
|
||||
const isCapacitor = platform === BuildPlatform.Capacitor;
|
||||
const isPyWebView = platform === BuildPlatform.PyWebView;
|
||||
|
||||
// Explicitly set platform and disable PWA for Electron
|
||||
process.env.VITE_PLATFORM = mode;
|
||||
process.env.VITE_PWA_ENABLED = isElectron ? 'false' : 'true';
|
||||
process.env.VITE_PLATFORM = platform;
|
||||
process.env.VITE_PWA_ENABLED = (isElectron || isPyWebView || isCapacitor)
|
||||
? 'false'
|
||||
: 'true';
|
||||
process.env.VITE_DISABLE_PWA = isElectron ? 'true' : 'false';
|
||||
|
||||
if (isElectron || isPyWebView || isCapacitor) {
|
||||
process.env.VITE_PWA_ENABLED = 'false';
|
||||
}
|
||||
|
||||
return {
|
||||
base: isElectron || isPyWebView ? "./" : "/",
|
||||
plugins: [vue()],
|
||||
@@ -56,7 +70,7 @@ 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_PLATFORM': JSON.stringify(platform),
|
||||
'process.env.VITE_PWA_ENABLED': JSON.stringify(!isElectron),
|
||||
'process.env.VITE_DISABLE_PWA': JSON.stringify(isElectron),
|
||||
__dirname: isElectron ? JSON.stringify(process.cwd()) : '""',
|
||||
@@ -78,10 +92,10 @@ export async function createBuildConfig(mode: string): Promise<UserConfig> {
|
||||
'path': path.resolve(__dirname, './src/utils/node-modules/path.js'),
|
||||
'fs': path.resolve(__dirname, './src/utils/node-modules/fs.js'),
|
||||
'crypto': path.resolve(__dirname, './src/utils/node-modules/crypto.js'),
|
||||
'nostr-tools/nip06': mode === 'development'
|
||||
'nostr-tools/nip06': buildEnv === BuildEnv.Development
|
||||
? 'nostr-tools/nip06'
|
||||
: path.resolve(__dirname, 'node_modules/nostr-tools/nip06'),
|
||||
'nostr-tools/core': mode === 'development'
|
||||
'nostr-tools/core': buildEnv === BuildEnv.Development
|
||||
? 'nostr-tools'
|
||||
: path.resolve(__dirname, 'node_modules/nostr-tools'),
|
||||
'nostr-tools': path.resolve(__dirname, 'node_modules/nostr-tools'),
|
||||
@@ -108,4 +122,4 @@ export async function createBuildConfig(mode: string): Promise<UserConfig> {
|
||||
};
|
||||
}
|
||||
|
||||
export default defineConfig(async () => createBuildConfig('web'));
|
||||
export default defineConfig(async () => createBuildConfig(BuildPlatform.Web));
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import { defineConfig } from "vite";
|
||||
import { createBuildConfig } from "./vite.config.common.mts";
|
||||
|
||||
export default defineConfig(async () => createBuildConfig('development'));
|
||||
@@ -1,9 +1,10 @@
|
||||
import { defineConfig, mergeConfig } from "vite";
|
||||
import { createBuildConfig } from "./vite.config.common.mts";
|
||||
import path from 'path';
|
||||
import { BuildPlatform } from "./src/interfaces/build.ts";
|
||||
|
||||
export default defineConfig(async () => {
|
||||
const baseConfig = await createBuildConfig('electron');
|
||||
const baseConfig = await createBuildConfig(BuildPlatform.Electron);
|
||||
|
||||
return mergeConfig(baseConfig, {
|
||||
build: {
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
'nostr-tools': path.resolve(__dirname, 'node_modules/nostr-tools'),
|
||||
'nostr-tools/nip06': path.resolve(__dirname, 'node_modules/nostr-tools/nip06'),
|
||||
'nostr-tools/core': path.resolve(__dirname, 'node_modules/nostr-tools/core'),
|
||||
stream: 'stream-browserify',
|
||||
util: 'util',
|
||||
crypto: 'crypto-browserify',
|
||||
assert: 'assert/',
|
||||
http: 'stream-http',
|
||||
https: 'https-browserify',
|
||||
url: 'url/',
|
||||
zlib: 'browserify-zlib',
|
||||
path: 'path-browserify',
|
||||
fs: false,
|
||||
tty: 'tty-browserify',
|
||||
net: false,
|
||||
dns: false,
|
||||
child_process: false,
|
||||
os: false
|
||||
},
|
||||
mainFields: ['module', 'jsnext:main', 'jsnext', 'main'],
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['nostr-tools', 'nostr-tools/nip06', 'nostr-tools/core'],
|
||||
esbuildOptions: {
|
||||
define: {
|
||||
global: 'globalThis'
|
||||
}
|
||||
}
|
||||
},
|
||||
build: {
|
||||
sourcemap: true,
|
||||
target: 'esnext',
|
||||
chunkSizeWarningLimit: 1000,
|
||||
commonjsOptions: {
|
||||
include: [/node_modules/],
|
||||
transformMixedEsModules: true
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [
|
||||
'stream', 'util', 'crypto', 'http', 'https', 'url', 'zlib',
|
||||
'path', 'fs', 'tty', 'assert', 'net', 'dns', 'child_process', 'os'
|
||||
],
|
||||
output: {
|
||||
globals: {
|
||||
stream: 'stream',
|
||||
util: 'util',
|
||||
crypto: 'crypto',
|
||||
http: 'http',
|
||||
https: 'https',
|
||||
url: 'url',
|
||||
zlib: 'zlib',
|
||||
path: 'path',
|
||||
assert: 'assert',
|
||||
tty: 'tty'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { defineConfig } from "vite";
|
||||
import { createBuildConfig } from "./vite.config.common.mts";
|
||||
import { BuildPlatform } from "./src/interfaces/build.ts";
|
||||
|
||||
export default defineConfig(async () => createBuildConfig('pywebview'));
|
||||
export default defineConfig(async () => createBuildConfig(BuildPlatform.PyWebView));
|
||||
@@ -1,53 +0,0 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
headers: {
|
||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||
'Cross-Origin-Embedder-Policy': 'require-corp'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
'nostr-tools': path.resolve(__dirname, 'node_modules/nostr-tools'),
|
||||
'nostr-tools/nip06': path.resolve(__dirname, 'node_modules/nostr-tools/nip06'),
|
||||
'nostr-tools/core': path.resolve(__dirname, 'node_modules/nostr-tools/core'),
|
||||
stream: 'stream-browserify',
|
||||
util: 'util',
|
||||
crypto: 'crypto-browserify'
|
||||
},
|
||||
mainFields: ['module', 'jsnext:main', 'jsnext', 'main'],
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['nostr-tools', 'nostr-tools/nip06', 'nostr-tools/core', '@jlongster/sql.js'],
|
||||
esbuildOptions: {
|
||||
define: {
|
||||
global: 'globalThis'
|
||||
}
|
||||
}
|
||||
},
|
||||
build: {
|
||||
sourcemap: true,
|
||||
target: 'esnext',
|
||||
chunkSizeWarningLimit: 1000,
|
||||
commonjsOptions: {
|
||||
include: [/node_modules/],
|
||||
transformMixedEsModules: true
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['stream', 'util', 'crypto'],
|
||||
output: {
|
||||
globals: {
|
||||
stream: 'stream',
|
||||
util: 'util',
|
||||
crypto: 'crypto'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
assetsInclude: ['**/*.wasm']
|
||||
});
|
||||
@@ -1,17 +1,18 @@
|
||||
import { defineConfig, mergeConfig } from "vite";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
import { createBuildConfig } from "./vite.config.common.mts";
|
||||
import { loadAppConfig } from "./vite.config.utils.mts";
|
||||
import { loadPwaConfig } from "./vite.config.common-utils.mts";
|
||||
import { BuildPlatform } from "./src/interfaces/build.ts";
|
||||
|
||||
export default defineConfig(async () => {
|
||||
const baseConfig = await createBuildConfig('web');
|
||||
const appConfig = await loadAppConfig();
|
||||
const baseConfig = await createBuildConfig(BuildPlatform.Web);
|
||||
const pwaConfig = await loadPwaConfig();
|
||||
|
||||
return mergeConfig(baseConfig, {
|
||||
plugins: [
|
||||
VitePWA({
|
||||
registerType: 'autoUpdate',
|
||||
manifest: appConfig.pwaConfig?.manifest,
|
||||
manifest: pwaConfig.manifest,
|
||||
devOptions: {
|
||||
enabled: false
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user