From 00e902747b1c4655bf5a09a301fd79e7d54c01cc Mon Sep 17 00:00:00 2001 From: Majd Sehwail Date: Sun, 6 Aug 2023 10:49:51 +0300 Subject: [PATCH] Implement mechanism to refresh Firestore access tokens before expiry --- lib/firestore.ex | 53 +++++++++++++++++++++++++++++++++++++------ lib/firestore/repo.ex | 2 +- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/lib/firestore.ex b/lib/firestore.ex index d78f110..d589c06 100644 --- a/lib/firestore.ex +++ b/lib/firestore.ex @@ -3,17 +3,32 @@ defmodule Firestore do This is the main entry point for the Firestore application. """ + defmodule State do + defstruct [ + :config, + :client + ] + + @type t :: %__MODULE__{ + config: map(), + client: Connection.t() + } + end + use GenServer - def init(%{otp_app: app} = config) do - credentials = Enum.into(config, %{}, fn {k, v} -> {to_string(k), v} end) + alias Firestore.Connection + alias Goth.Token - with {:ok, %{token: token}} <- Goth.Token.fetch(source: {:service_account, credentials}) do - client = Firestore.Connection.init(token, config) + @ets_table :firestore_table + @refresh_token_interval_ms 1 * 60 * 1000 - :ets.new(:firestore_conn_table, [:set, :public, :named_table]) - :ets.insert(:firestore_conn_table, {:"#{app}_firestore_client", client}) - {:ok, client} + @impl true + def init(config) do + :ets.new(@ets_table, [:set, :public, :named_table]) + + with {:ok, client} <- init_client(config) do + {:ok, %State{config: config, client: client}} end end @@ -24,4 +39,28 @@ defmodule Firestore do def child_spec(opts) do %{id: __MODULE__, start: {__MODULE__, :start_link, [opts]}} end + + @impl true + def handle_info(:refresh_token, %State{config: config} = state) do + case init_client(config) do + {:ok, client} -> + {:noreply, %State{state | client: client}} + + {:error, reason} -> + {:stop, reason} + end + end + + defp init_client(%{otp_app: app} = config) do + credentials = Enum.into(config, %{}, fn {k, v} -> {to_string(k), v} end) + + with {:ok, %{token: token}} <- Token.fetch(source: {:service_account, credentials}) do + client = Connection.init(token, config) + + :ets.insert(@ets_table, {:"#{app}_firestore_client", client}) + Process.send_after(self(), :refresh_token, @refresh_token_interval_ms) + + {:ok, client} + end + end end diff --git a/lib/firestore/repo.ex b/lib/firestore/repo.ex index d43289a..642dea5 100644 --- a/lib/firestore/repo.ex +++ b/lib/firestore/repo.ex @@ -127,7 +127,7 @@ defmodule Firestore.Repo do end defp get_client() do - case :ets.lookup(:firestore_conn_table, :"#{@otp_app}_firestore_client") do + case :ets.lookup(:firestore_table, :"#{@otp_app}_firestore_client") do [{_, %Tesla.Client{} = client}] -> {:ok, client}