@@ -2,7 +2,10 @@ package handler
22
33import (
44 "encoding/json"
5+ "net"
56 "path/filepath"
7+ "strings"
8+ "syscall"
69 "time"
710
811 "github.com/gofiber/contrib/websocket"
@@ -42,6 +45,36 @@ func NewUiDevHandler(uiDevService ports.UiDevServiceInterface, scrollService por
4245 }
4346}
4447
48+ // isConnectionError checks if the error is related to a broken connection
49+ func isConnectionError (err error ) bool {
50+ if err == nil {
51+ return false
52+ }
53+
54+ // Check for common connection error patterns
55+ errStr := strings .ToLower (err .Error ())
56+ if strings .Contains (errStr , "broken pipe" ) ||
57+ strings .Contains (errStr , "connection reset" ) ||
58+ strings .Contains (errStr , "connection refused" ) ||
59+ strings .Contains (errStr , "use of closed network connection" ) {
60+ return true
61+ }
62+
63+ // Check for specific error types
64+ if netErr , ok := err .(* net.OpError ); ok {
65+ if netErr .Op == "write" {
66+ return true
67+ }
68+ }
69+
70+ // Check for syscall errors
71+ if errno , ok := err .(syscall.Errno ); ok {
72+ return errno == syscall .EPIPE || errno == syscall .ECONNRESET
73+ }
74+
75+ return false
76+ }
77+
4578// @Summary Enable development mode
4679// @ID enableDev
4780// @Tags ui, dev, druid, daemon
@@ -148,19 +181,23 @@ func (udh *UiDevHandler) Status(ctx *fiber.Ctx) error {
148181
149182// NotifyChange handles WebSocket connections for real-time file change notifications
150183func (udh * UiDevHandler ) NotifyChange (c * websocket.Conn ) {
151- defer func () {
152- if err := c .Close (); err != nil {
153- logger .Log ().Error ("Error closing WebSocket connection" , zap .Error (err ))
154- }
155- }()
156-
157184 // Set connection timeouts
158185 const (
159186 writeWait = 10 * time .Second
160187 pongWait = 60 * time .Second
161188 pingPeriod = (pongWait * 9 ) / 10
162189 )
163190
191+ // Create a done channel to signal when the connection should be closed
192+ done := make (chan struct {})
193+
194+ defer func () {
195+ close (done )
196+ if err := c .Close (); err != nil {
197+ logger .Log ().Debug ("Error closing WebSocket connection" , zap .Error (err ))
198+ }
199+ }()
200+
164201 c .SetReadDeadline (time .Now ().Add (pongWait ))
165202 c .SetPongHandler (func (string ) error {
166203 c .SetReadDeadline (time .Now ().Add (pongWait ))
@@ -199,17 +236,50 @@ func (udh *UiDevHandler) NotifyChange(c *websocket.Conn) {
199236 "watchedPaths" : udh .uiDevService .GetWatchedPaths (),
200237 "timestamp" : time .Now (),
201238 }
202- c .WriteJSON (connectMsg )
239+
240+ c .SetWriteDeadline (time .Now ().Add (writeWait ))
241+ if err := c .WriteJSON (connectMsg ); err != nil {
242+ logger .Log ().Error ("Failed to send initial connection message" , zap .Error (err ))
243+ return
244+ }
203245
204246 logger .Log ().Info ("WebSocket client connected for file change notifications" )
205247
206248 // Start ping ticker
207249 ticker := time .NewTicker (pingPeriod )
208250 defer ticker .Stop ()
209251
252+ // Start a goroutine to read messages (to handle pong responses and detect broken connections)
253+ go func () {
254+ defer func () {
255+ select {
256+ case <- done :
257+ // Connection is already being closed
258+ default :
259+ close (done )
260+ }
261+ }()
262+
263+ for {
264+ _ , _ , err := c .ReadMessage ()
265+ if err != nil {
266+ if isConnectionError (err ) {
267+ logger .Log ().Debug ("WebSocket client disconnected" , zap .Error (err ))
268+ } else {
269+ logger .Log ().Warn ("WebSocket read error" , zap .Error (err ))
270+ }
271+ return
272+ }
273+ }
274+ }()
275+
210276 // Handle messages and file changes
211277 for {
212278 select {
279+ case <- done :
280+ logger .Log ().Debug ("WebSocket connection done signal received" )
281+ return
282+
213283 case data := <- changesChan :
214284 if data == nil {
215285 logger .Log ().Info ("File change channel closed" )
@@ -232,7 +302,11 @@ func (udh *UiDevHandler) NotifyChange(c *websocket.Conn) {
232302
233303 c .SetWriteDeadline (time .Now ().Add (writeWait ))
234304 if err := c .WriteJSON (changeMessage ); err != nil {
235- logger .Log ().Error ("Failed to write file change to WebSocket" , zap .Error (err ))
305+ if isConnectionError (err ) {
306+ logger .Log ().Debug ("WebSocket client disconnected while sending file change" , zap .Error (err ))
307+ } else {
308+ logger .Log ().Error ("Failed to write file change to WebSocket" , zap .Error (err ))
309+ }
236310 return
237311 }
238312
@@ -243,7 +317,11 @@ func (udh *UiDevHandler) NotifyChange(c *websocket.Conn) {
243317 case <- ticker .C :
244318 c .SetWriteDeadline (time .Now ().Add (writeWait ))
245319 if err := c .WriteMessage (websocket .PingMessage , nil ); err != nil {
246- logger .Log ().Error ("Failed to send ping" , zap .Error (err ))
320+ if isConnectionError (err ) {
321+ logger .Log ().Debug ("WebSocket client disconnected during ping" , zap .Error (err ))
322+ } else {
323+ logger .Log ().Error ("Failed to send ping" , zap .Error (err ))
324+ }
247325 return
248326 }
249327 }
0 commit comments