aboutsummaryrefslogtreecommitdiffstats
path: root/tools/unifiedpush-test.js
Commit message (Collapse)AuthorAgeFilesLines
* Browser tests: public relays answered in the page, never reachedAdam Soltys7 days1-1/+1
| | | | | | | | | | | | | | A suite run let ~90 throwaway test wallets subscribe and publish on public relays from one home address, and nos.lol and relay.damus.io banned it. tools/puppeteer.js wraps puppeteer-core: every socket to a non-local host gets an in-page stand-in relay (EVENT -> OK, REQ -> EOSE). Tests that read real content launch with { relays: 'read' } (real reads, publishes answered in place); feed-live, chat-layout and cordn-ui launch with { relays: 'live' }. LIVE_RELAYS=read|1|host,... overrides per run. profile-key-qr: measure the QR again if a repaint swapped it out. Co-Authored-By: Claude Opus 5.5 <[email protected]>
* First run: "Create a new wallet" shows the twelve words at once.Adam Soltys2026-09-211-1/+1
| | | | | | | | | | | | No create/import tabs and no Generate button inside the wizard: the words appear the moment the button is tapped. Underneath, "Have an existing seed or entropy to import?" swaps to the import pane (which has its own entropy door), and "Create a new wallet instead" swaps back with fresh words. The create/import card elsewhere (adding an account) keeps its tabs. Tests that entered through the old tab go through the new line. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Tmfg8k5f3TbNJS34qiqHAp
* The front door points at your own keys.Adam Soltys2026-09-131-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Four buttons of equal weight, and only the sign-in ones said what they did. "Get started" said nothing, so the concrete offer on that screen was Google — which is a convenience wrapped around a seed phrase, not an alternative to one. Now the page says what the wallet is before it offers a button, the way in reads "Create a new wallet" with the cost under it (a minute, twelve words), and the three sign-ins sit below a line labelled "or, if you already have an account". And the second half of the same problem: a wallet that arrives by sign-in has a recovery phrase its owner has never seen and nobody can reissue. So the wizard shows it once, right after the first login, while it still reads as setting up rather than as a warning out of nowhere. "I've written it down" is the way on; "Later" is a real answer, and the wallet then keeps one quiet line — not a modal, not dismissible, gone the moment the phrase is actually seen, here or in settings. seedSeen records that per account: creating shows the words, importing means they typed them, revealing in settings counts. Only a derived seed starts false, which is exactly the case worth asking about. tools/onboard-backup-test.js covers both halves, including that "Later" leaves a reminder that survives a reload and leads somewhere. The sixteen tests that clicked the old button by its text are updated, and it has an id now so its copy can change without breaking them again. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
* End to end over the real network, and the advice now fits the app it's in.Adam Soltys2026-09-121-1/+25
| | | | | | | | | | | | | | | | | | | | | | | | | | | A push sent to ntfy.sh reached a killed app on a real phone: 17:02:21.259 ntfy.sh → the phone's ntfy → broadcast to io.coinos.webview 17:02:21.308 app process started (it had been killed) 17:02:21.458 wake: job started 17:02:22.926 wake: finishing (page said so) — 1.7s, and "You were paid" One thing that took two goes: `adb shell am force-stop` marks a package STOPPED, and Android delivers no broadcasts to a stopped package unless the sender sets FLAG_INCLUDE_STOPPED_PACKAGES — which `am broadcast` does and a real distributor rightly doesn't. The first attempt looked like a failure and was a test artefact; `am kill`, which is what Android itself does to a backgrounded app, delivers fine. And the WebView build can now say something useful about push. It was inheriting the browser's line — "your browser has no push service, try Firefox" — which is nonsense inside a WebView that IS the app. It asks the host what's actually there and says: install a push app like ntfy (nothing installed), or wait a moment and reopen (a distributor that hasn't answered yet). Payments and messages still arrive whenever coinos is open, which is the part that matters most to someone reading an apology. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
* The wake had to be a job, not a foreground service — Android says so.Adam Soltys2026-09-121-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | Tested on a real phone, and the first attempt didn't work at all: Background started FGS: Disallowed [... code:DENIED ...] startForegroundService() not allowed due to mAllowStartForeground false Android 12 forbids starting a foreground service from a broadcast receiver. An expedited job is the sanctioned replacement — it runs promptly from the background and needs no notification of its own to apologise for existing — so WakeService is now WakeJob, and the notification is decided by what arrived rather than by whether we could wake: a payment or a message is news either way, a wallet-connect request the wallet answers by itself is not. Two more things the phone found. A job with a connectivity constraint needs ACCESS_NETWORK_STATE, or schedule() throws and nothing runs at all. And a wake with nothing to answer used to hold a WebView open for the full thirty seconds — a locked wallet, no wallet on the device, or no NWC connections all end the job immediately now. Also a bug worth naming: ctx carried `wakeDone` by shorthand, which captured the binding while it was still undefined (ctx is built before it's defined). The page could never say it had finished, so every wake ran its full timeout. It reads through a closure now, like its neighbour. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
* A second Android build that can wake the wallet, because it holds it.Adam Soltys2026-09-121-0/+14
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The TWA can't. Chrome renders the site, so the wallet's keys live in Chrome's storage for its own origin, and nothing outside Chrome can reach them — a UnifiedPush message could raise a notification and no more. On a phone with Google Play Services none of this matters; without it, background answering was simply unavailable. So: a second flavour, io.coinos.webview, "Coinos (UnifiedPush)". The wallet runs in a WebView this app owns, which means a push can start it off-screen and let it answer for itself. WakeService loads it with ?wake=<payload> into the same storage the visible app uses, the page subscribes and answers whatever the payload was about, then calls CoinosHost.done() so the service stops — capped at 25 seconds from the page and 30 from the service, because nothing here is allowed to sit in the background costing battery. Its foreground notification says what it's doing rather than apologising for existing. The WebView carries what the wallet actually needs: DOM storage and IndexedDB, the camera for the QR scanner (granted only once Android has granted it to us), a file chooser for attaching pictures, and links to anywhere but coinos.io handed to a real browser. The trade is named in the code and worth naming here: a separate app is a separate wallet. Its storage is its own, so signing in there is signing in again. That's why this is a second build rather than a replacement — the TWA is untouched, still the better browser for anyone whose phone has push. The shared push receiver now asks the system for whatever front door this build has, and asks a per-flavour Wake class whether the wallet can be woken at all — no flavour checks in the middle of it. Both APKs build and ship: coinos.apk as before, coinos-unifiedpush.apk beside it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
* UnifiedPush: reach a phone that has no Google in it.Adam Soltys2026-09-121-0/+83
Web push on Android is Firebase's, so a phone without Google Play Services — GrapheneOS, CalyxOS, any de-Googled Android — cannot be woken by a web app at all. UnifiedPush is the way that doesn't involve Google: the user runs a distributor (ntfy and friends), apps ask it for an endpoint, and whoever wants to reach the device POSTs to that endpoint. THE WRAPPER speaks the protocol directly — four broadcasts each way, so it pulls in no library; an APK this small has no business growing a Kotlin runtime for eight intents. It asks the distributor on every launch (the call is idempotent, and it's how a distributor installed later gets noticed), raises a notification when a message arrives, and acknowledges it so the distributor stops resending. THE ENDPOINT reaches the web app on the launch URL as ?up=, the same road payment intents already travel — because the distributor talks to the app and only the web app knows which pubkeys are worth watching. It's registered with the notifier in place of a browser subscription, for notifications and for NWC alike, and needs neither permission nor push service. THE NOTIFIER sends to either kind: one deliver() that POSTs the payload to a UnifiedPush endpoint, or hands it to web-push as before. Same payloads, same 404/410 pruning. The endpoint is the only secret there, so it's never logged in full. Verified: a POST to a live ntfy endpoint delivers the payload byte for byte, so the app parses exactly what the notifier sent; the APK builds, carries the receiver, and is signed with the certificate assetlinks already pins (v1.6). tools/unifiedpush-test.js drives a browser with no push service and checks the endpoint is taken off the URL, kept, and registered. Honest about the limit: a UnifiedPush message wakes the ANDROID app, not Chrome's service worker, and the wallet's keys live in the browser's storage where nothing here can reach them. So a zap request arrives as a notification you tap, and the wallet answers when it opens — one tap instead of none. Automatic answering while closed still needs a browser with a push service. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx