Fuaran.UI.ServerDriven.WebSocket
0.46.0
Prefix Reserved
dotnet add package Fuaran.UI.ServerDriven.WebSocket --version 0.46.0
NuGet\Install-Package Fuaran.UI.ServerDriven.WebSocket -Version 0.46.0
<PackageReference Include="Fuaran.UI.ServerDriven.WebSocket" Version="0.46.0" />
<PackageVersion Include="Fuaran.UI.ServerDriven.WebSocket" Version="0.46.0" />
<PackageReference Include="Fuaran.UI.ServerDriven.WebSocket" />
paket add Fuaran.UI.ServerDriven.WebSocket --version 0.46.0
#r "nuget: Fuaran.UI.ServerDriven.WebSocket, 0.46.0"
#:package Fuaran.UI.ServerDriven.WebSocket@0.46.0
#addin nuget:?package=Fuaran.UI.ServerDriven.WebSocket&version=0.46.0
#tool nuget:?package=Fuaran.UI.ServerDriven.WebSocket&version=0.46.0
Fuaran.UI.ServerDriven.WebSocket
Backend 2 for Fuaran.UI.ServerDriven — one bidirectional WebSocket
channel over ASP.NET. The lower-latency transport for measured high-frequency
interaction (per-keystroke, drag, pointer-move, live cursors).
This is a first-class second backend, not a placeholder. Its value is twofold:
the latency win above, and — structurally — it differs from the SSE+POST backend
only in its channel + endpoint glue (WsChannel mirrors SseChannel; the
driver / diff / lowering core is untouched). That structural identity is the
architectural-integrity check that IFuaranLiveChannel is genuinely
transport-neutral and not accidentally SSE-shaped.
That identity is now asserted rather than asserted-about. The two backends share one connection-token implementation and one inbound budget, both in the transport-agnostic core, and a transport-parity test pins their configs against each other — because for a while this README's neighbouring config comment claimed a security parity the code did not have, and a claim no test reads is just a sentence. Neither package references the other; what they share, they share through the core.
Wiring
open Fuaran.UI.ServerDriven.Driver
open Fuaran.UI.ServerDriven.WebSocket.Endpoints
app.UseWebSockets() |> ignore // required
let makeSession () =
Driver.init (DriverServices.create renderFragment) update view initialModel
mapFuaranLiveWebSocket app (defaultWsConfig makeSession)
Connecting: mint, then upgrade
mapFuaranLiveWebSocket maps two endpoints. GET /live/ws-token resolves the
request principal, mints a connection token bound to it, and sets it as a
hardened correlation cookie (HttpOnly + Secure + SameSite=Strict).
GET /live/ws then upgrades only if that cookie verifies against the
principal on the upgrade request; anything else is a 401 and no socket is
accepted. So a client fetches the token first, with credentials, and then
connects:
await fetch("/live/ws-token", { credentials: "same-origin" })
const ws = new WebSocket(`wss://${location.host}/live/ws`)
The order matters and the split is not ceremony. A WebSocket upgrade is a single
request that both opens and authorises the session, so there is no second
request to gate the way the SSE backend gates its POST — the token has to
exist before the handshake or there is nothing to check. Two endpoints give the
two transports the same shape: one mints the principal-bound token, the other
refuses anything not carrying it.
SameSite=Strict is doing specific work here. A browser applies no same-origin
policy to a WebSocket handshake, so an attacker's page can open a socket to your
server with the victim's cookies attached — cross-site WebSocket hijacking. A
Strict cookie is not sent on that handshake, so the upgrade carries no token and
is refused.
The host must still layer authentication in front of these paths. The principal resolver reflects whatever identity that authentication established; with none wired, every visitor resolves to the empty principal and the token closes forgeability but not authorisation.
Inbound messages are capped (1 MB by default, MaxMessageBytes), because the
fragment accumulator only flushes at EndOfMessage and an endless fragment
stream would otherwise grow server memory without bound. Exceeding the budget
closes the socket with MessageTooBig (1009). The budget itself lives in
Fuaran.UI.ServerDriven.LiveLimits, shared with the SSE backend.
GET /live/ws upgrades to a WebSocket; the same socket carries outbound frames
(FrameWire.encodeJson — raw JSON, no SSE id:/event: framing) and inbound
events (parsed → LiveConnection.Handle → G1 → driver → frames back). The
client uses the shim's WebSocket transport adapter (a Track-B drop-in — only the
connect/send adapter changes; the patch/effect/delegation core is identical).
SSE+POST vs WebSocket
Default to SSE+POST (Fuaran.UI.ServerDriven.AspNetCore): it traverses infra
natively, gets per-event HTTP governance for free, and reconnects via
Last-Event-ID. Reach for WebSocket when a measured interaction pattern
needs the lower per-message latency. The IFuaranLiveChannel seam makes that a
swap, not a rewrite. WS owns its own reconnect + resequencing client-side (no
EventSource freebie); the server half is Frame.Seq + LiveConnection.Resync.
See fuaran-dotnet/docs/SERVER_DRIVEN.md.
No platform-SDK dependency. Apache-2.0 licensed — see the repo LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Fable.Core (>= 5.0.0)
- FSharp.Core (>= 10.1.300)
- Fuaran.Core.Column (>= 0.11.0)
- Fuaran.Core.DataFrame (>= 0.11.0)
- Fuaran.Core.Function (>= 0.11.0)
- Fuaran.Core.Ops (>= 0.11.0)
- Fuaran.Core.OpStream (>= 0.11.0)
- Fuaran.Core.Tree (>= 0.11.0)
- Fuaran.Core.Validator (>= 0.11.0)
- Fuaran.Core.Wire (>= 0.11.0)
- Fuaran.UI.ServerDriven (>= 0.46.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.46.0 | 0 | 8/28/2026 |
| 0.39.0 | 54 | 8/27/2026 |
| 0.35.0 | 83 | 8/24/2026 |
| 0.32.0 | 79 | 8/23/2026 |
| 0.31.0 | 89 | 8/21/2026 |
| 0.30.0 | 88 | 8/21/2026 |
| 0.29.0 | 91 | 8/19/2026 |
| 0.28.0 | 83 | 8/18/2026 |
| 0.27.0 | 86 | 8/18/2026 |
| 0.26.0 | 98 | 8/18/2026 |
| 0.18.0 | 94 | 8/10/2026 |
| 0.15.0 | 88 | 8/9/2026 |
| 0.12.0 | 102 | 7/31/2026 |
| 0.11.0 | 99 | 7/30/2026 |
| 0.10.0 | 101 | 7/29/2026 |
| 0.6.0 | 100 | 7/27/2026 |
| 0.4.0 | 101 | 7/26/2026 |
| 0.3.0 | 112 | 7/24/2026 |