diff --git a/.husky/commit-msg b/.husky/commit-msg index 4b8c242d..cc1ce805 100755 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -1,9 +1,7 @@ -#!/usr/bin/env bash # # Husky Commit Message Hook # Validates commit message format using commitlint # -. "$(dirname -- "$0")/_/husky.sh" # Run commitlint but don't fail the commit (|| true) # This provides helpful feedback without blocking commits diff --git a/.husky/pre-commit b/.husky/pre-commit index b31dbcc0..f122047f 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,9 +1,7 @@ -#!/usr/bin/env bash # # Husky Pre-commit Hook # Runs lint-fix and Build Architecture Guard on staged files # -. "$(dirname -- "$0")/_/husky.sh" echo "πŸ” Running pre-commit hooks..." diff --git a/.husky/pre-push b/.husky/pre-push index 2d79fde4..67780a48 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,9 +1,7 @@ -#!/usr/bin/env bash # # Husky Pre-push Hook # Runs Build Architecture Guard to check commits being pushed # -. "$(dirname -- "$0")/_/husky.sh" echo "πŸ” Running Build Architecture Guard (pre-push)..." diff --git a/CHANGELOG.md b/CHANGELOG.md index 9514f07d..1fb1bbff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [?] - 2026 +### Added +- Full flow for setting up SMS notifications: phone registration and code + verification, delegated alertSearch JWT batches with a send hour, and + revocation to stop the texts +- Push-channel client for the notify-api's alert authorization + (`authorizePushAlertSearch`, `revokePushAlertSearch`), built on the same batch + minter as the SMS channel + + ## [1.3.8] - 2026 ### Added - Device wake-up for notifications diff --git a/README.md b/README.md index 2af6c457..0f4688f9 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,15 @@ Assumes Xcode and Xcode Command Line Tools are installed. See [BUILDING.md](BUILDING.md) for comprehensive build instructions for all platforms (Web, Electron, iOS, Android, Docker). +## Tests + +``` +npm run test:web + +# ... or do every check: +npm run test:all +``` + ## πŸ›‘οΈ Build Architecture Guard This project uses **Husky Git hooks** to protect the build system diff --git a/doc/local-android-testing-ngrok.md b/doc/local-android-testing-ngrok.md index 00a9754d..38c7788e 100644 --- a/doc/local-android-testing-ngrok.md +++ b/doc/local-android-testing-ngrok.md @@ -14,24 +14,24 @@ End-to-end flow when testing New Activity / silent wake on a physical Android ph ```text β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” HTTPS β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Mac (localhost) β”‚ ◄───────────── β”‚ ngrok edge β”‚ -β”‚ notification- β”‚ tunnel β”‚ (public HTTPS URL) β”‚ +β”‚ Mac (localhost) β”‚ ◄───────────── β”‚ ngrok edge β”‚ +β”‚ notification- β”‚ tunnel β”‚ (public HTTPS URL) β”‚ β”‚ wakeup-service β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ - β”‚ β”‚ fetch - β”‚ POST /notifications/refresh β”‚ POST /notifications/register - β”‚ β–Ό + β”‚ β”‚ fetch + β”‚ POST /notifications/refresh β”‚ POST /notifications/register + β”‚ β–Ό β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” - β”‚ β”‚ crowd-funder-for- β”‚ - β”‚ β”‚ time-pwa (Capacitor β”‚ - β”‚ β”‚ Android on device) β”‚ + β”‚ β”‚ crowd-funder-for- β”‚ + β”‚ β”‚ time-pwa (Capacitor β”‚ + β”‚ β”‚ Android on device) β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ FCM data message (WAKEUP_PING) β”‚ daily-notification-plugin β–Ό β–Ό (local schedule replace) β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Firebase Cloud β”‚ ──FCM────────► β”‚ Android device β”‚ -β”‚ Messaging β”‚ direct β”‚ app.timesafari.app β”‚ +β”‚ Firebase Cloud β”‚ ──FCM────────► β”‚ Android device β”‚ +β”‚ Messaging β”‚ direct β”‚ app.timesafari.app β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` @@ -578,7 +578,7 @@ curl -sS -w "\nHTTP %{http_code}\n" "$BASE/health" 3. Confirm **Backend Status β†’ URL** matches the saved ngrok host. 4. Enable **Test Mode** if using dev backend behavior. -**Expected outcome:** **Active** URL in the panel equals your ngrok `https://…` host. Subsequent app requests use that base (not `DEFAULT_NOTIFY_API_SERVER`) for `/notifications/register` and `/notifications/refresh`. +**Expected outcome:** **Active** URL in the panel equals your ngrok `https://…` host. Subsequent app requests use that base (not the default `DEFAULT_NOTIFY_API_SERVER`) for `/notifications/register` and `/notifications/refresh`. --- diff --git a/doc/local-ios-testing-ngrok.md b/doc/local-ios-testing-ngrok.md index 12d982a9..c091f067 100644 --- a/doc/local-ios-testing-ngrok.md +++ b/doc/local-ios-testing-ngrok.md @@ -281,6 +281,7 @@ Before ngrok end-to-end testing, confirm: ## 6. Configure the Notification Debug Panel backend override + The app normally calls `DEFAULT_NOTIFY_API_SERVER` (from `VITE_DEFAULT_NOTIFY_API_SERVER`, falling back to `AppString.PROD_NOTIFY_API_SERVER`). That is independent of `APP_SERVER`. For local wakeup testing, override the notification API base URL in the Debug Panel without rebuilding. For a full panel reference (configuration, URL resolution order, authentication, and troubleshooting), see [notification-debug-panel.md](./notification-debug-panel.md). diff --git a/doc/sms-notifications.md b/doc/sms-notifications.md new file mode 100644 index 00000000..7a3c8001 --- /dev/null +++ b/doc/sms-notifications.md @@ -0,0 +1,30 @@ +# SMS Registration + +All text providers now require 10DLC registration, which is a horrendous process. (I can refer you to others who have also found the process to be a nightmare. I just tried to look up docs on the official pages and found broken links... cool.) + +The functionality here mirrors the server-push FCM functionality. We're taking this approach as well because A) iOS client-side notifications are unreliable, and B) some users prefer to get text messages. + +## Details on iOS client-side problems + +iOS in particular makes it impossible to guarantee that the user will get notifications, +even if we separate the data-fetch from the user-notify as designed in the daily-notification-plugin + +You can see more details here: https://chatgpt.com/share/69e601ea-6434-8398-8d28-f1a3118f86ad +... which explains: + +``` +That implies one of these patterns: + +- Polling (setInterval / timers / background fetch) +- Service worker / PWA background sync +- App wake-up logic (foreground or semi-background) + +All three are fragile or outright blocked on iOS. + +Unlike Android, iOS has these restrictions: + +- No persistent timers when app is backgrounded +- No reliable background fetch at exact times +- No service worker push for non-installed PWAs (and even then, limited) +- No β€œwake up at X time and run JS” +``` diff --git a/package-lock.json b/package-lock.json index bf3ef7ea..3e190ce7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -153,7 +153,8 @@ "ts-jest": "^29.4.0", "tsx": "^4.20.4", "typescript": "~5.2.2", - "vite": "^5.2.0" + "vite": "^5.2.0", + "vue-tsc": "^2.1.10" } }, "node_modules/@adraffy/ens-normalize": { @@ -11605,6 +11606,35 @@ "vue": "^3.2.25" } }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, "node_modules/@vue-leaflet/vue-leaflet": { "version": "0.10.1", "license": "MIT", @@ -11663,6 +11693,17 @@ "@vue/shared": "3.5.13" } }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, "node_modules/@vue/devtools-api": { "version": "6.6.4", "license": "MIT" @@ -11890,6 +11931,31 @@ "node": ">=4.0" } }, + "node_modules/@vue/language-core": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.1.10.tgz", + "integrity": "sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~2.4.8", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^0.2.0", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@vue/reactivity": { "version": "3.5.13", "license": "MIT", @@ -12155,6 +12221,13 @@ "ajv": "^6.9.1" } }, + "node_modules/alien-signals": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.2.2.tgz", + "integrity": "sha512-cZIRkbERILsBOXTQmMrxc9hgpxglstn69zm+F1ARf4aPAzdAFYd6sBq87ErO0Fj3DV94tglcyHG5kQz9nDC/8A==", + "dev": true, + "license": "MIT" + }, "node_modules/anser": { "version": "1.4.10", "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", @@ -15004,6 +15077,13 @@ "version": "1.11.13", "license": "MIT" }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.4", "license": "MIT", @@ -23436,6 +23516,13 @@ "license": "Apache-2.0", "optional": true }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/multibase": { "version": "4.0.6", "license": "MIT", @@ -30292,6 +30379,13 @@ "optional": true, "peer": true }, + "node_modules/vscode-uri": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.2.0.tgz", + "integrity": "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==", + "dev": true, + "license": "MIT" + }, "node_modules/vue": { "version": "3.5.13", "license": "MIT", @@ -30433,6 +30527,24 @@ "vue": "^3.2.0" } }, + "node_modules/vue-tsc": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.1.10.tgz", + "integrity": "sha512-RBNSfaaRHcN5uqVqJSZh++Gy/YUzryuv9u1aFWhsammDJXNtUiJMNoJ747lZcQ68wUQFx6E73y4FY3D8E7FGMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "~2.4.8", + "@vue/language-core": "2.1.10", + "semver": "^7.5.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, "node_modules/walker": { "version": "1.0.8", "devOptional": true, diff --git a/package.json b/package.json index dddf9695..a8addc95 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src", "type-safety-check": "./scripts/type-safety-check.sh", "type-check": "tsc --noEmit", + "type-check:vue": "vue-tsc --noEmit", "prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js && node scripts/copy-wasm.js", "test:prerequisites": "node scripts/check-prerequisites.js", "check:dependencies": "./scripts/check-dependencies.sh", @@ -282,6 +283,7 @@ "ts-jest": "^29.4.0", "tsx": "^4.20.4", "typescript": "~5.2.2", - "vite": "^5.2.0" + "vite": "^5.2.0", + "vue-tsc": "^2.1.10" } } diff --git a/src/components/ChoiceButtonDialog.vue b/src/components/ChoiceButtonDialog.vue index 21624aae..3dae5622 100644 --- a/src/components/ChoiceButtonDialog.vue +++ b/src/components/ChoiceButtonDialog.vue @@ -25,6 +25,7 @@

{{ text }}

-
- -
+ + +
+
New Activity Notification
+

+ Get told about new relevant activity for you. Choose a delivery + channel below -- you can turn on either one, or both. +

+
+ + +
+ +
+
@@ -178,7 +202,12 @@ >
-
+
+ + Troubleshoot your in-app notifications… + +
+
@@ -191,17 +220,128 @@ class="w-full text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md" @click="editNewActivityNotification" > - Edit New Activity Notification… + Edit In-App Time…
-
- - Troubleshoot your notifications… - + + +
+ +
+
+ +
+ +
+
+
+
+
+
+ +
+ Texting: {{ notifyingNewActivitySmsPhone }} +
+ +
+
+ +
+
+
+ New Activity Text + +
+ +
+ +
+
+
+
+
+
+
+ Time: + {{ notifyingNewActivitySmsTime.replace(" ", " ") }} +
+
+
+ +
+
+ +
-
-

Claim Server

-
-

- Claim Server Configuration -

- - - - -
+

+ Claim Server Configuration +

+ + + + +
+ + + +
+
+ + + + +
+ + + +
+

+ Notification Push Server +

+
+ +
+ + When that setting is blank, this app will use the default web push + server URL: + {{ DEFAULT_PUSH_SERVER }} +
- - - -
- - -

- Notification Push Server + Partner Server URL

- - When that setting is blank, this app will use the default web push + + When that setting is blank, this app will use the default partner server URL: - {{ DEFAULT_PUSH_SERVER }} + {{ DEFAULT_PARTNER_API_SERVER }} -
-

Partner Server URL

-
- - - - - -
- - When that setting is blank, this app will use the default partner server - URL: - {{ DEFAULT_PARTNER_API_SERVER }} - +
+ Image Server URL +   + {{ DEFAULT_IMAGE_API_SERVER }} +
-
- Image Server URL -   - {{ DEFAULT_IMAGE_API_SERVER }} +
+ Notify Server URL +   + {{ notifyApiServer }} + +
diff --git a/src/views/dev/NotificationDebugView.vue b/src/views/dev/NotificationDebugView.vue index abb0254b..d210afbf 100644 --- a/src/views/dev/NotificationDebugView.vue +++ b/src/views/dev/NotificationDebugView.vue @@ -1,13 +1,18 @@