relayclient

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 13, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

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

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

func New(baseURL, credential, daemonID string) *Client

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL