ios notifications #89

closed cmc opened this on 2026-09-02 02:48 UTC · ios mobile · milestone mobile

Discussion

cmc 2026-09-02 02:48 UTC

research options for ios notification support for builds, MRs, assignments, etc.

cmc 2026-09-20 08:59 UTC

Researched. Recommendation: gitbayd speaks APNs directly.

Scope

gitbay.org only. Push works for accounts on this instance; a self-hoster gets nothing until they ship their own build under their own bundle ID. That decision removes the relay from consideration — an APNs key belongs to a bundle ID, and holding one on behalf of other instances would mean their notification text transits this server for no benefit anyone asked for.

What already exists

notify() in internal/control/notifications.go takes a notice, resolves recipients (participants widened by watchers, minus mutes, minus the actor), files an inbox row, and optionally queues mail. There are 16 call sites covering issues, MRs, diff comments, review requests and failed builds. Push is a third branch in that loop, not new plumbing.

The delivery discipline is written twice already — internal/notify for mail, internal/webhook for HTTP POSTs: a queue table, a drainer goroutine, exponential backoff, dead-lettering. internal/push is a third instance of the same shape.

Approach

No new Go dependencies. APNs requires HTTP/2 and stdlib net/http negotiates h2 over ALPN. Token auth is an ES256 JWT — crypto/ecdsa plus encoding/json, about 30 lines. The JWT is cached ~50 minutes; APNs mandates refresh between 20 and 60, and minting per request returns TooManyProviderTokenUpdates.

Config gets a [push] section alongside [mail]: enabled, key_file, key_id, team_id, topic = "org.gitbay.gitbay", environment. The .p8 lives at /etc/gitbay/ mode 0600 and is referenced by path, as host_keys and the TLS key are.

Migration 0059 adds push_devices (user_id, token, label, created_at, last_seen_at, unique on token) and a push queue mirroring the mail queue's columns. Note the mail queue table is named notifications, so the push queue needs a different name.

Commands, since the capability lands in the registry before any surface renders it: notifications device add|list|remove and notifications settings push on|off, the latter matching the existing settings mail and settings watch pair.

APNs 410 Unregistered and BadDeviceToken delete the device row. That is the whole token lifecycle; no expiry job.

Alternatives rejected

A relay this instance operates: pure cost at gitbay.org-only scope.

Background refresh instead of push: BGAppRefreshTask is opportunistic, routinely 15 minutes to hours, and cannot badge reliably. "Your build failed" is exactly what it fails to deliver. Silent content-available pushes for badging need the same infrastructure, so there is no cheaper half-step.

Payload

Notification text is sent in full regardless of repository visibility. A private repository's name and issue number therefore reach Apple's infrastructure and a lock screen. The alternative — a generic "new activity" push with the app fetching detail — costs a Notification Service Extension holding the bearer token in a shared keychain group, which is not worth it here. Revisit if the instance stops having one human user.

Cost

Server is the small half: roughly 400-500 lines across internal/push, the store, config and two control commands, plus an e2e against a fake APNs endpoint.

The app is the larger half and is unwritten. krz/gitbay-ios has no push scaffolding at all today — no app delegate adaptor, no UNUserNotificationCenter, no background modes. It needs the permission prompt, registration posting the token on sign-in, deregistration on sign-out, and deep-linking a tap into existing routing. The inbox row's path field is already the right shape for that link. Then the Push Notifications capability on org.gitbay.gitbay, a privacy nutrition label update, and a resubmission.

Verified

Outbound HTTP/2 from bay1 to api.push.apple.com:443 works — a GET to /3/device/test returns 405 over h2, so TLS and ALPN reached Apple and only the method was wrong. No network blocker.

Gaps found on the way

issue assign files no notice. This issue names assignments, but assignment is not among the 16 notify() call sites — the dashboard surfaces assigned work and nothing announces it. Being fixed as part of this work.

DESIGN.org in krz/gitbay-ios records push as "not planned; propose if the app makes the case". That row and the Parity wiki page both need updating.

Spec and plan to follow.

referenced in commit a453498589 by cmc: web: render the push queue on /admin

2026-09-20 11:33 UTC

referenced in commit 5063e69c97 by cmc: web: confirm a device removal on its id

2026-09-20 11:33 UTC

referenced in commit 452ae0d87e by cmc: CHANGELOG: push gating and the queue row

2026-09-20 11:33 UTC

referenced in commit d8dfc35992 by cmc: web: mask a short device token on the settings page

2026-09-20 11:33 UTC

referenced in commit dbed1606f9 by cmc: push: downgrade the scheme only for a loopback host

2026-09-20 11:33 UTC

referenced in commit 9e99af2a11 by cmc: control: issue assign notifies only new assignees

2026-09-20 11:33 UTC

referenced in commit 1dea539c7c by cmc: store: a token changing hands drops the old queue

2026-09-20 11:33 UTC

referenced in commit 16c2392828 by cmc: store: report the push queue on the dashboard

2026-09-20 11:33 UTC

referenced in commit f08b7b5e1a by cmc: control: gate push on [push] enabled

2026-09-20 11:33 UTC

closed by cmc in commit b81a25efa5: docs: push notifications

2026-09-20 11:33 UTC

referenced in commit b58fd61f44 by cmc: e2e: check the exit code in the device-reap poll

2026-09-20 11:33 UTC

referenced in commit adeefc6b75 by cmc: push: log the GITBAY_APNS_HOST scheme downgrade

2026-09-20 11:33 UTC

referenced in commit 49d601e534 by cmc: e2e: push delivery and device reaping

2026-09-20 11:33 UTC

referenced in commit 1155b57e8a by cmc: push: match scheme to the GITBAY_APNS_HOST override

2026-09-20 11:33 UTC

referenced in commit fc527a7e38 by cmc: web: require confirmation to remove a push device

2026-09-20 11:33 UTC

referenced in commit 3a58c644d0 by cmc: web: push toggle and device list on notification settings

2026-09-20 11:33 UTC

referenced in commit 90af4498e8 by cmc: control: issue assign files a notice

2026-09-20 11:33 UTC

referenced in commit c89859bef9 by cmc: control: push as the third route in notify

2026-09-20 11:33 UTC

referenced in commit 4b8e4d61f0 by cmc: control: mask short device tokens instead of echoing them whole

2026-09-20 11:33 UTC

referenced in commit 74936a4b62 by cmc: control: notifications device and settings push

2026-09-20 11:33 UTC

referenced in commit 6a187ab6f5 by cmc: push: drain the queue, started by gitbayd

2026-09-20 11:33 UTC

referenced in commit 0030cf8082 by cmc: push: truncate alert bodies on a rune boundary

2026-09-20 11:33 UTC

referenced in commit 3beb0f5b68 by cmc: push: the APNs client

2026-09-20 11:33 UTC

referenced in commit a7d44dc543 by cmc: push: APNs provider tokens

2026-09-20 11:33 UTC

referenced in commit 10c1e6efa5 by cmc: config: the [push] section

2026-09-20 11:33 UTC

referenced in commit 29548d6557 by cmc: store: the push queue, swept like the mail queue

2026-09-20 11:33 UTC

referenced in commit c9cf3e5403 by cmc: store: fix AddPushDevice id on token-reuse path

2026-09-20 11:33 UTC

referenced in commit 2979e93075 by cmc: store: push device registrations

2026-09-20 11:33 UTC

referenced in commit 94878ec5b8 by cmc: docs: preflight corrections to the push plan

2026-09-20 11:33 UTC

referenced in commit 57f3ffe0ab by cmc: docs: implementation plan for the push server half

2026-09-20 11:33 UTC

referenced in commit e4483e3d3c by cmc: docs: spec for iOS push notifications

2026-09-20 11:33 UTC

referenced in commit f327db6192 by cmc: CHANGELOG: v1.32.0

2026-09-20 20:00 UTC

referenced in commit 66304b6cd8 by cmc: push: name the account an alert is for

2026-09-20 20:58 UTC

referenced in commit 72a0560c4d by cmc: push: return the device id and badge the alert

2026-09-20 22:46 UTC

referenced in commit 9c84807c66 by cmc: wiki: push device commands reach the iOS app

2026-09-21 00:09 UTC

referenced in commit 20e869ceb2 by cmc: docs: push notifications

2026-09-21 00:22 UTC
ios-smoke 2026-09-21 00:25 UTC

First live push test of the iOS client.

Commenting from a second account on purpose: notify() drops the acting user, so cmc acting on cmc's own repository notifies nobody.

ios-smoke 2026-09-21 01:02 UTC

Second live push test, against the TestFlight build.

ios-smoke 2026-09-21 01:04 UTC

Third live push test.

ios-smoke 2026-09-21 01:13 UTC

Badge test, with the badge actually deployed this time.