diff --git a/build.sbt b/build.sbt index 86dbace98..c5dab1340 100644 --- a/build.sbt +++ b/build.sbt @@ -9,11 +9,11 @@ lazy val catsVersion = "2.10.0" lazy val catsRetryVersion = "3.1.0" lazy val circeVersion = "0.14.6" lazy val crystalVersion = "0.34.4" +lazy val fs2DomVersion = "0.3.0-M1" lazy val kittensVersion = "3.0.0" lazy val http4sVersion = "0.23.23" lazy val http4sDomVersion = "0.2.9" -lazy val lucumaBCVersion = "0.4.0" -lazy val lucumaCoreVersion = "0.85.1" +lazy val lucumaCoreVersion = "0.86.1" lazy val lucumaPrimeStylesVersion = "0.2.9" lazy val lucumaReactVersion = "0.44.1" lazy val lucumaRefinedVersion = "0.1.2" @@ -94,14 +94,15 @@ lazy val ui = "dev.optics" %%% "monocle-macro" % monocleVersion, "edu.gemini" %%% "crystal" % crystalVersion, "com.lihaoyi" %%% "pprint" % pprintVersion, + "com.armanbilge" %%% "fs2-dom" % fs2DomVersion, "org.http4s" %%% "http4s-core" % http4sVersion, "org.http4s" %%% "http4s-circe" % http4sVersion, "org.http4s" %%% "http4s-dom" % http4sDomVersion, "com.github.cb372" %%% "cats-retry" % catsRetryVersion, "io.circe" %%% "circe-core" % circeVersion, "io.circe" %%% "circe-parser" % circeVersion, - "edu.gemini" %%% "lucuma-sso-frontend-client" % lucumaSsoVersion, - "edu.gemini" %%% "lucuma-broadcast-channel" % lucumaBCVersion + "edu.gemini" %%% "lucuma-sso-frontend-client" % lucumaSsoVersion + // "edu.gemini" %%% "lucuma-broadcast-channel" % lucumaBCVersion ) ) diff --git a/modules/ui/src/main/scala/lucuma/ui/components/state/IfLogged.scala b/modules/ui/src/main/scala/lucuma/ui/components/state/IfLogged.scala index f490eb09b..0d2959fe2 100644 --- a/modules/ui/src/main/scala/lucuma/ui/components/state/IfLogged.scala +++ b/modules/ui/src/main/scala/lucuma/ui/components/state/IfLogged.scala @@ -7,6 +7,7 @@ import cats.syntax.all.* import crystal.react.View import crystal.react.syntax.view.* import eu.timepit.refined.types.string.NonEmptyString +import fs2.dom.Serializer import io.circe.Json import japgolly.scalajs.react.* import japgolly.scalajs.react.util.DefaultEffects.{Async => DefaultA} @@ -34,7 +35,8 @@ case class IfLogged[E]( )( val render: DefaultA[Unit] => VdomNode )(using - val logger: Logger[DefaultA] + val logger: Logger[DefaultA], + val serializerE: Serializer[E] ) extends ReactFnProps(IfLogged.component) object IfLogged: diff --git a/modules/ui/src/main/scala/lucuma/ui/components/state/LogoutTracker.scala b/modules/ui/src/main/scala/lucuma/ui/components/state/LogoutTracker.scala index 14464ab50..88c517227 100644 --- a/modules/ui/src/main/scala/lucuma/ui/components/state/LogoutTracker.scala +++ b/modules/ui/src/main/scala/lucuma/ui/components/state/LogoutTracker.scala @@ -4,13 +4,14 @@ package lucuma.ui.components.state import cats.Applicative -import cats.effect.Sync import cats.syntax.all.* +import crystal.react.hooks.* import eu.timepit.refined.types.string.NonEmptyString +import fs2.dom.BroadcastChannel +import fs2.dom.Serializer import japgolly.scalajs.react.* import japgolly.scalajs.react.util.DefaultEffects.{Async => DefaultA} import japgolly.scalajs.react.vdom.html_<^.* -import lucuma.broadcastchannel.* import lucuma.react.common.ReactFnProps import lucuma.refined.* import lucuma.ui.sso.UserVault @@ -22,8 +23,8 @@ case class LogoutTracker[E]( isLogoutEvent: E => Boolean, getEventNonce: E => String, createEventWithNonce: String => E -)(val render: DefaultA[Unit] => VdomNode) - extends ReactFnProps(LogoutTracker.component) +)(val render: DefaultA[Unit] => VdomNode)(using val serializerE: Serializer[E]) + extends ReactFnProps(LogoutTracker.component) {} object LogoutTracker: private type Props[E] = LogoutTracker[E] @@ -33,34 +34,27 @@ object LogoutTracker: .withHooks[Props[E]] // Create a nonce .useMemo(())(_ => System.currentTimeMillis) - // Hold the broadcast channel - .useState(none[BroadcastChannel[E]]) - .useEffectOnMountBy { (props, nonce, state) => - val bc = new BroadcastChannel[E](props.channelName.value) - - bc.onmessage = ( - (e: E) => - if (props.isLogoutEvent(e)) + .useResourceOnMountBy { (props, _) => + import props.given + BroadcastChannel[DefaultA, E](props.channelName.value) + } + .useStreamBy((_, _, bc) => bc.isReady)((props, nonce, bc) => + _ => + bc.toOption.map(_.messages).orEmpty.evalTap { e => + if (props.isLogoutEvent(e.data)) (props.setVault(none) >> props.setMessage( "You logged out in another instance".refined - )).whenA(props.getEventNonce(e) =!= nonce.value.toString) + )).whenA(props.getEventNonce(e.data) =!= nonce.value.toString) else Applicative[DefaultA].unit - ): (E => DefaultA[Unit]) // Scala 3 infers the return type as Any if we don't ascribe - - state - .setState(bc.some) *> CallbackTo(Callback(bc.close()).attempt) - } - .render { (props, nonce, bc) => - bc.value.fold[VdomNode](React.Fragment())(bc => + } + ) + .render { (props, nonce, bc, _) => + bc.toOption.fold[VdomNode](React.Fragment())(bc => props.render( - Sync[DefaultA] - .delay( - bc.postMessage( - props.createEventWithNonce(nonce.value.toString) - ) - ) - .attempt + bc.postMessage( + props.createEventWithNonce(nonce.value.toString) + ).attempt .void ) )