109 lines
2.6 KiB
Go
109 lines
2.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"git.haelnorr.com/h/golib/cookies"
|
|
"git.haelnorr.com/h/golib/hws"
|
|
"git.haelnorr.com/h/golib/notify"
|
|
"git.haelnorr.com/h/oslstats/internal/config"
|
|
"git.haelnorr.com/h/oslstats/internal/db"
|
|
"git.haelnorr.com/h/oslstats/internal/throw"
|
|
"git.haelnorr.com/h/oslstats/internal/view/popup"
|
|
|
|
"github.com/coder/websocket"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
func NotificationWS(
|
|
s *hws.Server,
|
|
cfg *config.Config,
|
|
) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Header.Get("Upgrade") != "websocket" {
|
|
throw.NotFound(s, w, r, r.URL.Path)
|
|
return
|
|
}
|
|
nc, err := setupClient(s, w, r)
|
|
if err != nil {
|
|
logError(s, "Failed to get notification client", errors.Wrap(err, "setupClient"))
|
|
return
|
|
}
|
|
ws, err := websocket.Accept(w, r, &websocket.AcceptOptions{
|
|
OriginPatterns: []string{cfg.HWSAuth.TrustedHost},
|
|
})
|
|
if err != nil {
|
|
logError(s, "Failed to open websocket", errors.Wrap(err, "websocket.Accept"))
|
|
return
|
|
}
|
|
ctx := ws.CloseRead(r.Context())
|
|
err = notifyLoop(ctx, nc, ws)
|
|
if err != nil {
|
|
logError(s, "Notification error", errors.Wrap(err, "notifyLoop"))
|
|
}
|
|
_ = ws.CloseNow()
|
|
},
|
|
)
|
|
}
|
|
|
|
func setupClient(s *hws.Server, w http.ResponseWriter, r *http.Request) (*hws.Client, error) {
|
|
user := db.CurrentUser(r.Context())
|
|
altID := ""
|
|
if user != nil {
|
|
altID = strconv.Itoa(user.ID)
|
|
}
|
|
subCookie, err := r.Cookie("ws_sub_id")
|
|
subID := ""
|
|
if err == nil {
|
|
subID = subCookie.Value
|
|
}
|
|
nc, err := s.GetClient(subID, altID)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "s.GetClient")
|
|
}
|
|
cookies.SetCookie(w, "ws_sub_id", "/", nc.ID(), 0)
|
|
return nc, nil
|
|
}
|
|
|
|
func notifyLoop(ctx context.Context, c *hws.Client, ws *websocket.Conn) error {
|
|
notifs, stop := c.Listen()
|
|
defer close(stop)
|
|
count := 0
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case nt, ok := <-notifs:
|
|
count++
|
|
if !ok {
|
|
return nil
|
|
}
|
|
w, err := ws.Writer(ctx, websocket.MessageText)
|
|
if err != nil {
|
|
return errors.Wrap(err, "ws.Writer")
|
|
}
|
|
switch nt.Level {
|
|
case hws.LevelShutdown:
|
|
err = popup.Toast(nt, count, 30000).Render(ctx, w)
|
|
case notify.LevelWarn:
|
|
err = popup.Toast(nt, count, 10000).Render(ctx, w)
|
|
case notify.LevelError:
|
|
// Parse error code and stacktrace from Details field
|
|
code, stacktrace := parseErrorDetails(nt.Details)
|
|
err = popup.ErrorModalWS(code, stacktrace, nt, count).Render(ctx, w)
|
|
default:
|
|
err = popup.Toast(nt, count, 6000).Render(ctx, w)
|
|
}
|
|
if err != nil {
|
|
return errors.Wrap(err, "popup.Toast")
|
|
}
|
|
err = w.Close()
|
|
if err != nil {
|
|
return errors.Wrap(err, "w.Close")
|
|
}
|
|
}
|
|
}
|
|
}
|