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)
}
}