// Package wsconn 將 gorilla/websocket.Conn 包裝成 net.Conn, // 讓 hashicorp/yamux 之類的 stream multiplexer 能在 WebSocket 之上運作。 // // 此 package 於 2026-04-22 從 POC (edge-ai-platform) 複製: // // Source: edge-ai-platform/server/pkg/wsconn/wsconn.go // Baseline commit: c9d56a62e23bc45554391123152ca90a07a60bdc // // 為什麼存兩份 wsconn(visiona-agent/internal/wsconn + server/pkg/wsconn): // // local-agent/server/ 與 local-agent/visiona-agent/ 是兩個獨立 Go module。 // server binary 內的 wsconn 由未來的 server 端 tunnel 使用(目前未使用), // visiona-agent Wails shell 的 tunnel client 不能跨 module import,所以 // 複製一份。兩份程式內容相同;未來若 tunnel 改放 server module 可合併。 // // 參考: // // .autoflow/04-architecture/adr/adr-008-tunnel-client-reuse.md (v2) package wsconn import ( "io" "net" "sync" "time" "github.com/gorilla/websocket" ) // Conn 將 *websocket.Conn 轉成 net.Conn 介面。 // 所有訊息以 Binary frame 送收。 type Conn struct { ws *websocket.Conn reader io.Reader rmu sync.Mutex wmu sync.Mutex } // New 把 WebSocket 連線包成 net.Conn。 func New(ws *websocket.Conn) *Conn { return &Conn{ws: ws} } // Read 讀取下一批 WebSocket binary frame 資料到 p。 // 若目前 reader 已耗盡,會向底層 websocket 請求下一個 frame。 func (c *Conn) Read(p []byte) (int, error) { c.rmu.Lock() defer c.rmu.Unlock() for { if c.reader != nil { n, err := c.reader.Read(p) if err == io.EOF { c.reader = nil if n > 0 { return n, nil } continue } return n, err } _, reader, err := c.ws.NextReader() if err != nil { return 0, err } c.reader = reader } } // Write 將 p 以 BinaryMessage 送出。 func (c *Conn) Write(p []byte) (int, error) { c.wmu.Lock() defer c.wmu.Unlock() err := c.ws.WriteMessage(websocket.BinaryMessage, p) if err != nil { return 0, err } return len(p), nil } // Close 關閉底層 WebSocket 連線。 func (c *Conn) Close() error { return c.ws.Close() } // LocalAddr 回傳底層 WebSocket 連線的本地位址。 func (c *Conn) LocalAddr() net.Addr { return c.ws.LocalAddr() } // RemoteAddr 回傳底層 WebSocket 連線的遠端位址。 func (c *Conn) RemoteAddr() net.Addr { return c.ws.RemoteAddr() } // SetDeadline 同時設定讀寫超時。 func (c *Conn) SetDeadline(t time.Time) error { if err := c.ws.SetReadDeadline(t); err != nil { return err } return c.ws.SetWriteDeadline(t) } // SetReadDeadline 設定讀取超時。 func (c *Conn) SetReadDeadline(t time.Time) error { return c.ws.SetReadDeadline(t) } // SetWriteDeadline 設定寫入超時。 func (c *Conn) SetWriteDeadline(t time.Time) error { return c.ws.SetWriteDeadline(t) }