node build fixed
This commit is contained in:
289
seanime-2.9.10/internal/events/websocket.go
Normal file
289
seanime-2.9.10/internal/events/websocket.go
Normal file
@@ -0,0 +1,289 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"os"
|
||||
"seanime/internal/util"
|
||||
"seanime/internal/util/result"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type WSEventManagerInterface interface {
|
||||
SendEvent(t string, payload interface{})
|
||||
SendEventTo(clientId string, t string, payload interface{}, noLog ...bool)
|
||||
SubscribeToClientEvents(id string) *ClientEventSubscriber
|
||||
SubscribeToClientNativePlayerEvents(id string) *ClientEventSubscriber
|
||||
SubscribeToClientNakamaEvents(id string) *ClientEventSubscriber
|
||||
UnsubscribeFromClientEvents(id string)
|
||||
}
|
||||
|
||||
type GlobalWSEventManagerWrapper struct {
|
||||
WSEventManager WSEventManagerInterface
|
||||
}
|
||||
|
||||
var GlobalWSEventManager *GlobalWSEventManagerWrapper
|
||||
|
||||
func (w *GlobalWSEventManagerWrapper) SendEvent(t string, payload interface{}) {
|
||||
if w.WSEventManager == nil {
|
||||
return
|
||||
}
|
||||
w.WSEventManager.SendEvent(t, payload)
|
||||
}
|
||||
|
||||
func (w *GlobalWSEventManagerWrapper) SendEventTo(clientId string, t string, payload interface{}, noLog ...bool) {
|
||||
if w.WSEventManager == nil {
|
||||
return
|
||||
}
|
||||
w.WSEventManager.SendEventTo(clientId, t, payload, noLog...)
|
||||
}
|
||||
|
||||
type (
|
||||
// WSEventManager holds the websocket connection instance.
|
||||
// It is attached to the App instance, so it is available to other handlers.
|
||||
WSEventManager struct {
|
||||
Conns []*WSConn
|
||||
Logger *zerolog.Logger
|
||||
hasHadConnection bool
|
||||
mu sync.Mutex
|
||||
eventMu sync.RWMutex
|
||||
clientEventSubscribers *result.Map[string, *ClientEventSubscriber]
|
||||
clientNativePlayerEventSubscribers *result.Map[string, *ClientEventSubscriber]
|
||||
nakamaEventSubscribers *result.Map[string, *ClientEventSubscriber]
|
||||
}
|
||||
|
||||
ClientEventSubscriber struct {
|
||||
Channel chan *WebsocketClientEvent
|
||||
mu sync.RWMutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
WSConn struct {
|
||||
ID string
|
||||
Conn *websocket.Conn
|
||||
}
|
||||
|
||||
WSEvent struct {
|
||||
Type string `json:"type"`
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
)
|
||||
|
||||
// NewWSEventManager creates a new WSEventManager instance for App.
|
||||
func NewWSEventManager(logger *zerolog.Logger) *WSEventManager {
|
||||
ret := &WSEventManager{
|
||||
Logger: logger,
|
||||
Conns: make([]*WSConn, 0),
|
||||
clientEventSubscribers: result.NewResultMap[string, *ClientEventSubscriber](),
|
||||
clientNativePlayerEventSubscribers: result.NewResultMap[string, *ClientEventSubscriber](),
|
||||
nakamaEventSubscribers: result.NewResultMap[string, *ClientEventSubscriber](),
|
||||
}
|
||||
GlobalWSEventManager = &GlobalWSEventManagerWrapper{
|
||||
WSEventManager: ret,
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// ExitIfNoConnsAsDesktopSidecar monitors the websocket connection as a desktop sidecar.
|
||||
// It checks for a connection every 5 seconds. If a connection is lost, it starts a countdown a waits for 15 seconds.
|
||||
// If a connection is not established within 15 seconds, it will exit the app.
|
||||
func (m *WSEventManager) ExitIfNoConnsAsDesktopSidecar() {
|
||||
go func() {
|
||||
defer util.HandlePanicInModuleThen("events/ExitIfNoConnsAsDesktopSidecar", func() {})
|
||||
|
||||
m.Logger.Info().Msg("ws: Monitoring connection as desktop sidecar")
|
||||
// Create a ticker to check connection every 5 seconds
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
// Track connection loss time
|
||||
var connectionLostTime time.Time
|
||||
exitTimeout := 10 * time.Second
|
||||
|
||||
for range ticker.C {
|
||||
// Check WebSocket connection status
|
||||
if len(m.Conns) == 0 && m.hasHadConnection {
|
||||
// If not connected and first detection of connection loss
|
||||
if connectionLostTime.IsZero() {
|
||||
m.Logger.Warn().Msg("ws: No connection detected. Starting countdown...")
|
||||
connectionLostTime = time.Now()
|
||||
}
|
||||
|
||||
// Check if connection has been lost for more than 15 seconds
|
||||
if time.Since(connectionLostTime) > exitTimeout {
|
||||
m.Logger.Warn().Msg("ws: No connection detected for 10 seconds. Exiting...")
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
// Connection is active, reset connection lost time
|
||||
connectionLostTime = time.Time{}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *WSEventManager) AddConn(id string, conn *websocket.Conn) {
|
||||
m.hasHadConnection = true
|
||||
m.Conns = append(m.Conns, &WSConn{
|
||||
ID: id,
|
||||
Conn: conn,
|
||||
})
|
||||
}
|
||||
|
||||
func (m *WSEventManager) RemoveConn(id string) {
|
||||
for i, conn := range m.Conns {
|
||||
if conn.ID == id {
|
||||
m.Conns = append(m.Conns[:i], m.Conns[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SendEvent sends a websocket event to the client.
|
||||
func (m *WSEventManager) SendEvent(t string, payload interface{}) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
// If there's no connection, do nothing
|
||||
//if m.Conn == nil {
|
||||
// return
|
||||
//}
|
||||
|
||||
if t != PlaybackManagerProgressPlaybackState && payload == nil {
|
||||
m.Logger.Trace().Str("type", t).Msg("ws: Sending message")
|
||||
}
|
||||
|
||||
for _, conn := range m.Conns {
|
||||
err := conn.Conn.WriteJSON(WSEvent{
|
||||
Type: t,
|
||||
Payload: payload,
|
||||
})
|
||||
if err != nil {
|
||||
// Note: NaN error coming from [progress_tracking.go]
|
||||
//m.Logger.Err(err).Msg("ws: Failed to send message")
|
||||
}
|
||||
//m.Logger.Trace().Str("type", t).Msg("ws: Sent message")
|
||||
}
|
||||
|
||||
//err := m.Conn.WriteJSON(WSEvent{
|
||||
// Type: t,
|
||||
// Payload: payload,
|
||||
//})
|
||||
//if err != nil {
|
||||
// m.Logger.Err(err).Msg("ws: Failed to send message")
|
||||
//}
|
||||
//m.Logger.Trace().Str("type", t).Msg("ws: Sent message")
|
||||
}
|
||||
|
||||
// SendEventTo sends a websocket event to the specified client.
|
||||
func (m *WSEventManager) SendEventTo(clientId string, t string, payload interface{}, noLog ...bool) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
for _, conn := range m.Conns {
|
||||
if conn.ID == clientId {
|
||||
if t != "pong" {
|
||||
if len(noLog) == 0 || !noLog[0] {
|
||||
truncated := spew.Sprint(payload)
|
||||
if len(truncated) > 500 {
|
||||
truncated = truncated[:500] + "..."
|
||||
}
|
||||
m.Logger.Trace().Str("to", clientId).Str("type", t).Str("payload", truncated).Msg("ws: Sending message")
|
||||
}
|
||||
}
|
||||
_ = conn.Conn.WriteJSON(WSEvent{
|
||||
Type: t,
|
||||
Payload: payload,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *WSEventManager) SendStringTo(clientId string, s string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
for _, conn := range m.Conns {
|
||||
if conn.ID == clientId {
|
||||
_ = conn.Conn.WriteMessage(websocket.TextMessage, []byte(s))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *WSEventManager) OnClientEvent(event *WebsocketClientEvent) {
|
||||
m.eventMu.RLock()
|
||||
defer m.eventMu.RUnlock()
|
||||
|
||||
onEvent := func(key string, subscriber *ClientEventSubscriber) bool {
|
||||
go func() {
|
||||
defer util.HandlePanicInModuleThen("events/OnClientEvent/clientNativePlayerEventSubscribers", func() {})
|
||||
subscriber.mu.RLock()
|
||||
defer subscriber.mu.RUnlock()
|
||||
if !subscriber.closed {
|
||||
select {
|
||||
case subscriber.Channel <- event:
|
||||
default:
|
||||
// Channel is blocked, skip sending
|
||||
m.Logger.Warn().Msg("ws: Client event channel is blocked, event dropped")
|
||||
}
|
||||
}
|
||||
}()
|
||||
return true
|
||||
}
|
||||
|
||||
switch event.Type {
|
||||
case NativePlayerEventType:
|
||||
m.clientNativePlayerEventSubscribers.Range(onEvent)
|
||||
case NakamaEventType:
|
||||
m.nakamaEventSubscribers.Range(onEvent)
|
||||
default:
|
||||
m.clientEventSubscribers.Range(onEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *WSEventManager) SubscribeToClientEvents(id string) *ClientEventSubscriber {
|
||||
subscriber := &ClientEventSubscriber{
|
||||
Channel: make(chan *WebsocketClientEvent, 900),
|
||||
}
|
||||
m.clientEventSubscribers.Set(id, subscriber)
|
||||
return subscriber
|
||||
}
|
||||
|
||||
func (m *WSEventManager) SubscribeToClientNativePlayerEvents(id string) *ClientEventSubscriber {
|
||||
subscriber := &ClientEventSubscriber{
|
||||
Channel: make(chan *WebsocketClientEvent, 100),
|
||||
}
|
||||
m.clientNativePlayerEventSubscribers.Set(id, subscriber)
|
||||
return subscriber
|
||||
}
|
||||
|
||||
func (m *WSEventManager) SubscribeToClientNakamaEvents(id string) *ClientEventSubscriber {
|
||||
subscriber := &ClientEventSubscriber{
|
||||
Channel: make(chan *WebsocketClientEvent, 100),
|
||||
}
|
||||
m.nakamaEventSubscribers.Set(id, subscriber)
|
||||
return subscriber
|
||||
}
|
||||
|
||||
func (m *WSEventManager) UnsubscribeFromClientEvents(id string) {
|
||||
m.eventMu.Lock()
|
||||
defer m.eventMu.Unlock()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
m.Logger.Warn().Msg("ws: Failed to unsubscribe from client events")
|
||||
}
|
||||
}()
|
||||
subscriber, ok := m.clientEventSubscribers.Get(id)
|
||||
if !ok {
|
||||
subscriber, ok = m.clientNativePlayerEventSubscribers.Get(id)
|
||||
}
|
||||
if ok {
|
||||
subscriber.mu.Lock()
|
||||
defer subscriber.mu.Unlock()
|
||||
subscriber.closed = true
|
||||
m.clientEventSubscribers.Delete(id)
|
||||
close(subscriber.Channel)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user