feat: optional per-room JetStream persistence (history + offline replay), gated by RoomPolicy.Persist

This commit is contained in:
agent
2026-06-03 21:48:55 +02:00
parent bb2e412744
commit 8c680bc002
8 changed files with 578 additions and 45 deletions
+10 -11
View File
@@ -34,8 +34,6 @@ import (
_ "embed"
"github.com/nats-io/nats.go"
"github.com/enmanuel/unibus/pkg/blobstore"
"github.com/enmanuel/unibus/pkg/client"
"github.com/enmanuel/unibus/pkg/embeddednats"
@@ -86,9 +84,9 @@ type peerState struct {
endpoint client.Endpoint
mu sync.Mutex
subs map[string]*nats.Subscription // roomID -> subscription
rooms map[string]roomInfo // roomID -> subject/encrypt
listeners map[chan Event]struct{} // attached SSE channels
subs map[string]*client.Sub // roomID -> subscription
rooms map[string]roomInfo // roomID -> subject/encrypt
listeners map[chan Event]struct{} // attached SSE channels
}
// emit fans an event out to all attached listeners without blocking on a slow
@@ -155,7 +153,7 @@ func (h *Hub) getOrCreate(name string) (*peerState, error) {
name: name,
client: c,
endpoint: c.Endpoint(),
subs: map[string]*nats.Subscription{},
subs: map[string]*client.Sub{},
rooms: map[string]roomInfo{},
listeners: map[chan Event]struct{}{},
}
@@ -311,6 +309,7 @@ func (h *Hub) handleRoom(w http.ResponseWriter, r *http.Request) {
Peer string `json:"peer"`
Subject string `json:"subject"`
Encrypt bool `json:"encrypt"`
Persist bool `json:"persist"`
}
if err := decodeBody(r, &req); err != nil || req.Peer == "" || req.Subject == "" {
writeErr(w, http.StatusBadRequest, "peer and subject required")
@@ -321,10 +320,10 @@ func (h *Hub) handleRoom(w http.ResponseWriter, r *http.Request) {
writeErr(w, http.StatusBadRequest, "unknown peer "+req.Peer)
return
}
policy := room.ModeNATS
if req.Encrypt {
policy = room.ModeMatrix
}
// The two checkboxes map to an explicit per-room policy. encrypt drives both
// encryption and per-message signing; persist (default false) independently
// toggles durable JetStream history. persist=false keeps plain ephemeral NATS.
policy := room.Policy{Encrypt: req.Encrypt, Persist: req.Persist, SignMsgs: req.Encrypt}
roomID, err := p.client.CreateRoom(req.Subject, policy)
if err != nil {
writeErr(w, http.StatusInternalServerError, err.Error())
@@ -336,7 +335,7 @@ func (h *Hub) handleRoom(w http.ResponseWriter, r *http.Request) {
return
}
writeJSON(w, http.StatusOK, map[string]any{
"room_id": roomID, "subject": req.Subject, "encrypt": req.Encrypt,
"room_id": roomID, "subject": req.Subject, "encrypt": req.Encrypt, "persist": req.Persist,
})
}