Native push notifications (Apple Push Notification service).
ONE job: when a turn finishes on this gateway, wake the phone that asked to be woken. Everything here is server-side; the app only ever hands us a device token.
Three moving parts:
Credentials. A token-based APNs auth key (.p8, ES256) plus its key
id, the Apple team id and the app's bundle id (the APNs topic).
Resolved from VIS_APNS_KEY_PATH / VIS_APNS_KEY_ID / VIS_APNS_TEAM_ID
/ VIS_APNS_TOPIC, else auto-discovered from ~/.vis/apns/AuthKey_<kid>.p8
(the key id is read off the filename) with the team/topic still from env
or ~/.vis/apns/apns.edn. No credentials = push silently OFF; the gateway
keeps working exactly as before.
A device registry at ~/.vis/devices.edn — device token -> platform,
APNs environment, client label/version, timestamps. Registration is
idempotent on the token. Tokens are SECRETS: nothing here logs more than
a masked prefix.
A sender — a signed ES256 JWT (cached, refreshed well inside Apple's
one-hour window) over HTTP/2 to api.push.apple.com. A device that
registered with the wrong environment is retried once against the other
host, and an APNs BadDeviceToken/Unregistered verdict evicts the
device so a stale token cannot accumulate.
The wire surface lives in gateway.server (/v1/devices); this namespace
knows nothing about Ring.
Native push notifications (Apple Push Notification service). ONE job: when a turn finishes on this gateway, wake the phone that asked to be woken. Everything here is server-side; the app only ever hands us a device token. Three moving parts: 1. **Credentials.** A token-based APNs auth key (`.p8`, ES256) plus its key id, the Apple team id and the app's bundle id (the APNs *topic*). Resolved from `VIS_APNS_KEY_PATH` / `VIS_APNS_KEY_ID` / `VIS_APNS_TEAM_ID` / `VIS_APNS_TOPIC`, else auto-discovered from `~/.vis/apns/AuthKey_<kid>.p8` (the key id is read off the filename) with the team/topic still from env or `~/.vis/apns/apns.edn`. No credentials = push silently OFF; the gateway keeps working exactly as before. 2. **A device registry** at `~/.vis/devices.edn` — device token -> platform, APNs environment, client label/version, timestamps. Registration is idempotent on the token. Tokens are SECRETS: nothing here logs more than a masked prefix. 3. **A sender** — a signed ES256 JWT (cached, refreshed well inside Apple's one-hour window) over HTTP/2 to `api.push.apple.com`. A device that registered with the wrong environment is retried once against the other host, and an APNs `BadDeviceToken`/`Unregistered` verdict evicts the device so a stale token cannot accumulate. The wire surface lives in `gateway.server` (`/v1/devices`); this namespace knows nothing about Ring.
(any-configured?)True when this gateway can deliver a push to SOME platform.
It normally can, with nothing configured: a gateway names the publisher's relay by default, and a registered device may name the one that sealed its grant. Its own Apple or Android credentials answer yes too. The only no left is an operator who pointed this machine at an address a bearer grant may not be handed to.
True when this gateway can deliver a push to SOME platform. It normally can, with nothing configured: a gateway names the publisher's relay by default, and a registered device may name the one that sealed its grant. Its own Apple or Android credentials answer yes too. The only no left is an operator who pointed this machine at an address a bearer grant may not be handed to.
(broadcast! notification)Send one notification to every registered device. Returns a per-device summary; never throws.
Send one notification to every registered device. Returns a per-device summary; never throws.
(config)Resolved APNs credentials, or a map with :is-configured false and the
list of what is still missing. Never throws and never returns key MATERIAL.
Resolved APNs credentials, or a map with `:is-configured false` and the list of what is still missing. Never throws and never returns key MATERIAL.
(configured?)True when this gateway can deliver an APPLE push.
True when this gateway can deliver an APPLE push.
(devices-file)Where the device registry is persisted.
Where the device registry is persisted.
(list-devices)Every registered device, secrets MASKED — safe for an HTTP response.
Every registered device, secrets MASKED — safe for an HTTP response.
(mask token)A device token is a secret. This is the ONLY form allowed into a log.
A device token is a secret. This is the ONLY form allowed into a log.
(on-event! sid event)Event tap: push exactly on a terminal turn event or on a human-input request the run is now blocked on, and only when push is both configured and wanted by at least one device. Cheap and silent otherwise — this runs on EVERY gateway event.
Event tap: push exactly on a terminal turn event or on a human-input request the run is now blocked on, and only when push is both configured and wanted by at least one device. Cheap and silent otherwise — this runs on EVERY gateway event.
(public-device device)One device as the wire may see it. The raw device token and the relay grant are both SECRETS: neither ever leaves this process.
One device as the wire may see it. The raw device token and the relay grant are both SECRETS: neither ever leaves this process.
(register-device! {:keys [token grant platform environment client client-version
label bundle-id relay-url]})Idempotently register (or refresh) one device. A device identifies itself
either by a raw APNs/FCM token this gateway pushes to directly, or by a relay
GRANT (gateway.relay) that lets it be woken WITHOUT this gateway ever
learning its token. Returns the stored device, or nil when neither is usable.
A grant is sealed by ONE relay, so the device also names where to spend it; an address that is not TLS is dropped here rather than at send time. The address sticks across a refresh that omits it — losing it would silence a device that is still perfectly reachable.
Idempotently register (or refresh) one device. A device identifies itself either by a raw APNs/FCM token this gateway pushes to directly, or by a relay GRANT (`gateway.relay`) that lets it be woken WITHOUT this gateway ever learning its token. Returns the stored device, or nil when neither is usable. A grant is sealed by ONE relay, so the device also names where to spend it; an address that is not TLS is dropped here rather than at send time. The address sticks across a refresh that omits it — losing it would silence a device that is still perfectly reachable.
(reload-devices!)Drop the in-memory cache and re-read the registry from disk.
Drop the in-memory cache and re-read the registry from disk.
(send-to-device! device notification)Deliver one alert to one registered device. A device that handed us a relay
grant is delivered through the relay it named — that relay holds the signing
key so this gateway does not have to — and everything else goes straight to
APNs/FCM with this gateway's own credentials. Retries once against the other
APNs environment (a TestFlight build registered as sandbox, or the reverse,
is the single most common misconfiguration) and forgets the device when the
provider says it is gone. Returns {:is-delivered bool :status :reason}.
Deliver one alert to one registered device. A device that handed us a relay
grant is delivered through the relay it named — that relay holds the signing
key so this gateway does not have to — and everything else goes straight to
APNs/FCM with this gateway's own credentials. Retries once against the other
APNs environment (a TestFlight build registered as `sandbox`, or the reverse,
is the single most common misconfiguration) and forgets the device when the
provider says it is gone. Returns `{:is-delivered bool :status :reason}`.(set-gateway-id! id)Install this gateway's stable instance id; every alert carries it as
gateway_id so a tap opens the session on the gateway that sent it.
Install this gateway's stable instance id; every alert carries it as `gateway_id` so a tap opens the session on the gateway that sent it.
(set-session-describer! f)Install the fn that turns a session id + turn id into {:title … :answer …}
for the alert. :answer is the finished turn's own text.
Install the fn that turns a session id + turn id into `{:title … :answer …}`
for the alert. `:answer` is the finished turn's own text.(status)Push capability for /v1/capabilities and /v1/admin/status.
Push capability for `/v1/capabilities` and `/v1/admin/status`.
(unregister-device! token)Drop one device token. Returns true when it was present.
Drop one device token. Returns true when it was present.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |