Skip to content

Push a notification when a sync finds new transactions - #784

Open
michaelst wants to merge 1 commit into
mainfrom
claude/push-notifications-transactions-bd9810
Open

Push a notification when a sync finds new transactions#784
michaelst wants to merge 1 commit into
mainfrom
claude/push-notifications-transactions-bd9810

Conversation

@michaelst

Copy link
Copy Markdown
Owner

Phase 2b, server half. Device half stacks on this branch.

What

  • PATCH /api/session registers the APNs device token on the API token the request was made with, so revoking the token stops its pushes. No new table.
  • SyncMember collects its inserts and calls Accounts.notify_user/2 once at the end of the run - "3 new transactions · $84.21", never fifty pushes.
  • Every completed sync pushes. Silent (content-available: 1) when there is nothing to say, which is the completion signal POST /api/banks/:id/sync has never had. 410 Unregistered / BadDeviceToken nulls the device token; anything else is an Oban retry.
  • Unconfigured is a supported state: with no .p8 the client returns {:error, :not_configured} and nothing else changes.

No APNs library

pigeon and pushx both hardcode api.push.apple.com with no base-URL override, so neither can be driven through TeslaMock - each would need its own Hammox wrapper and no test would assert the real request. Finch already speaks HTTP/2 (one pools: entry) and joken already signs ES256, so the client is ~50 lines on the seam docs/tests.md requires, and the 410 path is tested against a real response.

Deliberate deviation from the plan

The plan says both "suppress on historical sync - a first connect fires nothing" and "send on sync completion regardless of count ... which closes the gap where POST /api/banks/:id/sync has no completion signal". Those contradict: /sync is the historical sync. Resolved by suppressing the visible alert rather than the whole notification, and applying it to the first-connect sync too (which the plan wanted covered, but which runs through queue_sync, not queue_historical_sync). So alerts fire only on webhook-driven syncs - exactly when new charges genuinely arrive - and every run still wakes the app.

Drive-by

mix openapi was writing ./openapi.json, not the priv/static/openapi.json that CI diffs - the task takes --filename and ignores a positional path, so the drift check could never have failed. Fixed, stray copy deleted.

Gates

mix format, mix compile --warnings-as-errors, mix credo, mix coveralls 100%, spec + Dart client regenerated.

Not verified here

The APNs request itself has never hit Apple - no .p8 on this machine. Needs a real key and a device before it is known to work end to end.

🤖 Generated with Claude Code

Direct APNs, no Firebase and no APNs library: Finch already speaks HTTP/2 and joken already
signs ES256, so the client is ~50 lines on the Tesla seam the test standards require, where a
410 and its response headers are asserted rather than mocked away.

- `PATCH /api/session` registers the device token on the calling API token, so revoking the
  token stops its pushes.
- `SyncMember` collects its inserts and calls `Accounts.notify_user/2` once at the end of the
  run - one push saying "3 new transactions · $84.21", never fifty.
- Every completed sync pushes: silent when there is nothing to say, which is the completion
  signal `POST /api/banks/:id/sync` has never had. The visible alert is held back on a first
  connect and on a historical sync, where the count is history rather than news.
- A `410 Unregistered` nulls the device token instead of retrying. Anything else is an Oban retry.
- Unconfigured is a supported state: with no `.p8` the client refuses to send and nothing else
  changes.

`mix openapi` was writing to ./openapi.json rather than the file CI diffs, because the task takes
`--filename` and ignores a positional path. Fixed, and the stray copy deleted.

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