# `Daraja.TokenCache`
[🔗](https://github.com/okothkongo/daraja/blob/v0.1.1/lib/daraja/token_cache.ex#L1)

ETS-backed GenServer that caches Daraja access tokens to eliminate the
network round-trip on every API call.

Add `Daraja.Supervisor` to your application's supervision tree to opt in:

    children = [
      {Daraja.Supervisor, []}
    ]

The library is process-free by default; caching is opt-in.

## Cache key

Tokens are keyed by `{consumer_key, environment, secret_hash}` where
`secret_hash` is a SHA-256 digest of `consumer_secret`. Rotating the secret
while keeping the same consumer key produces a cache miss and fetches a fresh
token. Call `invalidate/1` after rotation to evict any orphaned entry keyed
under the previous secret.

## TTL and refresh

Token lifetime comes from Safaricom's OAuth `expires_in` field. `:ttl`
(default 3600 s) is used only when `expires_in` is absent or unparseable.
`:refresh_before` (default 120 s) controls how many seconds before expiry
the cache proactively fetches a fresh token in the background.

`refresh_before` should be less than the configured `:ttl` fallback and the
effective token lifetime. When `expires_in` is shorter than `refresh_before`,
refresh is scheduled immediately. `init/1` raises when `refresh_before >= ttl`.

## Concurrent fetches

OAuth network I/O runs in `Task.Supervisor` workers — not inside the
GenServer mailbox — so cache misses for different credentials fetch in
parallel. Concurrent misses for the **same** key share one in-flight task.

## Crash and restart

ETS tables are owned by the GenServer process. On crash and supervisor
restart, the old table is gone and a fresh one is created — all cached tokens
are lost and the next request per client pays a cold network fetch.

## ETS access

The cache table is `:protected`: any process may read (lock-free happy path
in `get_token/2`), but only the owning GenServer may insert or delete. This
prevents local cache poisoning by unrelated processes while preserving read
concurrency.

## Credential retention

Client credentials are held in the GenServer's `clients` map (keyed like the
token cache) so proactive refresh timers carry only the cache key—not a full
`%Daraja.Client{}`—in delayed messages.

# `get_token`

```elixir
@spec get_token(Daraja.Client.t(), atom()) ::
  {:ok, String.t()}
  | {:error, :auth_failed, term()}
  | {:error, :http_error, term()}
```

Returns a cached access token for `client`, fetching from the network on a
miss.

Reads directly from ETS on the happy path (lock-free). Falls back to a
`GenServer.call` on a miss or when the ETS table doesn't exist yet (brief
startup race between supervisor start and `init/1` completing).

`server` defaults to `Daraja.TokenCache`. Pass a custom atom when using a
non-default cache name configured via `cache_name:` in `Daraja.Supervisor`.

# `invalidate`

```elixir
@spec invalidate(Daraja.Client.t(), atom()) :: :ok
```

Evicts the cached token for `client`, canceling any scheduled refresh.

Use after credential rotation to drop a stale entry obtained with the
previous secret. With the updated cache key, a rotated secret already
misses the cache; `invalidate/1` cleans up the orphaned entry and timer.

`server` defaults to `Daraja.TokenCache`.

# `start_link`

---

*Consult [api-reference.md](api-reference.md) for complete listing*
