package connection import ( "context" "log" "mchess_server/types" "sync" "github.com/google/uuid" gorillaws "github.com/gorilla/websocket" ) type Connection struct { ID uuid.UUID ws *gorillaws.Conn ctx context.Context rxBuffer *MessageBuffer txBuffer *MessageBuffer disconnectCallback func() forColor types.ChessColor /* this is a hack since a user using the same browser might get the same object of type Connection in two instances of the same browser. Then if Close() gets called for both of these instances the first Close() will set ws=nil, and the seconds one will dereference this nil pointer. The correct way to fix this, is to make sure, two instances of the same browser do not get the same connection instance. At the moment, the usage of localStorage 'identifies' an already connected player and reuses the old Connection struct instance */ closingMutex sync.Mutex } func NewConnection(options ...func(*Connection)) *Connection { connection := Connection{ ID: uuid.New(), rxBuffer: newMessageBuffer(100), txBuffer: newMessageBuffer(100), } for _, option := range options { option(&connection) } return &connection } func WithWebsocket(ws *gorillaws.Conn) func(*Connection) { return func(c *Connection) { c.ws = ws } } func WithContext(ctx context.Context) func(*Connection) { return func(c *Connection) { c.ctx = ctx } } func WithDisconnectCallback(cb func()) func(*Connection) { return func(c *Connection) { if cb != nil { c.disconnectCallback = cb } } } func (conn *Connection) SetForColor(color types.ChessColor) { conn.forColor = color } func (conn *Connection) SetDisconnectCallback(cb func()) { conn.disconnectCallback = cb } func (conn *Connection) HasWebsocketConnection() bool { return conn.ws != nil } func (conn *Connection) readFromRxBuffer() { for { _, msg, err := conn.ws.ReadMessage() if err != nil { conn.logConnection("while reading from websocket: %w", "ERROR:", err.Error()) conn.Close("") return } conn.rxBuffer.Insert(string(msg)) } } func (conn *Connection) writeTxBuffer() { for { msg := conn.txBuffer.Get() if conn.ws == nil { return } err := conn.ws.WriteMessage(gorillaws.TextMessage, []byte(msg)) if err != nil { conn.logConnection("while writing to websocket: %w", "ERROR:", err.Error()) return } } } func (conn *Connection) SetWebsocketConnection(ws *gorillaws.Conn) { if ws == nil { conn.logConnection("ERROR: setting ws = null") return } conn.ws = ws go conn.readFromRxBuffer() go conn.writeTxBuffer() defer conn.logConnection("websocket connection set") } func (conn *Connection) Write(msg string) { conn.logConnection("Writing message: ", string(msg)) conn.txBuffer.Insert(msg) } func (conn *Connection) Read() []byte { msg := conn.rxBuffer.Get() return []byte(msg) } func (conn *Connection) Close(msg string) { /* This is a hack, do not try this at home. See more details at the definition of Connection */ conn.closingMutex.Lock() defer conn.closingMutex.Unlock() if conn == nil || conn.ws == nil { return } conn.logConnection("closing websocket connection") conn.ws.WriteMessage(gorillaws.TextMessage, []byte(msg)) conn.ws.Close() conn.ws = nil conn.txBuffer.Insert("we do this to make txBuffer.Get() return") } func (con *Connection) logConnection(v ...string) { a := "" for _, s := range v { a += s + ", " } log.Println(a, "on connection ", con.ID, ", for color ", con.forColor.String()) }