feat(push): APNs device-token registration (N0 plumbing) - #31
Open
lemmy-winks wants to merge 1 commit into
Open
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_subsgrows atoken_typediscriminator (web|apns), an additive column migrated in themain.pylifespan likefavoritewas. APNs rows put the device token inendpointwith 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/deviceregisters a token;POST /api/push/device/unregisterdrops it.APNS_KEY_ID/APNS_TEAM_ID/APNS_PRIVATE_KEYare admin-overridable settings with blank compose placeholders, and/api/push/confignow reportsapnsso the app can be honest about whether push actually works yet.The bug this would have caused
send_pushqueried everypush_subsrow for a user and handed each topywebpush. 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 stubbingpywebpushand 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.descriptionyields<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.pyupdated — it compared/api/push/configas an exact dict.🤖 Generated with Claude Code