Skip to content

fix: per-device push tokens, and an update prompt that actually clears - #308

Merged
chattermate merged 3 commits into
mainfrom
fix/multi-device-push-tokens
Aug 21, 2026
Merged

fix: per-device push tokens, and an update prompt that actually clears#308
chattermate merged 3 commits into
mainfrom
fix/multi-device-push-tokens

Conversation

@chattermate

Copy link
Copy Markdown
Owner

Description

Push notifications stopped reaching a signed-in phone and never came back. Two causes.

users.fcm_token_web held one token for the whole account, so signing in on a second device overwrote the first one's token and signing out anywhere cleared it for every device. Replaced with an fcm_tokens table, one row per browser: logout removes only the token it names, and sends fan out to every device. Tokens FCM reports as dead are now pruned instead of being swallowed, and a push skipped for want of a token is logged — that early return was silent, which is why the logs showed nothing.

The client then never recovered, because it skipped the POST whenever localStorage said the token was already synced. FCM returns the same token for the life of a browser install, so that cache never invalidated while the server value could change behind its back. It now posts on every load, so devices that are already stuck fix themselves on their next visit.

Also in here: the "new version of ChatterMate is available" toast reappearing on every page load. registerSW's updateSW ignores its reloadPage argument and only posts SKIP_WAITING without awaiting activation, so the explicit reload beat the handshake — the page came back on the old worker with the new one still waiting. The reload belongs to registerSW's own controlling listener.

Deploy note: migration fcm_tokens_per_device_001 copies existing tokens across and then drops users.fcm_token_web, so the backend has to go out with it. Anyone currently seeing the update toast will get it once more with the old handler and need a single manual refresh before the fix takes effect.

Related issues

Type of change

  • Bug fix
  • New feature
  • Refactor / chore
  • Documentation

Checklist

  • All commits are signed off (DCO) — git commit -s (see CONTRIBUTING.md)
  • My contribution is licensed under Apache-2.0
  • Tests added/updated where relevant and the suite passes
  • Documentation updated where relevant

…logout

Push stopped reaching a signed-in phone and never came back. Two causes:

users.fcm_token_web held one token for the whole account, so signing in on a
second device overwrote the first and signing out anywhere cleared it for every
device. It is replaced by an fcm_tokens table with a row per browser; logout
now removes only the token it names, and sends fan out to every device.

The client then never recovered, because it skipped the POST whenever
localStorage said the token was already synced. FCM returns the same token for
the life of a browser install, so that cache never invalidated while the server
value could change behind its back. It now posts on every load.

Also: prune tokens FCM reports as Unregistered/SenderIdMismatch instead of
swallowing the error, and log when a push is skipped for want of a token —
that early return was silent, which is why the logs showed nothing at all.

Signed-off-by: chattermate <admin@chattermate.chat>
Clicking Reload never applied the update, so the "new version available" toast
came back on every page load until an unrelated manual refresh happened to
activate the worker. One prompt per release is intended; an unclearable one is
not.

registerSW's updateSW ignores its reloadPage argument and only posts
SKIP_WAITING, without awaiting activation, so it resolves within a tick.
Reloading on that resolution beat the handshake every time: the document came
back under the old worker with the new one still in `waiting`, and the next
load re-dispatched `waiting`.

Leave the refresh to the `controlling` listener registerSW arms just before
calling onNeedRefresh, and keep a short fallback reload so a worker that never
takes control cannot strand anyone on the old build.

Signed-off-by: chattermate <admin@chattermate.chat>
Three issues from reviewing the branch:

Logout awaited the push-unregister before clearing the session, and that waits
on getSWRegistration(), which only settles after an 8s timeout when no worker
is ready — so signing out appeared to hang. Bound it to 3s; giving up early is
safe, since a browser with no ready worker cannot display a push anyway and the
row is pruned on its next failed send.

register() did check-then-insert. The client now posts on every app load, so
two tabs opening together both insert and the loser hit the unique constraint,
leaving that browser unregistered for the session. Retry against the winner's
row instead.

send_each rejects more than 500 messages outright, and pruning only reads
responses from a successful call — so an account over that limit could never
send or recover. Chunk the sends, carrying the token offset so dead tokens in
later chunks still map to the right row.

Signed-off-by: chattermate <admin@chattermate.chat>
@chattermate
chattermate merged commit ac1a758 into main Aug 21, 2026
7 checks passed
@chattermate
chattermate deleted the fix/multi-device-push-tokens branch August 21, 2026 10:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant