Skip to content

feat(push): APNs device-token registration (N0 plumbing) - #31

Open
lemmy-winks wants to merge 1 commit into
mainfrom
claude/apns-device-registration
Open

feat(push): APNs device-token registration (N0 plumbing)#31
lemmy-winks wants to merge 1 commit into
mainfrom
claude/apns-device-registration

Conversation

@lemmy-winks

Copy link
Copy Markdown
Owner

The last N0 server item from docs/ios-implementation-plan.md. Delivery is still N3 — this stores tokens so there's a fleet to send to the moment the auth key lands.

What changed

push_subs grows a token_type discriminator (web | apns), an additive column migrated in the main.py lifespan like favorite was. APNs rows put the device token in endpoint with empty key columns — reusing the column keeps per-user uniqueness and dedupe identical across both channels rather than adding a parallel nullable one.

POST /api/push/device registers a token; POST /api/push/device/unregister drops it. APNS_KEY_ID / APNS_TEAM_ID / APNS_PRIVATE_KEY are admin-overridable settings with blank compose placeholders, and /api/push/config now reports apns so the app can be honest about whether push actually works yet.

The bug this would have caused

send_push queried every push_subs row for a user and handed each to pywebpush. The moment a device token was stored, every web push send would have started failing on it — silently, in a log line, on a channel nobody watches closely.

It now filters to token_type == 'web', with a test that pins it by stubbing pywebpush and asserting only the web endpoint is reached.

Two things worth a look

Registration is idempotent, and reassigns. iOS reissues device tokens on reinstall and restore, so the app re-registers on every launch — accumulating rows would be the naive outcome. Re-registering also moves the row to the current user: with two seats in one household, a handed-over phone continuing to receive the previous owner's notifications is a realistic hazard, not a hypothetical.

Tokens are normalised. Older iOS Data.description yields <aabb ccdd …>; that's accepted and folded to the same 64-hex-char form rather than stored as a second spelling of one device. Anything that isn't 64 hex chars is a 422.

Not included

The client half. N0 scopes this as "stubbed server-side", and the app cannot obtain a token until the Push Notifications capability is added in Xcode (handover A2). Wiring a Settings toggle that fails confusingly until then would be worse than leaving it.

Tests

8 new (scoping, idempotency, reassignment, normalisation, malformed input, and the web-push isolation regression). 73 green. One existing assertion in test_smoke.py updated — it compared /api/push/config as an exact dict.

🤖 Generated with Claude Code

The last N0 server item. Delivery is still N3 — this stores the tokens so
there's a fleet to send to the moment the auth key lands.

push_subs grows a token_type discriminator ('web' | 'apns', additive column
migrated in the lifespan). APNs rows put the device token in `endpoint` with
empty key columns: reusing the column keeps per-user uniqueness and dedupe
identical across both channels.

The bit that would have broken production: send_push queried every row for a
user and handed each to pywebpush, so a stored device token would have failed
every web push send. It now filters to token_type == 'web', with a test that
pins it.

POST /api/push/device is idempotent — iOS reissues tokens on reinstall and
restore, so the app re-registers every launch. Re-registering also reassigns
the row to the current user, so a handed-over phone stops receiving the
previous owner's notifications; with two seats in one household that's a real
hazard, not a hypothetical. Tokens are normalised (the `<aabb ccdd>` spelling
older iOS produces) and validated as 64 hex chars.

APNS_KEY_ID / APNS_TEAM_ID / APNS_PRIVATE_KEY are admin-overridable settings
with blank compose placeholders; /api/push/config reports `apns` so the app can
be honest about whether push works yet.

Not included: the client half. N0 scopes this as "stubbed server-side", and the
app can't obtain a token until the Push Notifications capability is added in
Xcode (handover A2).

Tests: 8 new, 73 green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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