Documentation
¶
Overview ¶
Package relayclient is the daemon-side client for the publisher APNs relay. It builds and submits E2E alert envelopes (and, later, Live Activity updates) over the relay's §3 HTTPS API, mapping responses into a retry-classifiable result so the daemon can decide whether to re-queue, shed, or drop.
Index ¶
- func BuildEnvelope(installationID string, e2ePubKey, plaintext []byte, ev EnvelopeContext) (hpke.Envelope, error)
- type Client
- func (c *Client) Register(ctx context.Context, installationID, deviceToken string, e2ePubKey []byte, ...) (apns.Env, error)
- func (c *Client) SubmitActivities(ctx context.Context, installationID, laToken, mode string, ...) SubmitResult
- func (c *Client) SubmitAlerts(ctx context.Context, installationID, collapseHint string, expiresAt int64, ...) SubmitResult
- type EnvelopeContext
- type SubmitResult
- type SubmitStatus
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BuildEnvelope ¶
func BuildEnvelope(installationID string, e2ePubKey, plaintext []byte, ev EnvelopeContext) (hpke.Envelope, error)
BuildEnvelope seals plaintext to e2ePubKey (the device's per-install X25519 HPKE public key), binding the AAD context, and returns the wire envelope the relay carries. The plaintext never appears in the envelope or on the wire — only enc (KEM encapsulation) + ciphertext (ChaCha20-Poly1305) do.
Types ¶
type Client ¶
type Client struct {
BaseURL string // relay origin, e.g. https://relay.lph.id.vn
Credential string // bearer access credential, daemon_id-bound (from pairing/consume)
DaemonID string // this daemon's id, sent in register
HTTP *http.Client
}
Client submits E2E alert envelopes (and, later, Live Activity updates) to the publisher APNs relay over its §3 HTTPS API. It carries the daemon's bearer credential and maps relay responses to a retry-classifiable SubmitResult.
This is the daemon counterpart to the relay server (agent/internal/apnsrelay). It is intentionally transport-only: it does not know the alert plaintext's origin or the device registry — the caller (eventually fireAPNsForHook) hands it built envelopes. All identifiers travel in request bodies, never the URL path (the audit log writes r.URL.Path verbatim).
func New ¶
New builds a client. http.Client defaults to a 10s timeout if nil; tests inject a faster one.
func (*Client) Register ¶
func (c *Client) Register(ctx context.Context, installationID, deviceToken string, e2ePubKey []byte, keyID, laToken string, env apns.Env) (apns.Env, error)
Register upserts this daemon against an installation at the relay. The relay 409s if the installation's env doesn't match its configured env (the sandbox/prod silent-no-push guard, contract #14); that surfaces as an error here. Returns the relay's configured_env on success.
func (*Client) SubmitActivities ¶
func (c *Client) SubmitActivities(ctx context.Context, installationID, laToken, mode string, u apns.LiveActivityUpdate) SubmitResult
SubmitActivities posts a Live Activity content-state update to the relay's priority LA lane. The relay forwards it synchronously to APNs (no queue; LA is freshness-critical). The caller MUST pass a relay-safe (rich-minimal) content_state — ActivityKit decodes content-state itself (the NSE cannot intercept LA pushes), so anything in ContentState crosses the relay in the clear. The daemon strips project/toolTarget/todoFocus/lastPrompt/answer before calling this (contract #10); mode is "rich".
func (*Client) SubmitAlerts ¶
func (c *Client) SubmitAlerts(ctx context.Context, installationID, collapseHint string, expiresAt int64, envelopes []hpke.Envelope) SubmitResult
SubmitAlerts posts sealed envelopes to the relay for delivery. The relay stamps the originating daemon from the bearer credential and builds the APNs payload from ciphertext + a static fallback body (it never sees plaintext). collapseHint is used verbatim as apns-collapse-id; expiresAt is the relay accept-time TTL anchor.
type EnvelopeContext ¶
type EnvelopeContext struct {
KeyID string
EventID string
Sequence uint64
CreatedAt int64 // unix seconds, sender clock (informational)
ExpiresAt int64 // unix seconds; authoritative TTL is re-stamped by the relay
Silent bool // relay omits aps.sound without learning encrypted alert content
}
EnvelopeContext is the per-message binding an envelope is sealed against. The receiver reconstructs the AAD from these fields (plus installationID) and the AEAD rejects any mismatch — so a ciphertext sealed for one event/sequence/ expiry cannot be replayed against another.
type SubmitResult ¶
type SubmitResult struct {
Status SubmitStatus
RetryAfter time.Duration // meaningful only for SubmitRetryAfter
RequestID string // relay-assigned; empty on hard failures before accept
// Err is the underlying cause for SubmitRejected and SubmitRetryAfter
// (network/HTTP errors); nil for SubmitAccepted/SubmitDeduplicated.
Err error
}
SubmitResult is the outcome of a SubmitAlerts/SubmitActivities call.
type SubmitStatus ¶
type SubmitStatus int
SubmitStatus is the retry classification of a relay submission (contract §15). It replaces the plain-error send seam so the daemon can distinguish retryable backpressure from permanent rejection.
const ( // SubmitAccepted: the relay enqueued the envelopes for delivery. SubmitAccepted SubmitStatus = iota // SubmitDeduplicated: every envelope was a duplicate of one already queued // (collapse/idempotency hit); nothing new was sent. Not an error. SubmitDeduplicated // SubmitRetryAfter: transient — the relay is applying admission control // (429), had a network/5xx blip, or asked the client to back off. Retry after // RetryAfter with jitter. SubmitRetryAfter // SubmitRejected: permanent — 401/403/409/400 or an unrecoverable payload // problem. Do not retry without a change (re-pair, fix env, rotate key). SubmitRejected )
func (SubmitStatus) String ¶
func (s SubmitStatus) String() string
String is a short label for logs/metrics.