| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
| |
Android holds 'normal' pushes while the phone dozes and drops them once
the 1 h TTL runs out; a pocketed phone missed most zaps and mentions
that the laptop showed. Reactions and community chat stay normal.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011bvzKaZosXGHkigcGcSzAR
|
| |
|
|
|
|
|
|
|
|
|
|
| |
The notifier now watches kind-7 reactions p-tagged at the user (opt-in
by an explicit flag, so older apps that can't word them stay quiet) and
sends zap receipts as 'zap' to apps that report the category, 'payment'
to older ones. Coinos zap receipts carry their amount into the push. The
service worker names the reactor when it knows them and stays quiet for
a muted one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VP5Ljqk7EYcm5QQAbzDwog
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
The push notifier now watches kind 1 for the user's pubkey beside DMs and
zap receipts, and pushes who, what, and an excerpt. The worker shows
"New reply to your post" or "You were mentioned" with the text, and a tap
lands in the Notifications list — as ?open=notifs when a window has to
be opened, or as a worker message when one already is. It has its own
switch, "Replies and mentions on nostr", in Settings → Notifications,
on by default like payments and DMs.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Every awake device of a wallet hears a wallet-connect pay request within
the same second, and the per-tab lock only reaches one browser profile:
two profiles on one laptop each locked an HTLC for the same 21-sat zap.
The push notifier now hands each pay request to the first device that
claims it; a device that loses the claim stands down silently, and a
device that cannot reach the notifier answers as before so nobody goes
quiet over a hiccup. The worker path claims the same way.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
| |
|
|
|
|
|
| |
notifications went silent. registerPush reused whatever getSubscription() returned and only ever subscribed fresh when there was none; a subscription made against an OLD VAPID key can never receive a push (the notifier's send fails with a permanent 403, which was never pruned, and the client kept re-registering the same dead endpoint). Now the client fetches the current VAPID key first and, when an existing subscription's applicationServerKey doesn't match, unsubscribes and re-subscribes against the current key — so a device self-heals on next open. And the notifier prunes 403 endpoints alongside 404/410. Deployed nwcpush on cs
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
|
| |
|
|
|
|
|
| |
stops opening the staging PWA. The same seed derives the same nostr pubkey on mainnet and staging, so both PWAs registered push under one pubkey and the notifier — matching only by pubkey — fanned every mainnet payment out to the staging subscription too; tapping it opened staging.coinos.io. Now the client stamps its network on the push registration (pushWatch net: getNetwork()), the registrar declares its own network on the /notify call (CFG.forwarder.network), and nwcpush delivers a payment only to registrations whose net matches — with a rollout-safe fallback: a device that hasn't re-registered yet (net=null) still gets the push, and a caller that omits net still reaches everyone, so nobody goes dark mid-deploy. Deployed names + nwcpush on cs
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
|
| |
|
|
|
|
|
| |
honours server-side — and the status bar gets a real coinos badge instead of a black blob
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A NIP-17 wrap is signed by a throwaway key with the sender sealed under
two layers of nip44, so no server can filter DMs by who sent them. The
notifier now ships the wrap inside the push payload and the service
worker opens it locally: a message from someone you follow arrives with
their name on it, a stranger's is dropped, and your own sent-copy — every
DM is wrapped to the sender too — no longer notifies you about what you
just typed.
That needs the nostr identity key in the worker's IndexedDB, which
dm-inbox.js states plainly: for a seed-derived identity it is a second
copy of a key localStorage's seed already yields, and it exists only to
keep strangers out. A remote signer never hands its key over, so those
wallets store no secret and keep getting unfiltered notifications rather
than silence.
Silence is always the riskier failure here, so the rule leans the other
way whenever it isn't sure: a wrap we can't open notifies, an account
with no contact list published notifies (otherwise a new user's welcome
message would vanish), and anyone you already have a thread with counts
as known whether or not you follow them. The server-side DM cooldown
drops to a second — with the device discarding pushes, a throttle there
would let a discarded one swallow the message behind it.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The nwcpush notifier grows a second registration kind alongside NWC:
a notify watch of what it can see without keys — kind-1059 wraps
p-tagged at the user (DMs, direct invites), zap receipts (9735/9737),
and wraps authored by the user's community channel keys (chat). It
pushes a typed nudge, throttled per device (chat bursts collapse); the
service worker shows a generic notification unless a window is visible,
and tapping opens the app. The names registrar reports settled
lightning-address receives server-to-server (token-gated /notify), so
the common payment path notifies even with no nostr event in sight.
The app registers silently whenever permission is already granted and
keeps the watch current as communities change; a dismissible card on
the chat home offers to enable it otherwise. Ark zap receipts (9737)
join the relay whitelist so the payment watch can see them.
Also: the offline Spending receive pane now says you're offline and
offers a one-tap switch to Savings instead of spinning forever.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
CLINK offers (noffer): every wallet gets a static offer keypair (synced
across devices, first-minted wins) answering kind-21001 invoice
requests — from open tabs and from the service worker while closed,
which mints against the mirrored state (the record now carries chain-4
and chain-5 key windows) and raises an open-to-receive notification.
Zap requests ride along and their kind-9735 receipts publish once the
payment is claimed. The code is shown in the payment-address card for
pasting into a Nostr profile; the notifier watches and relays kind
21001 alongside NWC.
Payment names also gain a Lightning offer: the registrar creates a
BOLT 12 offer per name on the node and appends lno= to the DNS record;
a settle-forwarder pushes each received payment on to the name's ark
destination from a small float wallet (custody window: seconds between
Lightning settle and the ark send).
Name resolution now runs full client-side DNSSEC validation
(src/dnssec.js): binary DoH, root-anchor chain walk, RSA + ECDSA via
WebCrypto — the resolver is untrusted transport, verified against live
signed zones including the canonical BIP-353 test name.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The no-tap half of background wallet-connect. The push payload carries
the (encrypted, client-signed) request event; a bundled service-worker
responder (src/sw-nwc.js -> nwc-respond.js) finds the matching pouch in
IndexedDB, decrypts with the connection's service key, and answers
get_info/get_balance/list_transactions from pouch state. pay_invoice
pays through the swap bridge with an ArkManager running over the
pouch's chain-6 key window (one HTLC lock, one HTTP swap), enforcing
per-payment and daily budgets from the pouch's own spend log, checking
the notifier's /answered endpoint so it never double-pays or shouts
over an open device, and persisting the swap before handing it off so
the app can refund a crashed flight. Anything it can't handle falls
back to the wake-the-user notification.
The notifier grows /publish (verified 23195s only, rate-limited) as the
worker's reply pipe and /answered for race avoidance. The app gains
pouch funding (mailbox-less arkoor self-send whose recipient bytes we
keep), emptying, spend/budget reconciliation on open, and refund
recovery with the pouch's own keys.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
nostr-tools ≥2.23 changed subscribeMany to take one filter object; the
old array form now serializes as ["REQ",id,[{...}]], which every relay
answers with CLOSED "provided filter is not an object". That killed the
NWC request subscription (the Amethyst forever-spinner), the device-sync
live subscription, and the push notifier's relay watch — all silently.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
|
A service worker cannot hold a relay socket open, so something must tell a
closed browser "a request arrived". This watches the relays for NIP-47
requests addressed to the wallet-service pubkeys a device registers, and
sends that device a Web Push. The service worker will do the actual work in
the browser; keys never leave it.
Scoped to leak as little as possible: the notifier learns the service pubkeys
registered and the timing of requests to them, and nothing else. Request
contents are encrypted to keys it doesn't hold, it never sees the wallet seed,
and it cannot move funds — the worst it can do is fail to wake a device.
No database or accounts: registrations live in one JSON file, expire if a
device stops refreshing, and push endpoints that return 404/410 are dropped.
Wake-ups are de-duplicated across relays and only fire for live traffic, so a
replayed old request can't wake anyone.
Co-Authored-By: Claude Fable 5 <[email protected]>
|