Skip to content

Tracking: unify APNs/FCM push-provider machinery — shared auth cache + rich PushOutcome #214

Description

@erskingardner

Root cause

ApnsClient and FcmClient are ~95%-identical copy-pastes that have already diverged, and the send path collapses every outcome into Result<bool>, destroying the information downstream consumers need.

  • Duplicated auth machinery: identical CachedToken { token: Zeroizing<String>, expires_at } behind Arc<RwLock<Option<CachedToken>>> with the same double-checked read/write-lock pattern in src/push/apns.rs:206-389 and src/push/fcm.rs:214-455. The copies have already drifted (transport-retry budgets, log levels, FCM-only aud/endpoint mismatch) — a fix in one provider does not reach the other.
  • Lossy outcome type: SendAttemptResult::Success(bool) conflates "provider says the token is dead" with "retries exhausted" (with_retry returns Ok(false) on exhaustion, src/push/retry.rs:197-204) and "unknown status" (APNs catch-all _ => Success(false), src/push/apns.rs:495-502). The dispatcher then labels every Ok(false) as reason="invalid_token" (src/push/dispatcher.rs:258-293) — the one metric whose downstream use (dead-token pruning) is destructive.

Every member issue below is a symptom of one of these two defects. Fixing them individually re-patches the same duplicated code paths; fixing the architecture closes them all.

Refactor design

New src/push/auth.rs — shared credential machinery:

Rich outcome enum, replacing Result<bool> / Success(bool) end-to-end:

enum PushOutcome {
    Delivered,
    TokenInvalid(InvalidReason),        // only provider-confirmed dead tokens
    RetriesExhausted { last_status: u16 },
    Shed(ShedReason),                   // shutdown, etc.
    Failed(ProviderError),              // unexpected status, config fault, transport
}

Members

In-flight PRs

Sequencing

  • Wave 1; parallel with the config, lifecycle, secret-hygiene, NIP-59, and health trackers (disjoint files except dispatcher.rs, where the admission tracker touches different functions).
  • The retry-policy tracker (Retry-After cap, live-task bound) is hard-blocked behind this one — same retry.rs machinery.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions