blasphem/pkg/wsapi/api.go

265 lines
4.8 KiB
Go
Raw Normal View History

2022-12-19 19:24:01 -05:00
package wsapi
import (
2022-12-21 13:22:18 -05:00
"context"
2022-12-20 19:05:45 -05:00
"encoding/json"
"errors"
2022-12-20 20:30:58 -05:00
"strings"
2022-12-19 19:24:01 -05:00
"dynatron.me/x/blasphem/pkg/auth"
2022-12-20 19:05:45 -05:00
"dynatron.me/x/blasphem/pkg/blas/core"
2022-12-19 19:24:01 -05:00
"github.com/gorilla/websocket"
"github.com/labstack/echo/v4"
2022-12-20 21:22:00 -05:00
"github.com/mitchellh/mapstructure"
2022-12-19 19:24:01 -05:00
"github.com/rs/zerolog/log"
)
2022-12-20 19:05:45 -05:00
var (
NoSuchHandlerErr = errors.New("bad websocket command")
2022-12-20 20:34:53 -05:00
NoMessageIDErr = errors.New("no message ID")
2022-12-21 13:22:18 -05:00
AuthInvalidErr = errors.New("invalid auth")
2022-12-20 19:05:45 -05:00
)
2022-12-19 19:24:01 -05:00
type Type string
type MsgBase struct {
2022-12-21 13:22:26 -05:00
ID *int `json:"id,omitempty"`
2022-12-19 19:24:01 -05:00
Type Type `json:"type"`
}
type (
wsSession struct {
2022-12-21 13:22:26 -05:00
conn *websocket.Conn
b core.Blas
ec echo.Context
2022-12-21 13:22:18 -05:00
write chan<- interface{}
2022-12-19 19:24:01 -05:00
2022-12-20 13:16:30 -05:00
user *auth.User
2022-12-19 19:24:01 -05:00
refreshToken *auth.RefreshToken
}
2022-12-20 19:05:45 -05:00
wsEntry struct {
dataNew core.NewData
hnd core.Handler
}
wsRegistry map[string]wsEntry
wsManager struct {
r wsRegistry
}
2022-12-19 19:24:01 -05:00
)
2022-12-20 19:05:45 -05:00
func (wsm *wsManager) RegisterWSCommand(cmd string, hnd core.Handler, dataNew core.NewData) {
wsm.r[cmd] = wsEntry{
dataNew: dataNew,
hnd: hnd,
}
}
2022-12-20 20:34:45 -05:00
func (wsm *wsManager) WSCommandHandler(cmd string, cmdSplit []string) (core.NewData, core.Handler, error) {
if wse, ok := wsm.r[cmd]; ok {
return wse.dataNew, wse.hnd, nil
}
if wse, ok := wsm.r[cmdSplit[0]]; ok {
return wse.dataNew, wse.hnd, nil
2022-12-20 19:05:45 -05:00
}
2022-12-20 20:34:45 -05:00
return nil, nil, NoSuchHandlerErr
2022-12-20 19:05:45 -05:00
}
func NewManager() core.WebSocketManager {
return &wsManager{
r: make(wsRegistry),
}
}
func NewSession(s core.Blas, c echo.Context, conn *websocket.Conn) core.WebSocketSession {
2022-12-19 19:24:01 -05:00
ws := &wsSession{
2022-12-21 13:22:18 -05:00
conn: conn,
2022-12-20 13:16:30 -05:00
b: s,
ec: c,
2022-12-19 19:24:01 -05:00
}
return ws
}
2022-12-21 13:22:18 -05:00
func (ws *wsSession) Conn() *websocket.Conn {
return ws.conn
2022-12-20 20:30:58 -05:00
}
2022-12-20 19:05:45 -05:00
func (ws *wsSession) Blas() core.Blas { return ws.b }
2022-12-21 13:22:18 -05:00
func (ws *wsSession) Go(ctx context.Context) error {
authP := &authPhase{ws}
2022-12-19 19:24:01 -05:00
err := ws.sendAuthRequired()
if err != nil {
return err
}
2022-12-21 13:22:26 -05:00
2022-12-21 13:22:18 -05:00
_, rdr, err := ws.conn.NextReader()
if err != nil {
return err
}
err = authP.handleMsg(ctx, rdr)
2022-12-21 19:34:26 -05:00
if err != nil || ws.refreshToken == nil {
2022-12-21 13:22:18 -05:00
return err
}
// command phase
msgChan := make(chan map[string]interface{})
write := make(chan interface{})
ws.write = write
defer close(write)
cCtx, cancel := context.WithCancel(ctx)
defer cancel()
go func(ctx context.Context, ch chan<- map[string]interface{}) {
defer close(ch)
for {
if err := ctx.Err(); err != nil {
return
}
_, rdr, err := ws.conn.NextReader()
switch {
case err == nil:
case websocket.IsCloseError(err, websocket.CloseGoingAway):
return
case err != nil:
log.Error().Err(err).Str("remote", ws.ec.Request().RemoteAddr).Msg("websocket read fail")
return
}
var msgMap map[string]interface{}
err = json.NewDecoder(rdr).Decode(&msgMap)
if err != nil {
ws.writeError(-1, Error{Code: "invalid_format", Message: err.Error()})
}
ch <- msgMap
2022-12-19 19:24:01 -05:00
}
2022-12-21 13:22:18 -05:00
}(cCtx, msgChan)
2022-12-19 19:24:01 -05:00
2022-12-21 13:22:18 -05:00
for {
select {
case msg, ok := <-msgChan:
if !ok {
return nil
}
err = ws.handleMsg(ctx, msg)
if err != nil {
log.Error().Err(err).Msg("handleMsg")
}
case <-ctx.Done():
2022-12-21 19:34:26 -05:00
close(msgChan) // maybe remove this?
2022-12-21 13:22:18 -05:00
return nil
case m := <-write:
err := ws.conn.WriteJSON(m)
if err != nil {
log.Error().Err(err).Msg("writeMsg")
}
2022-12-19 19:24:01 -05:00
}
}
}
2022-12-21 13:22:18 -05:00
func (ws *wsSession) Write(msg interface{}) {
ws.write <- msg
2022-12-19 19:24:01 -05:00
}
type MsgType string
2022-12-20 20:34:53 -05:00
2022-12-20 20:11:11 -05:00
const (
ResultMsgType MsgType = "result"
)
type Error struct {
2022-12-20 20:34:53 -05:00
Code string `json:"code"`
2022-12-20 20:11:11 -05:00
Message string `json:"message"`
}
type WSError struct {
2022-12-20 20:34:53 -05:00
ID *int `json:"id,omitempty"`
Type MsgType `json:"type"`
Success bool `json:"success"`
Error Error `json:"error"`
2022-12-20 20:11:11 -05:00
}
2022-12-21 13:22:18 -05:00
func (ws *wsSession) writeError(id int, err Error) error {
return ws.conn.WriteJSON(WSError{
2022-12-20 20:34:53 -05:00
ID: &id,
Type: ResultMsgType,
2022-12-20 20:11:11 -05:00
Success: false,
2022-12-20 20:34:53 -05:00
Error: err,
2022-12-20 20:11:11 -05:00
})
}
2022-12-19 19:24:01 -05:00
2022-12-21 13:22:18 -05:00
func (ws *wsSession) handleMsg(ctx context.Context, msgMap map[string]interface{}) error {
if err := ctx.Err(); err != nil {
return nil
2022-12-20 19:05:45 -05:00
}
msgType, ok := msgMap["type"].(string)
if !ok {
return NoSuchHandlerErr
}
2022-12-20 20:11:11 -05:00
idFl, ok := msgMap["id"].(float64)
2022-12-20 19:05:45 -05:00
if !ok {
2022-12-21 13:22:18 -05:00
ws.Write(WSError{
2022-12-21 13:22:26 -05:00
Type: ResultMsgType,
Success: false,
Error: Error{
Code: "invalid_id",
Message: "command has no ID",
},
})
2022-12-21 13:22:18 -05:00
return nil
2022-12-20 20:11:11 -05:00
}
id := int(idFl)
2022-12-20 20:30:58 -05:00
cmd := strings.Split(msgType, "/")
2022-12-20 20:34:45 -05:00
newData, hand, err := ws.b.WSCommandHandler(cmd[0], cmd)
2022-12-20 20:11:11 -05:00
switch err {
case nil:
case NoSuchHandlerErr:
2022-12-21 13:22:18 -05:00
ws.writeError(id, Error{
2022-12-20 20:34:53 -05:00
Code: "invalid_type",
2022-12-20 20:11:11 -05:00
Message: "no such command",
})
2022-12-21 13:22:18 -05:00
return nil
2022-12-20 20:11:11 -05:00
default:
2022-12-21 13:22:18 -05:00
log.Error().Err(err).Msg("dispatch")
return nil
2022-12-20 19:05:45 -05:00
}
2022-12-20 20:30:58 -05:00
nd := newData(cmd)
if _, ok := nd.(map[string]interface{}); !ok {
err := mapstructure.Decode(&msgMap, &nd)
if err != nil {
ws.writeError(id, Error{
Code: "invalid_format",
Message: err.Error(),
})
2022-12-20 21:22:00 -05:00
return nil
}
2022-12-20 21:22:00 -05:00
}
2022-12-21 13:22:18 -05:00
err = hand(ctx, ws, id, cmd, nd)
if err != nil {
log.Error().Err(err).Msg("dispatch")
}
return nil
2022-12-19 19:24:01 -05:00
}