All Downloads are FREE. Search and download functionalities are using the official Maven repository.

izumi.idealingua.runtime.rpc.http4s.ws.WsClientSession.scala Maven / Gradle / Ivy

package izumi.idealingua.runtime.rpc.http4s.ws

import cats.effect.std.Queue
import io.circe.syntax.*
import io.circe.{Json, Printer}
import izumi.functional.bio.{F, IO2, Primitives2, Temporal2}
import izumi.fundamentals.platform.time.IzTime
import izumi.fundamentals.platform.uuid.UUIDGen
import izumi.idealingua.runtime.rpc.*
import izumi.idealingua.runtime.rpc.http4s.ws.WsRpcHandler.WsClientResponder
import logstage.LogIO2
import org.http4s.websocket.WebSocketFrame
import org.http4s.websocket.WebSocketFrame.Text

import java.time.ZonedDateTime
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
import scala.concurrent.duration.*

trait WsClientSession[F[+_, +_], RequestCtx, ClientId] extends WsClientResponder[F] {
  def id: WsClientId[ClientId]
  def initialContext: RequestCtx

  def updateId(maybeNewId: Option[ClientId]): F[Throwable, Unit]
  def outQueue: Queue[F[Throwable, _], WebSocketFrame]

  def requestAndAwaitResponse(method: IRTMethodId, data: Json, timeout: FiniteDuration): F[Throwable, Option[RawResponse]]

  def finish(): F[Throwable, Unit]
}

object WsClientSession {
  class WsClientSessionImpl[F[+_, +_]: IO2: Temporal2: Primitives2, RequestCtx, ClientId](
    val outQueue: Queue[F[Throwable, _], WebSocketFrame],
    val initialContext: RequestCtx,
    listeners: Seq[WsSessionListener[F, ClientId]],
    wsSessionStorage: WsSessionsStorage[F, RequestCtx, ClientId],
    printer: Printer,
    logger: LogIO2[F],
  ) extends WsClientSession[F, RequestCtx, ClientId] {
    private val openingTime: ZonedDateTime                  = IzTime.utcNow
    private val sessionId: WsSessionId                      = WsSessionId(UUIDGen.getTimeUUID())
    private val clientId: AtomicReference[Option[ClientId]] = new AtomicReference[Option[ClientId]](None)
    private val requestState: WsRequestState[F]             = WsRequestState.create[F]

    def id: WsClientId[ClientId] = WsClientId(sessionId, clientId.get())

    def requestAndAwaitResponse(method: IRTMethodId, data: Json, timeout: FiniteDuration): F[Throwable, Option[RawResponse]] = {
      val id      = RpcPacketId.random()
      val request = RpcPacket.buzzerRequest(id, method, data)
      for {
        _ <- logger.debug(s"WS Session: enqueue $request with $id to request state & send queue.")
        response <- requestState.requestAndAwait(id, Some(method), timeout) {
          outQueue.offer(Text(printer.print(request.asJson)))
        }
        _ <- logger.debug(s"WS Session: $method, ${id -> "id"}: cleaning request state.")
      } yield response
    }

    override def responseWith(id: RpcPacketId, response: RawResponse): F[Throwable, Unit] = {
      requestState.responseWith(id, response)
    }

    override def responseWithData(id: RpcPacketId, data: Json): F[Throwable, Unit] = {
      requestState.responseWithData(id, data)
    }

    override def updateId(maybeNewId: Option[ClientId]): F[Throwable, Unit] = {
      for {
        old     <- F.sync(id)
        _       <- F.sync(clientId.set(maybeNewId))
        current <- F.sync(id)
        _       <- F.when(old != current)(F.traverse_(listeners)(_.onClientIdUpdate(current, old)))
      } yield ()
    }

    override def finish(): F[Throwable, Unit] = {
      F.fromEither(WebSocketFrame.Close(1000)).flatMap(outQueue.offer(_)) *>
      wsSessionStorage.deleteClient(sessionId) *>
      F.traverse_(listeners)(_.onSessionClosed(id)) *>
      requestState.clear()
    }

    protected[http4s] def start(): F[Throwable, Unit] = {
      wsSessionStorage.addClient(this) *>
      F.traverse_(listeners)(_.onSessionOpened(id))
    }

    override def toString: String = s"[${id.toString}, ${duration().toSeconds}s]"

    private[this] def duration(): FiniteDuration = {
      val now = IzTime.utcNow
      val d   = java.time.Duration.between(openingTime, now)
      FiniteDuration(d.toNanos, TimeUnit.NANOSECONDS)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy