package main import ( "fmt" "log" "net/http" "sync" "github.com/gorilla/websocket" ) var ( upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } dashboardHTML = ` Clipboard On The Go - Stats

Clipboard On The Go - Server Stats

Connected Clients: 0
Messages Relayed: 0
` ) type Client struct { conn *websocket.Conn identification string send chan []byte } type Server struct { clients map[*Client]bool register chan *Client unregister chan *Client broadcast chan *Message mu sync.RWMutex // Statistics statsEnabled bool messagesRelayed uint64 } type Message struct { identification string data []byte sender *Client } func newServer() *Server { return &Server{ clients: make(map[*Client]bool), register: make(chan *Client), unregister: make(chan *Client), broadcast: make(chan *Message), statsEnabled: EmbeddedServerStats, } } func (s *Server) run() { for { select { case client := <-s.register: s.mu.Lock() s.clients[client] = true s.mu.Unlock() log.Printf("Client registered with identification: %s", client.identification) case client := <-s.unregister: s.mu.Lock() if _, ok := s.clients[client]; ok { delete(s.clients, client) close(client.send) log.Printf("Client unregistered: %s", client.identification) } s.mu.Unlock() case message := <-s.broadcast: s.mu.RLock() for client := range s.clients { if client.identification == message.identification && client != message.sender { select { case client.send <- message.data: default: close(client.send) delete(s.clients, client) } } } s.mu.RUnlock() if s.statsEnabled { s.mu.Lock() s.messagesRelayed++ s.mu.Unlock() } } } } func (c *Client) readPump(server *Server) { defer func() { server.unregister <- c c.conn.Close() }() for { _, message, err := c.conn.ReadMessage() if err != nil { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { log.Printf("error: %v", err) } break } log.Printf("Received clipboard data from %s: %d bytes", c.identification, len(message)) server.broadcast <- &Message{ identification: c.identification, data: message, sender: c, } } } func (c *Client) writePump() { defer c.conn.Close() for message := range c.send { sendMessage(c.conn, websocket.TextMessage, message) log.Printf("Sent clipboard data to %s", c.identification) } } func sendMessage(conn *websocket.Conn, messageType int, message []byte) { err := conn.WriteMessage(messageType, message) if err != nil { log.Println("Error sending message:", err) } } func handleWebSocket(server *Server, w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Println("Error upgrading connection:", err) return } // First message should be identification _, identification, err := conn.ReadMessage() if err != nil { log.Println("Error reading identification:", err) conn.Close() return } client := &Client{ conn: conn, identification: string(identification), send: make(chan []byte, 256), } server.register <- client go client.writePump() go client.readPump(server) } func main() { port := EmbeddedServerPort if port == "" { port = "8080" } server := newServer() go server.run() http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { handleWebSocket(server, w, r) }) // Serve stats dashboard if enabled if EmbeddedServerStats { http.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") fmt.Fprint(w, dashboardHTML) }) http.HandleFunc("/stats/data", func(w http.ResponseWriter, r *http.Request) { server.mu.RLock() clients := len(server.clients) messages := server.messagesRelayed server.mu.RUnlock() w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"clients":%d,"messages":%d}`, clients, messages) }) } log.Printf("Server starting on port %s", port) if EmbeddedServerStats { log.Printf("Stats dashboard enabled at /stats") } err := http.ListenAndServe(":"+port, nil) if err != nil { log.Fatal("ListenAndServe error:", err) } }