mirror of
https://github.com/phfroidmont/scalive.git
synced 2025-12-25 05:26:59 +01:00
Ability to read websocket join messages
This commit is contained in:
parent
aae3db841b
commit
fadef26425
8 changed files with 158 additions and 52 deletions
|
|
@ -1,11 +0,0 @@
|
|||
import morphdom from 'morphdom';
|
||||
|
||||
var el1 = document.createElement('div');
|
||||
el1.className = 'foo';
|
||||
|
||||
var el2 = document.createElement('div');
|
||||
el2.className = 'bar';
|
||||
|
||||
morphdom(el1, el2);
|
||||
|
||||
export const scalive = "scalive"
|
||||
|
|
@ -24,6 +24,9 @@ class HtmlElement(val tag: HtmlTag, val mods: Vector[Mod]):
|
|||
private[scalive] def syncAll(): Unit = mods.foreach(_.syncAll())
|
||||
private[scalive] def setAllUnchanged(): Unit = dynamicMods.foreach(_.setAllUnchanged())
|
||||
|
||||
def prepended(mod: Mod*): HtmlElement = HtmlElement(tag, mods.prependedAll(mod))
|
||||
def apended(mod: Mod*): HtmlElement = HtmlElement(tag, mods.appendedAll(mod))
|
||||
|
||||
class HtmlTag(val name: String, val void: Boolean = false):
|
||||
def apply(mods: Mod*): HtmlElement = HtmlElement(this, mods.toVector)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,22 +2,27 @@ package scalive
|
|||
|
||||
import zio.json.*
|
||||
|
||||
import java.util.Base64
|
||||
import scala.util.Random
|
||||
|
||||
final case class Socket[Cmd](lv: LiveView[Cmd]):
|
||||
|
||||
lv.el.syncAll()
|
||||
|
||||
private var clientInitialized = false
|
||||
val id: String = "scl-123"
|
||||
val id: String = s"phx-${Base64.getEncoder().encodeToString(Random().nextBytes(8))}"
|
||||
private val token = Token.sign("secret", id, "")
|
||||
private val element = lv.el.prepended(idAttr := id, dataAttr("phx-session") := token)
|
||||
|
||||
element.syncAll()
|
||||
|
||||
def receiveCommand(cmd: Cmd): Unit =
|
||||
lv.handleCommand(cmd)
|
||||
|
||||
def renderHtml(rootLayout: HtmlElement => HtmlElement = identity): String =
|
||||
lv.el.syncAll()
|
||||
HtmlBuilder.build(rootLayout(lv.el))
|
||||
element.syncAll()
|
||||
HtmlBuilder.build(rootLayout(element))
|
||||
|
||||
def syncClient: Unit =
|
||||
lv.el.syncAll()
|
||||
println(DiffBuilder.build(lv.el, trackUpdates = clientInitialized).toJsonPretty)
|
||||
element.syncAll()
|
||||
println(DiffBuilder.build(element, trackUpdates = clientInitialized).toJsonPretty)
|
||||
clientInitialized = true
|
||||
lv.el.setAllUnchanged()
|
||||
element.setAllUnchanged()
|
||||
|
|
|
|||
59
core/src/scalive/Token.scala
Normal file
59
core/src/scalive/Token.scala
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package scalive
|
||||
|
||||
import zio.json.*
|
||||
|
||||
import java.time.Instant
|
||||
import java.util.Base64
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import scala.concurrent.duration.Duration
|
||||
import scala.util.Random
|
||||
|
||||
final case class Token[T] private (
|
||||
version: Int,
|
||||
liveViewId: String,
|
||||
payload: T,
|
||||
issuedAt: Long,
|
||||
salt: String)
|
||||
derives JsonCodec
|
||||
|
||||
object Token:
|
||||
private val version = 1
|
||||
|
||||
def sign[T: JsonCodec](secret: String, liveViewId: String, payload: T)
|
||||
: String = // TODO use messagepack and add salt
|
||||
val salt = Random.nextString(16)
|
||||
val token =
|
||||
Token(version, liveViewId, payload, Instant.now().toEpochMilli(), salt).toJson.getBytes()
|
||||
val tokenHash = hash(secret, token)
|
||||
|
||||
s"${base64Encode(token)}.${base64Encode(tokenHash)}"
|
||||
|
||||
private def hash(secret: String, value: Array[Byte]): Array[Byte] =
|
||||
val mac = Mac.getInstance("HmacSHA256")
|
||||
mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"))
|
||||
mac.doFinal(value)
|
||||
|
||||
private def base64Encode(value: Array[Byte]): String =
|
||||
Base64.getEncoder().encodeToString(value)
|
||||
|
||||
private def base64Decode(value: String): Array[Byte] =
|
||||
Base64.getDecoder().decode(value)
|
||||
|
||||
def verify[T: JsonCodec](secret: String, token: String, maxAge: Duration)
|
||||
: Either[String, (liveViewId: String, payload: T)] =
|
||||
val tokenBase64 = token.takeWhile(_ != '.')
|
||||
val hashBase64 = token.drop(tokenBase64.length)
|
||||
val tokenBytes = base64Decode(tokenBase64)
|
||||
val tokenValue = String.valueOf(tokenBytes).fromJson[Token[T]]
|
||||
|
||||
val currentHash = hash(secret, tokenBytes)
|
||||
|
||||
if base64Decode(hashBase64) == currentHash then
|
||||
tokenValue.flatMap(t =>
|
||||
if (t.issuedAt + maxAge.toMillis) > Instant.now().toEpochMilli() then Left("Token expired")
|
||||
else Right(t.liveViewId, t.payload)
|
||||
)
|
||||
else Left("Invalid signature")
|
||||
|
||||
end Token
|
||||
Loading…
Add table
Add a link
Reference in a new issue