Skip to content

Instantly share code, notes, and snippets.

@peterstorm
Created August 31, 2021 13:09
Show Gist options
  • Save peterstorm/d46ff9d8b0aafe837871dcddf53d2c3b to your computer and use it in GitHub Desktop.
Save peterstorm/d46ff9d8b0aafe837871dcddf53d2c3b to your computer and use it in GitHub Desktop.
TokenCache
import cats.effect.syntax.all._
import cats.effect.{Deferred, Ref, Temporal}
import cats.syntax.all._
import domain.auth.LeadfamlyAccessToken
import scala.concurrent.duration._
trait TokenCache[F[_]]:
def get: F[LeadfamlyAccessToken]
def expire: F[Unit]
object TokenCache:
def make[F[_]: Temporal](refreshAction: F[LeadfamlyAccessToken], refreshTime: FiniteDuration): F[TokenCache[F]] =
enum State:
case Token(a: LeadfamlyAccessToken)
case Fetching(d: Deferred[F, Either[Throwable, LeadfamlyAccessToken]])
case Expired
Ref.of[F, State](State.Expired).map( state =>
new TokenCache[F]:
def get: F[LeadfamlyAccessToken] =
Deferred[F, Either[Throwable, LeadfamlyAccessToken]].flatMap( newValue =>
state.modify{
case st @ State.Token(a) => st -> a.pure[F]
case st @ State.Fetching(value) => st -> value.get.rethrow
case State.Expired => State.Fetching(newValue) -> refreshToken(newValue).rethrow
}.flatten
)
def refreshToken(d: Deferred[F, Either[Throwable, LeadfamlyAccessToken]]) =
Temporal[F].uncancelable( poll =>
for
either <- poll(refreshAction.attempt).onCancel(
state.set(State.Expired) >> d.complete(new Exception("refreshToken failed").asLeft).void
)
_ <- state.set(
either match
case Left(_) => State.Expired
case Right(v) => State.Token(v)
)
_ <- d.complete(either)
yield either
)
def expire: F[Unit] =
def loop: F[Unit] =
val effect = state.update{
case State.Token(_) => State.Expired
case State.Expired => State.Expired
case st @ State.Fetching(_) => st
}
Temporal[F].sleep(refreshTime) >> effect >> loop
loop
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment