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

@ -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,17 +24,41 @@ class LiveRouter(rootLayout: HtmlElement => HtmlElement, liveRoutes: List[LiveRo
private val socketApp: WebSocketApp[Any] =
Handler.webSocket { channel =>
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")))
yield ()
case _ => ZIO.unit
}
channel
.receiveAll {
case Read(WebSocketFrame.Text(content)) =>
for
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(
liveRoutes
@ -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,8 +85,9 @@ object SocketMessage:
Chunk(joinRef, Json.Str(messageRef), Json.Str(topic), Json.Str(eventType), payload)
) =>
val payloadParsed = eventType match
case "phx_join" => payload.as[Payload.Join]
case s => Left(s"Unknown event type : $s")
case "heartbeat" => Right(Payload.Heartbeat)
case "phx_join" => payload.as[Payload.Join]
case s => Left(s"Unknown event type : $s")
payloadParsed.map(
SocketMessage(
@ -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 p: Payload.Join => p.toJsonAST.getOrElse(throw new IllegalArgumentException())
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 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