Current section
Files
Jump to
Current section
Files
lib/supabase/go_true/auto_refresh.ex
defmodule Supabase.GoTrue.AutoRefresh do
@moduledoc """
A GenServer to automatically refresh auth tokens before they expire.
## Usage
Add this GenServer to your application's supervision tree:
```elixir
# In your application.ex
def start(_type, _args) do
children = [
# ... other children
{Supabase.Registry, keys: :unique, name: Supabase.Registry},
{DynamicSupervisor, strategy: :one_for_one, name: Supabase.AutoRefreshSupervisor}
# ... rest of your supervision tree
]
opts = [strategy: :one_for_one, name: YourApp.Supervisor]
Supervisor.start_link(children, opts)
end
```
Then start the auto-refresh process when you get a new session:
```elixir
{:ok, pid} = DynamicSupervisor.start_child(
Supabase.AutoRefreshSupervisor,
{Supabase.GoTrue.AutoRefresh, {client, session}}
)
```
To stop refreshing when the user logs out:
```elixir
Supabase.GoTrue.AutoRefresh.stop(client)
```
"""
use GenServer
alias Supabase.Client
alias Supabase.GoTrue
alias Supabase.GoTrue.Session
@auto_refresh_tick_duration 30
@auto_refresh_tick_threshold 3
@expiry_margin @auto_refresh_tick_threshold * @auto_refresh_tick_duration
def start_link({%Client{} = client, %Session{} = session}) do
GenServer.start_link(__MODULE__, {client, session}, name: via_tuple(client))
end
def stop(%Client{} = client) do
case Registry.lookup(Supabase.Registry, client_key(client)) do
[{pid, _}] -> GenServer.stop(pid, :normal)
[] -> :ok
end
end
def init({client, session}) do
state = %{
client: client,
session: session,
timer_ref: schedule_check()
}
{:ok, state}
end
def handle_info(:check_refresh, %{client: client, session: session, timer_ref: old_timer} = state) do
Process.cancel_timer(old_timer)
case check_refresh(client, session) do
{:ok, new_session} ->
{:noreply, %{state | session: new_session, timer_ref: schedule_check()}}
{:error, _reason} ->
check_ms = to_timeout(second: @auto_refresh_tick_duration * 2)
{:noreply, %{state | timer_ref: schedule_check(check_ms)}}
end
end
def handle_info(_, state), do: {:noreply, state}
def terminate(_reason, %{timer_ref: timer_ref}) do
Process.cancel_timer(timer_ref)
:ok
end
defp schedule_check(delay \\ @auto_refresh_tick_duration) do
Process.send_after(self(), :check_refresh, to_timeout(second: delay))
end
defp check_refresh(%Client{} = client, %Session{} = session) do
if needs_refresh?(session) do
GoTrue.refresh_session(client, session.refresh_token)
else
{:ok, session}
end
end
def needs_refresh?(%Session{} = session) do
now = System.os_time(:second)
margin_in_seconds = to_timeout(second: @expiry_margin)
session.expires_at != nil and
now + margin_in_seconds > session.expires_at
end
defp via_tuple(client) do
{:via, Registry, {Supabase.Registry, client_key(client)}}
end
defp client_key(client) do
"auth_refresh:#{client.base_url}|#{client.auth.url}"
end
end