The iOS app's icon count, pushed by ipx (#433)

Background refresh runs when iOS chooses, and for this app that has
been rarely, so the icon's unread count lagged until the app was
opened (#421). Now ipx sends the count:
- ipx: a push_devices table and POST/DELETE /api/push/devices. With
  [push] set to an APNs key, a task compares each phone's All
  Subscriptions unread count with the last sent, once a minute, and
  sends a changed one to Apple as a badge-only push: no alert, no
  sound. A token Apple says is gone is forgotten. The signing token is
  ES256 over the team and time, made again every forty minutes, inside
  Apple's limits.
- iOS: the aps-environment entitlement. The app registers for push
  once badge permission is given, each launch, and hands its token to
  the page.
- Page: registers the token with ipx as the person signed in, since
  the player's address (ipxapi) lets through only the player's paths,
  and removes it on signing out.

jsonwebtoken here is built without PEM support, so the .p8 key's
base64 is decoded in push.rs rather than turning on a feature and
changing Cargo.lock for one file.

cargo test -p ipx on macOS: 182 pass; the 3 that fail read /proc and
fail there without this change too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Ray Slakinski
2026-11-08 17:47:28 -05:00
parent e6414941ab
commit bd535851b0
20 changed files with 506 additions and 5 deletions

View File

@@ -137,6 +137,30 @@ metrics_bind = "" # e.g. "0.0.0.0:9187"; empty serves no
It is plain HTTP. On a LAN bind everything crosses the network in the clear — and a feed URL can
itself carry a credential. Put TLS in front of it if that matters.
## `[push]`
```toml
[push]
key_file = "/config/AuthKey_ABC123DEFG.p8" # the APNs key Apple gives once
key_id = "ABC123DEFG" # shown beside it in the developer account
team_id = "8743L8D729" # the team the iOS app is signed by
topic = "net.sdf1.ipx" # the app's bundle id, the default
```
The iOS app's icon shows All Subscriptions' unread count. With this set, ipx keeps it right while
the app is closed (#433): once a minute it compares each phone's count with the last one sent, and
sends a changed one through Apple's push service, a badge alone, with no alert or sound. Without
it, the count waits on the background refresh iOS runs when it chooses.
The key is made in the Apple developer account under Certificates, Identifiers & Profiles, Keys,
with Apple Push Notifications service enabled, and can be downloaded once. One key serves every
app of the team, the TestFlight and debug builds both. Keep it as private as the database: it lets
anyone send to the team's apps. A key, id or team that is wrong shows as a warning in the log,
`push: Apple refused the signing token`, and nothing is sent until it is right.
The app registers each phone through the page as the person signed in there
(`POST /api/push/devices`), and signing out removes it.
## `[feeds.<id>]`
Kept in the database once ipx has moved them in: a feed's settings are changed in the web UI, and