func serveWebsocketIO( ws *websocket.Conn, portal binder.Portal, timeout time.Duration, logger log.Modular, stats metrics.Aggregator, ) { defer portal.Exit(timeout) // Signal to close var incomingCloseInt uint32 outgoingCloseChan := make(chan struct{}) // Signals that goroutine is closing incomingClosedChan := make(chan struct{}) outgoingClosedChan := make(chan struct{}) // Loop incoming messages. go func() { var err error defer func() { if err != nil { websocket.JSON.Send(ws, leapSocketServerMessage{ Type: "error", Error: err.Error(), }) } close(incomingClosedChan) }() for atomic.LoadUint32(&incomingCloseInt) == 0 { var msg leapSocketClientMessage if socketErr := websocket.JSON.Receive(ws, &msg); socketErr != nil { return } switch msg.Command { case "submit": if msg.Transform == nil { err = errors.New("submit error: transform was nil") return } var ver int if ver, err = portal.SendTransform(*msg.Transform, timeout); err != nil { return } websocket.JSON.Send(ws, leapSocketServerMessage{ Type: "correction", Version: ver, }) case "update": if msg.Position != nil || len(msg.Message) > 0 { portal.SendMessage(binder.Message{ Content: msg.Message, Position: msg.Position, Active: true, }) } case "ping": // Do nothing default: err = errors.New("command not recognised") } } }() // Loop outgoing messages. go func() { defer close(outgoingClosedChan) for { select { case <-outgoingCloseChan: return case tform, open := <-portal.TransformReadChan(): if !open { return } websocket.JSON.Send(ws, leapSocketServerMessage{ Type: "transforms", Transforms: []text.OTransform{tform}, }) case msg, open := <-portal.UpdateReadChan(): if !open { return } websocket.JSON.Send(ws, leapSocketServerMessage{ Type: "update", Updates: []binder.ClientUpdate{msg}, }) } } }() // If one channel closes, close the other, if the socket is being closed then close both. select { case <-incomingClosedChan: close(outgoingCloseChan) <-outgoingClosedChan portal.SendMessage(binder.Message{ Active: false, }) case <-outgoingClosedChan: atomic.StoreUint32(&incomingCloseInt, 1) <-incomingClosedChan portal.SendMessage(binder.Message{ Active: false, }) } }