feat(push): real APNs delivery (token auth), inert without a key#131
Merged
Conversation
Implements the APNs path end-to-end so it's ready the moment an Apple push key is provided — no more "would notify" stub when configured: - apnsConfigFromEnv() reads LISA_APNS_KEY_ID/_TEAM_ID/_KEY (.p8 PEM or path) /_TOPIC/_ENV; returns null (→ stub log) when unset. - buildApnsJwt() signs the ES256 provider JWT (raw IEEE-P1363, cached ~50min). - buildApnsPayload() builds aps.alert + a `link` custom key (the deep-link twin of the ntfy Click). - sendApns() POSTs /3/device/<token> over HTTP/2 with the apns-* headers; the poster is injectable for tests. PushBridge now delivers via APNs when configured, else logs the stub. Verified: npm run typecheck && npm run build && npm test -> 817 pass / 1 skip, incl. new tests that sign+verify the ES256 JWT with a throwaway P-256 key and assert the request shaping. Live delivery still needs a real key + device token (the external dependency). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- realApnsPost: settle exactly once (a `settled` guard kills the
client.on("error")/req.on("error") double-resolve race), always close the
HTTP/2 client, and add a 10s request timeout so a hung connection can't leak a
never-settled promise.
- sendApns JWT cache is now keyed by config identity (keyId/teamId), not just
time — a rotated/second key can't reuse a stale token (wrong kid/iss → 403).
- Add apns-expiration (high → "0" deliver-now-or-drop; default → 1h) so a
transient operational alert isn't stored and delivered stale.
- agentDeepLink / buildPairUrl emit %20 for spaces instead of "+", which iOS
URLComponents reads literally — so a device label / value round-trips.
Verified: typecheck + build clean; npm test -> 818 pass / 1 skip (+ space-
encoding and apns-expiration assertions).
Co-Authored-By: Claude Opus 4.8 <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.
Real APNs delivery for operational push (the iOS-native path), implemented end-to-end so it works the moment an Apple push key is provided. Recreated PR — the original #129 was auto-closed when its stacked base branch (#127) was deleted during the merge; #127 is already on
main, so this carries only the APNs commits.apnsConfigFromEnv(LISA_APNS_KEY_ID/_TEAM_ID/_KEY/_TOPIC/_ENV; null → stub),buildApnsJwt(ES256 provider JWT, IEEE-P1363, config-keyed ~50min cache),buildApnsPayload(aps.alert+linkdeep-link),sendApns(HTTP/2/3/device/<token>, injectable poster, 10s timeout, single-settle,apns-expiration). Wired intoPushBridge.Pre-merge review fixes included (realApnsPost leak/timeout/double-resolve, config-keyed JWT cache, apns-expiration, %20 query encoding).
Verification:
npm run typecheck && npm run build && npm test→ 818 pass / 1 skip; APNs tests sign+verify a real ES256 JWT with a throwaway P-256 key. Live delivery needs a real Apple key + device (the external dependency).🤖 Generated with Claude Code