Rough websocket responses

This commit is contained in:
Paul-Henri Froidmont 2025-08-25 01:17:02 +02:00
parent fadef26425
commit ccdd22b61a
Signed by: phfroidmont
GPG key ID: BE948AFD7E7873BE
3 changed files with 70 additions and 16 deletions

View file

@ -52,5 +52,5 @@ object example extends ScalaCommon:
}
object js extends TypeScriptModule:
def mainFileName = "app.ts"
def mainFileName = "app.js"
def npmDeps = Seq("phoenix@1.7.21", "phoenix_live_view@1.1.8")

View file

@ -26,3 +26,10 @@ final case class Socket[Cmd](lv: LiveView[Cmd]):
println(DiffBuilder.build(element, trackUpdates = clientInitialized).toJsonPretty)
clientInitialized = true
element.setAllUnchanged()
def diff: Diff =
element.syncAll()
val diff = DiffBuilder.build(element, trackUpdates = clientInitialized)
clientInitialized = true
element.setAllUnchanged()
diff

View file

@ -1,5 +1,7 @@
package scalive
import scalive.SocketMessage.LiveResponse
import scalive.SocketMessage.Payload
import zio.*
import zio.http.*
import zio.http.ChannelEvent.Read
@ -22,16 +24,40 @@ class LiveRouter(rootLayout: HtmlElement => HtmlElement, liveRoutes: List[LiveRo
private val socketApp: WebSocketApp[Any] =
Handler.webSocket { channel =>
channel.receiveAll {
channel
.receiveAll {
case Read(WebSocketFrame.Text(content)) =>
for
_ <-
content.fromJson[SocketMessage].fold(m => ZIO.logError(m), m => ZIO.log(m.toString))
_ <- channel.send(Read(WebSocketFrame.text("bar")))
message <- ZIO
.fromEither(content.fromJson[SocketMessage])
.mapError(new IllegalArgumentException(_))
reply <- handleMessage(message)
_ <- channel.send(Read(WebSocketFrame.text(reply.toJson)))
yield ()
case _ => ZIO.unit
}.tapErrorCause(ZIO.logErrorCause(_))
}
}
def handleMessage(message: SocketMessage): Task[SocketMessage] =
val reply = message.payload match
case Payload.Heartbeat => ZIO.succeed(Payload.Reply("ok", LiveResponse.Empty))
case Payload.Join(url, session, static, sticky) =>
// TODO very rough handling
ZIO
.fromEither(URL.decode(url)).map(url =>
val req = Request(url = url)
liveRoutes
.collectFirst { route =>
val pathParams = route.path.decode(req.path).getOrElse(???)
val lv = route.liveviewBuilder(pathParams, req)
val s = Socket(lv)
Payload.Reply("ok", LiveResponse.InitDiff(s.diff))
}.getOrElse(???)
)
case Payload.Reply(_, _) => ZIO.die(new IllegalArgumentException())
reply.map(SocketMessage(message.joinRef, message.messageRef, message.topic, "phx_reply", _))
val routes: Routes[Any, Response] =
Routes.fromIterable(
@ -41,6 +67,7 @@ class LiveRouter(rootLayout: HtmlElement => HtmlElement, liveRoutes: List[LiveRo
Method.GET / "live" / "websocket" -> handler(socketApp.toResponse)
)
)
end LiveRouter
final case class SocketMessage(
// Live session ID, auto increment defined by the client on join
@ -58,6 +85,7 @@ object SocketMessage:
Chunk(joinRef, Json.Str(messageRef), Json.Str(topic), Json.Str(eventType), payload)
) =>
val payloadParsed = eventType match
case "heartbeat" => Right(Payload.Heartbeat)
case "phx_join" => payload.as[Payload.Join]
case s => Left(s"Unknown event type : $s")
@ -74,22 +102,41 @@ object SocketMessage:
},
m =>
Json.Arr(
m.joinRef.map(Json.Num(_)).getOrElse(Json.Null),
Json.Num(m.messageRef),
m.joinRef.map(ref => Json.Str(ref.toString)).getOrElse(Json.Null),
Json.Str(m.messageRef.toString),
Json.Str(m.topic),
Json.Str(m.eventType),
m.payload.match
case Payload.Heartbeat => Json.Obj.empty
case p: Payload.Join => p.toJsonAST.getOrElse(throw new IllegalArgumentException())
case p: Payload.Reply => p.toJsonAST.getOrElse(throw new IllegalArgumentException())
)
)
enum Payload:
case Heartbeat
case Join(
url: String,
// params: Map[String, String],
session: String,
static: Option[String],
sticky: Boolean)
case Reply(status: String, response: LiveResponse)
object Payload:
given JsonCodec[Payload.Join] = JsonCodec.derived
given JsonEncoder[Payload.Reply] = JsonEncoder.derived
enum LiveResponse:
case Empty
case InitDiff(rendered: scalive.Diff)
object LiveResponse:
given JsonEncoder[LiveResponse] =
JsonEncoder[Json].contramap {
case Empty => Json.Obj.empty
case InitDiff(rendered) =>
Json.Obj(
"liveview_version" -> Json.Str("1.1.8"),
"rendered" -> rendered.toJsonAST.getOrElse(throw new IllegalArgumentException())
)
}
end SocketMessage